春江暮客

春江暮客的个人学习分享网站

Build Your Own Solana Wallet Toolkit (Batch Address Generation / SOL and USDT Transfer)

2026-05-06 Technology

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:

  1. Batch generate Solana wallet addresses
  2. Check balances and transfer SOL / USDT

The toolkit uses two Python scripts:

  • generate_wallet_solana.py — multi-process batch generation of private keys and addresses
  • solana_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:

  1. Generate a 32-byte private key
  2. Derive the 32-byte public key
  3. 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:

  • pynacl
  • base58

If you also want on-chain transfers, add:

  • solana
  • solders
  • spl-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:

  1. Generate an Ed25519 private key with SigningKey.generate()
  2. Derive the public key
  3. Base58 encode the public key into a wallet address
  4. Concatenate private key + public key, then Base58 encode it into an importable priv_b58
  5. 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:

  1. Read a Base58 private key from the command line
  2. Derive the sender address
  3. Query the SOL balance
  4. Compute the sender USDT ATA and query the USDT balance
  5. If USDT is greater than 0, do an SPL Token transfer first
  6. 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:

  1. Restore a wallet from a Base58 private key
  2. Derive both sender and receiver USDT ATAs automatically
  3. Auto-create the receiver ATA if it does not exist
  4. 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:

  1. The script prints a transaction hash
  2. Solscan can find the transaction
  3. The receiver balance actually changes
  4. 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 key export 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.

友情链接

其它