Build Your Own Solana Wallet Toolkit (Batch Address Generation / SOL and USDT Transfer)
Official wallets are fine for daily use, but once you want batch address generation, fund collection, or scripted transfers, ready-made tools start to feel limiting. On Solana in particular, once SPL Tokens, ATAs, RPC endpoints, and rent-funded accounts enter the picture, manual wallet clicks stop scaling very well.
In an earlier post, I put together a similar TRON wallet workflow: Build Your Own TRON Wallet Toolkit (Batch Address Generation / USDT Transfer / Staking & Voting). This article adds the Solana version and keeps the scope just as practical, focusing on the two tasks that are used most often and script cleanly: batch address generation and SOL / USDT transfers.
This post builds a small practical Solana wallet toolkit around two high-frequency tasks:
- Batch generate Solana wallet addresses
- Check balances and transfer SOL / USDT
The toolkit uses two Python scripts:
generate_wallet_solana.py— multi-process batch generation of private keys and addressessolana_transfer.py— query SOL/USDT balances and transfer USDT or SOL automatically
Below is the full runnable breakdown.
1. Solana Wallet and Address Basics
Unlike TRON or Ethereum, which commonly use secp256k1, Solana wallets use the Ed25519 key system by default.
A Solana address is simply the Base58 encoding of the public key, so the generation flow is straightforward:
- Generate a 32-byte private key
- Derive the 32-byte public key
- Base58 encode the public key to get the wallet address
In practice, many tools export private key + public key as a 64-byte value, then Base58 encode it. That is the common priv_b58 format used by many Solana scripts and import flows.
For USDT transfers, there is one more concept you need to understand:
- On Solana, USDT does not live directly on the main wallet address
- It is an SPL Token
- Each wallet needs an Associated Token Account (ATA) for that specific token
So a USDT transfer has to manage both the sender ATA and the receiver ATA.
2. Setup
1. Install dependencies
pip install solana solders spl-token pynacl base58
If you only want address generation, the core dependencies are:
pynaclbase58
If you also want on-chain transfers, add:
solanasoldersspl-token
2. Before running
- These scripts handle real private keys, so only run them in a trusted environment.
- Never commit exported CSV files or Base58 private key strings into a public repository.
- For production or high-frequency use, replace the public RPC with your own provider endpoint.
3. Batch Generate Solana Wallet Addresses (generate_wallet_solana.py)
This script is useful for building an address pool, pre-generating wallets, or preparing collection targets.
The logic is simple:
- Generate an Ed25519 private key with
SigningKey.generate() - Derive the public key
- Base58 encode the public key into a wallet address
- Concatenate
private key + public key, then Base58 encode it into an importablepriv_b58 - Use multiple processes to write CSV files in parallel
1. Run
python3 generate_wallet_solana.py 1000000
If you do not pass an argument, it defaults to 1000000 addresses.
2. Output format
Output directory example:
wallet-tool/sol_batch/1746500000000_T16_N1000000_sol_wallets/
Each worker writes one CSV file. Each line looks like this:
private_key_hex,base58_private_key,solana_address
The second column is the most practical one because it can be passed directly into the transfer script.
3. Full script (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. Notes
- The script distributes work with
total // cpu, so any remainder is dropped. - If you need exactly N wallets, add a remainder worker.
- The generated CSV files are extremely sensitive. Encrypt and archive them immediately.
4. Check Balances and Transfer SOL / USDT (solana_transfer.py)
The second script handles manual transfer or fund collection.
Its flow is:
- Read a Base58 private key from the command line
- Derive the sender address
- Query the SOL balance
- Compute the sender USDT ATA and query the USDT balance
- If USDT is greater than 0, do an SPL Token transfer first
- If there is no USDT but enough SOL, do a native SOL transfer
1. Configure first
At minimum, confirm these constants:
RPC_URL = "https://api.mainnet-beta.solana.com"
USDT_MINT = Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")
DECIMALS = 6
TO_ADDRESS_STR = "your receiver address"
USDT_MINT is the Solana mainnet USDT mint, and DECIMALS = 6 matches USDT precision. In the example, the receiver address is hardcoded for convenience during testing; for real use, it is better to move it into a command-line argument or a config file.
2. Full script (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("your receiver address")
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. Run
python3 solana_transfer.py your_base58_private_key
The script prints the address and balances first, for example:
Sender: 7t9U...abcd
SOL Balance: 0.0831
USDT Balance: 15.0
If the address holds USDT, it prompts for a USDT amount first. If there is no USDT but there is enough SOL, it falls back to a native SOL transfer.
4. What this script already solves
Even though the script is small, it already covers the parts of Solana transfers that tend to cause the most friction:
- Restore a wallet from a Base58 private key
- Derive both sender and receiver USDT ATAs automatically
- Auto-create the receiver ATA if it does not exist
- Handle SPL Token transfer and native SOL transfer in one flow
For wallet sweeping or lightweight operations, this is already very practical.
5. Validation
1. Start with small amounts
Whether you send SOL or USDT, test with tiny amounts first:
0.001 SOL
1 USDT
Confirm these four things:
- The script prints a transaction hash
- Solscan can find the transaction
- The receiver balance actually changes
- For USDT, the receiver ATA is created automatically when needed
2. Check on Solscan
On success, the script prints:
https://solscan.io/tx/transaction_hash
Open it and verify:
- whether the transaction landed on-chain
- how much fee was charged
- whether ATA creation was triggered
- whether the token amount and receiver are correct
6. Troubleshooting
1. ModuleNotFoundError: No module named 'spl'
Your SPL Token dependencies are incomplete. Reinstall them:
pip install spl-token solana solders
2. Base58 private key import fails
Common causes:
- You passed a 32-byte seed instead of the 64-byte
private key + public keyexport format - The string was truncated
- Your CSV input still contains whitespace or a trailing newline
The fastest check is to verify that the key came from the second column of the generator output.
3. USDT transfer fails even though the USDT balance looks correct
On Solana, token transfers depend on more than the token balance:
- you still need enough SOL to pay network fees
- the receiver may need a new ATA
- ATA creation requires rent funding in SOL
If the wallet only holds USDT and no SOL, the transaction will often fail.
4. Batch count is lower than expected
The reason is this line:
per_worker = total // cpu
The remainder is ignored. If you need an exact count, add explicit remainder handling.
5. Receiver address is overwritten multiple times
Quick test scripts often end up like this:
TO_ADDRESS_STR = "address_1"
TO_ADDRESS_STR = "address_2"
TO_ADDRESS_STR = "address_3"
Only the last one takes effect. For real usage, keep exactly one destination address or move it to a command-line argument.
7. Summary
If your goal is to control the Solana wallet flow yourself, this toolkit is already enough: one script generates addresses, and one script checks balances and transfers funds. It is not a heavy all-in-one wallet app, but for address pools, fund sweeping, and scripted payouts, that narrower focus is exactly what makes it useful.
Natural next steps are:
- read CSV files in batch and sweep multiple wallets
- rotate across multiple destination addresses
- add a mint parameter to support other SPL tokens
- move the destination address, amount, and RPC into command-line arguments
The better sequence is to get the base flow working first: generate addresses, send a small transfer, and verify it on-chain. After that, it is much easier to extend the tooling into batch sweeping or multi-token support without losing control of the debugging process.
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/en/build_own_solana_wallet.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。