从0开始搭建自己的 Solana 钱包工具(批量生成地址/SOL 与 USDT 转账)
很多官方钱包适合日常使用,但一旦你想做批量地址生成、资金归集、脚本化转账,就会发现现成工具往往不够灵活。尤其在 Solana 生态里,SPL Token、ATA、主网 RPC、租金账户这些细节一多,常用钱包很快就不够用了。
前一篇我已经整理过一套 TRON 钱包工具思路:从0开始搭建自己的TRON钱包工具(批量生成地址/USDT转账/质押投票)。这篇继续补上 Solana 版本,重点还是放在最常用、最容易脚本化的两个场景:地址批量生成,以及 SOL / USDT 转账。
这篇文章整理一套非常直接的 Solana 钱包工具思路,只做两件高频操作:
- 批量生成 Solana 钱包地址
- 查询余额并转账 SOL / USDT
对应 2 个 Python 脚本:
generate_wallet_solana.py:多进程批量生成私钥与地址solana_transfer.py:查询 SOL/USDT 余额,并自动执行 USDT 或 SOL 转账
下面按可执行流程拆开说明。
一、Solana 钱包与地址的基础概念
和 TRON、ETH 常见的 secp256k1 不一样,Solana 钱包默认使用 Ed25519 密钥体系。
Solana 地址本质上就是公钥的 Base58 编码,因此地址生成逻辑相对直接:
- 生成 32 字节私钥
- 推导 32 字节公钥
- 将公钥做 Base58 编码,得到钱包地址
通常导出时会把 私钥 + 公钥 拼成 64 字节,再做 Base58 编码。这也是很多脚本和钱包工具里常见的 priv_b58 格式。
如果你要转的是 USDT,需要额外理解一个概念:
- Solana 上的 USDT 不是“直接挂在主地址上”
- 它属于 SPL Token
- 每个地址对应某个 Token 时,都需要一个关联代币账户,也就是 ATA(Associated Token Account)
所以做 USDT 转账时,除了发送地址和接收地址,还必须处理双方的 ATA。
二、环境准备
1. 安装依赖
pip install solana solders spl-token pynacl base58
如果你只是生成地址,核心依赖是:
pynaclbase58
如果你要发起链上转账,还需要:
solanasoldersspl-token
2. 运行前说明
- 脚本里会处理真实私钥,只建议在可信环境中运行。
- 不要把导出的 csv 文件、Base58 私钥字符串提交到公开仓库。
- 主网 RPC 建议替换成你自己的服务商节点,公开 RPC 在高频请求下容易限流。
三、批量生成 Solana 钱包地址(generate_wallet_solana.py)
这个脚本适合做地址池、批量分发钱包、归集前预生成地址。
核心逻辑很简单:
- 用
SigningKey.generate()生成 Ed25519 私钥 - 推导公钥
- 公钥做 Base58 编码得到地址
- 将
私钥 + 公钥拼接后做 Base58 编码,得到可导入的priv_b58 - 多进程并发写入多个 csv 文件
1. 运行方式
python3 generate_wallet_solana.py 1000000
如果不传参数,默认生成 1000000 个地址。
2. 输出格式
输出目录类似:
wallet-tool/sol_batch/1746500000000_T16_N1000000_sol_wallets/
每个 worker 会生成一个 csv,单行格式如下:
私钥hex,Base58私钥,Solana地址
其中第二列最实用,因为它可以直接用于后面的转账脚本。
3. 完整脚本(generate_wallet_solana.py)
#!/usr/bin/env python3
import os
import time
import base58
import multiprocessing as mp
from nacl.signing import SigningKey
BASE_DIR = "wallet-tool/sol_batch"
DEFAULT_COUNT = 1000000
def generate_batch(args):
count, worker_id, outdir = args
filepath = os.path.join(outdir, f"sol_wallets_{worker_id}.csv")
with open(filepath, "w", buffering=1024*1024) as f:
for _ in range(count):
sk = SigningKey.generate()
priv = sk.encode()
pub = sk.verify_key.encode()
address = base58.b58encode(pub).decode()
priv_b58 = base58.b58encode(priv + pub).decode()
f.write(f"{priv.hex()},{priv_b58},{address}\n")
def create_output_dir(total):
ts = int(time.time() * 1000)
folder = f"{ts}_T{mp.cpu_count()}_N{total}_sol_wallets"
path = os.path.join(BASE_DIR, folder)
os.makedirs(path, exist_ok=True)
return path
def main():
import sys
total = int(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_COUNT
cpu = mp.cpu_count()
outdir = create_output_dir(total)
per_worker = total // cpu
print(f"Generating {total} SOL wallets")
start = time.time()
args = [(per_worker, i, outdir) for i in range(cpu)]
with mp.Pool(cpu) as p:
p.map(generate_batch, args)
elapsed = time.time() - start
print("Done")
print(f"Time: {elapsed:.2f} sec")
print(f"Speed: {int(total/elapsed):,} wallets/sec")
if __name__ == "__main__":
main()
4. 使用建议
- 当前脚本使用
total // cpu分配任务,余数会被丢掉。 - 如果你要求“严格生成 N 个地址”,需要额外补一个 remainder worker。
- 输出的 csv 风险极高,建议生成完马上加密归档。
四、查询余额并转账 SOL / USDT(solana_transfer.py)
第二个脚本负责做归集或手动转账。
执行逻辑是:
- 从命令行读取 Base58 私钥
- 推导发送地址
- 查询 SOL 余额
- 计算发送地址的 USDT ATA 并查询 USDT 余额
- 如果 USDT 大于 0,优先走 SPL Token 转账
- 如果没有 USDT 但有足够 SOL,则执行原生 SOL 转账
1. 先修改配置
至少确认这几个常量:
RPC_URL = "https://api.mainnet-beta.solana.com"
USDT_MINT = Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
DECIMALS = 6
TO_ADDRESS_STR = "2Z1DtyfHifHeVZSYkN9J2xzy9KjC4oeHNLeFn2U9VVXU" #你的收款地址
这里的 USDT_MINT 是 Solana 主网 USDT 的 Mint 地址,DECIMALS = 6 对应 USDT 精度。示例里直接把收款地址写死在常量里,方便测试;正式使用时,更建议改成命令行参数或配置文件。
2. 完整脚本(solana_transfer.py)
#!/usr/bin/env python3
import sys
from solana.rpc.api import Client
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import transfer, TransferParams
from solders.transaction import Transaction
from spl.token.instructions import (
transfer_checked,
TransferCheckedParams,
get_associated_token_address,
create_associated_token_account,
)
from spl.token.constants import TOKEN_PROGRAM_ID
RPC_URL = "https://api.mainnet-beta.solana.com"
USDT_MINT = Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
DECIMALS = 6
TO_ADDRESS = Pubkey.from_string("2Z1DtyfHifHeVZSYkN9J2xzy9KjC4oeHNLeFn2U9VVXU") #你的收款地址
try:
priv_b58 = sys.argv[1]
keypair = Keypair.from_base58_string(priv_b58)
except Exception:
print("Key Error: please pass a Base58 private key")
print("Example: python3 solana_transfer.py 5Mpx...")
sys.exit(1)
sender = keypair.pubkey()
client = Client(RPC_URL)
print(f"Sender: {sender}")
sol_balance = client.get_balance(sender).value / 1e9
sender_ata = get_associated_token_address(sender, USDT_MINT)
usdt_balance = 0
try:
token_res = client.get_token_account_balance(sender_ata)
usdt_balance = float(token_res.value.ui_amount)
except Exception:
usdt_balance = 0
print(f"SOL Balance: {sol_balance}")
print(f"USDT Balance: {usdt_balance}")
if usdt_balance > 0:
amt = float(input(f"Enter USDT amount (max {usdt_balance}): "))
receiver_ata = get_associated_token_address(TO_ADDRESS, USDT_MINT)
instructions = []
if client.get_account_info(receiver_ata).value is None:
print("Creating ATA for receiver...")
instructions.append(
create_associated_token_account(sender, TO_ADDRESS, USDT_MINT)
)
instructions.append(
transfer_checked(
TransferCheckedParams(
program_id=TOKEN_PROGRAM_ID,
source=sender_ata,
mint=USDT_MINT,
dest=receiver_ata,
owner=sender,
amount=int(amt * 10**DECIMALS),
decimals=DECIMALS,
)
)
)
txn = Transaction(instructions, sender, [keypair])
res = client.send_transaction(txn, keypair)
print(f"USDT Sent: https://solscan.io/tx/{res.value}")
elif sol_balance > 0.001:
amt = float(input(f"Enter SOL amount (max {sol_balance}): "))
ix = transfer(
TransferParams(
from_pubkey=sender,
to_pubkey=TO_ADDRESS,
lamports=int(amt * 1e9),
)
)
latest_blockhash = client.get_latest_blockhash().value.blockhash
txn = Transaction.new_signed_with_payer(
[ix],
sender,
[keypair],
latest_blockhash,
)
res = client.send_transaction(txn)
print(f"SOL Sent: https://solscan.io/tx/{res.value}")
else:
print("Insufficient balance for transfer")
3. 运行方式
python3 solana_transfer.py 你的Base58私钥
执行后会先打印地址和余额,例如:
Sender: 7t9U...abcd
SOL Balance: 0.0831
USDT Balance: 15.0
如果当前地址持有 USDT,脚本会优先让你输入 USDT 转账数量;如果没有 USDT,但有足够 SOL,则进入 SOL 转账流程。
4. 这个脚本解决了什么问题
这个脚本虽然不复杂,但已经覆盖了 Solana 转账里最容易踩坑的几个点:
- 自动从 Base58 私钥恢复钱包
- 自动计算发送方和接收方的 USDT ATA
- 接收方没有 ATA 时自动创建
- 同一个脚本里兼容 SPL Token 转账和原生 SOL 转账
对于归集场景,这已经足够实用。
五、验证方式
1. 先做小额测试
无论是 SOL 还是 USDT,都先用极小金额验证一次:
0.001 SOL
1 USDT
确认以下几件事:
- 交易哈希能正常打印
- Solscan 能查到交易
- 接收地址余额确实变化
- 如果是 USDT,接收方 ATA 能被自动创建
2. 用 Solscan 检查结果
脚本执行成功后会输出:
https://solscan.io/tx/交易哈希
直接打开就可以确认:
- 是否上链成功
- 具体扣除了多少手续费
- 是否触发了 ATA 创建
- Token 数量和接收地址是否正确
六、常见问题与直接修复
1. ModuleNotFoundError: No module named 'spl'
说明 SPL Token 相关依赖没有装好,重新安装:
pip install spl-token solana solders
2. Base58 私钥导入失败
常见原因:
- 传入了 32 字节 seed,而不是
私钥 + 公钥的 64 字节导出格式 - 私钥字符串被截断
- csv 读取时带了换行或空格
最直接的排查办法就是先打印字符串长度,并确认它来自生成脚本的第二列。
3. USDT 转账时报余额足够但交易失败
Solana 上 Token 转账不只看 Token 余额,还要看:
- 是否有足够 SOL 支付手续费
- 接收方是否需要新建 ATA
- 新建 ATA 时发送方是否有足够 SOL 支付租金
如果钱包里只有 USDT 没有 SOL,交易通常也会失败。
4. 批量生成数量不准确
原因就是前面提到的:
per_worker = total // cpu
余数没有处理,所以严格模式下需要补逻辑。
5. 转账地址被多次覆盖
有些测试脚本会写成:
TO_ADDRESS_STR = "地址1"
TO_ADDRESS_STR = "地址2"
TO_ADDRESS_STR = "地址3"
最终只有最后一个地址生效。正式使用时一定只保留一个目标地址,或者改成命令行参数。
七、总结
如果你的目标是自己掌控 Solana 钱包的底层流程,这套工具已经够用了:一个脚本负责生成地址,一个脚本负责查询并转账。它没有做成“大而全”的钱包应用,但在批量地址池、自动归集、脚本化打款这类场景里反而更直接、更实用。
后续如果要继续扩展,也很自然,比如:
- 批量读取 csv 做归集
- 支持多个收款地址轮询
- 增加代币 mint 参数,支持其他 SPL Token
- 把目标地址、金额、RPC 改成命令行参数
建议先把最基础的“生成地址 + 小额转账 + 链上验证”跑通,再继续扩展批量归集和多币种支持。这样排查问题最省时间,也更适合后面逐步演进成自己的钱包工具链。
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/build_own_solana_wallet.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。