春江暮客

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

Build Your Own TRON Wallet Toolkit (Batch Address Generation / USDT Transfer / Staking & Voting)

2026-04-24 Technology

Most official crypto wallets come with one restriction or another — no staking, no voting, no free-form scripting. Sometimes you don’t need a heavy full-featured wallet app; you just need to automate the TRON operations you actually use, with full control over your own private keys and the freedom to extend the tooling however you like.

This post covers 3 Python scripts that form a runnable lightweight toolkit:

  1. Batch generate wallet addresses (ETH + TRON)
  2. Sweep USDT / TRX to a collection address
  3. Stake, unstake, vote, delegate energy

The three scripts are:

  • generate_wallet.py — multi-process batch generation of private key / public key / ETH address / TRON address
  • wallet_transfer.py — query balances and transfer USDT + TRX
  • wallet_manipulate.py — stake, unstake, withdraw, claim rewards, vote for SR, delegate / undelegate energy

Here is the full executable walkthrough.

0. Blockchain & TRON Network Background

A blockchain is a decentralized ledger — every transfer, contract call, staking action, and vote is packaged as an on-chain transaction, verified by all nodes, permanently recorded, and publicly queryable by anyone.

The TRON network has two properties worth understanding before scripting a wallet:

  1. Fast confirmations: Block interval is ~3 seconds; on-chain transactions typically confirm within seconds.
  2. Resource fee model: TRON uses two resources to pay for transactions — Energy and Bandwidth. Direct TRX transfers consume Bandwidth; TRC20 contract calls (e.g. USDT transfers) consume Energy. If a resource runs out, the system burns TRX to cover the shortfall. So when scripting a wallet, you need to track not just “is the balance enough?” but also “are the resources enough?” — which is why this post separates the transfer script from the resource management script.

How TRON addresses are derived: TRON and Ethereum share the same elliptic curve (secp256k1) key pair system. The only difference is address encoding: Ethereum takes the last 20 bytes of keccak256(public key) and prefixes 0x; TRON prepends 0x41 to the same 20 bytes and applies Base58Check encoding. This means one private key simultaneously maps to one ETH address and one TRON address.

1. Setup

1. Install Python dependencies

pip install tronpy coincurve base58 eth-hash

2. Before running

  • You need your own TronGrid API Key (free tier available at trongrid.io).
  • These scripts handle real private keys — only run them in a secure, offline-capable environment.
  • Never commit private keys or API keys to a public repository.

2. Batch Generate TRON Addresses (generate_wallet.py)

This script is an “address factory”:

  • os.urandom(32) generates a cryptographically random private key
  • coincurve derives the public key
  • keccak hashes the public key and takes the last 20 bytes → ETH address
  • Prepend 0x41 + Base58Check encode → TRON address

The script uses multiprocessing to saturate all CPU cores. On a 16-core machine, generating 1,000,000 addresses takes around 30 seconds. ETH and TRON addresses share the same private key.

1. Run

python generate_wallet.py 1000000

Without an argument, defaults to 1,000,000 addresses.

2. Output

Output directory pattern:

wallet-tool/dkg_batch/1713920000000_T16_N1000000_wallets/

One CSV file per worker. Each line:

privkey_hex,pubkey_hex,eth_address,tron_address

3. Notes

  • The script distributes total // cpu to each worker; any remainder is dropped. If you need exactly N wallets, add a remainder worker.
  • Encrypt the output CSV files immediately — they are high-value sensitive data.

4. Full script (generate_wallet.py)

#!/usr/bin/env python3

import os
import time
import base58
from eth_hash.auto import keccak
import multiprocessing as mp
from coincurve import PrivateKey


BASE_DIR = "wallet-tool/dkg_batch"
DEFAULT_COUNT = 1000000


def generate_batch(args):

    count, worker_id, outdir = args

    filepath = os.path.join(outdir, f"wallets_{worker_id}.csv")

    with open(filepath, "w", buffering=1024*1024) as f:

        for _ in range(count):

            priv = os.urandom(32)
            pk = PrivateKey(priv)

            pub = pk.public_key.format(compressed=False)[1:]

            # ETH address
            eth_addr = keccak(pub)[-20:]
            eth = "0x" + eth_addr.hex()

            # TRON address
            tron_bytes = b'\x41' + eth_addr
            tron = base58.b58encode_check(tron_bytes).decode()

            f.write(
                f"{priv.hex()},{pub.hex()},{eth},{tron}\n"
            )


def create_output_dir(total):

    ts = int(time.time()*1000)

    folder = f"{ts}_T{mp.cpu_count()}_N{total}_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} wallets")
    print(f"CPU cores: {cpu}")
    print(f"Output dir: {outdir}")
    print()

    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} seconds")
    print(f"Speed: {int(total/elapsed):,} wallets/sec")


if __name__ == "__main__":
    main()

3. Sweep & Transfer (wallet_transfer.py)

This script does three things:

  1. Derives the sender address from the private key
  2. Queries USDT (TRC20) balance + TRX balance + Energy / Bandwidth
  3. Interactively prompts for amounts and sends USDT and TRX

1. Configure first

Edit these constants before running:

  • TO_ADDRESS — your collection / destination address
  • USDT_CONTRACT — mainnet USDT contract: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
  • api_key — your own TronGrid API key

2. Run

python wallet_transfer.py <PRIVATE_KEY_HEX>

Output on start:

Sender: T...
USDT Balance: 10.5
TRX Balance: 12.3
Energy Available: 65000
Bandwidth Available: 1500

Then enter the amounts when prompted.

3. Fees and resources

  • TRC20 USDT transfers consume Energy. If Energy is insufficient, TRX is burned.
  • fee_limit(30_000_000) sets the maximum TRX (in sun) that can be burned as a fee fallback.
  • A successful transfer prints the TX ID and a direct Tronscan link.

4. Full script (wallet_transfer.py)

#!env python
from tronpy import Tron
from tronpy.keys import PrivateKey
from tronpy.providers import HTTPProvider
import sys

# ===== CONFIG =====
PRIVATE_KEY = sys.argv[1]
TO_ADDRESS = 'TF4YTndBMXGEB7EDz9G4L8WBPUi8PQjwtn'  # replace with your address

CHAIN = "mainnet"

USDT_CONTRACT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
DECIMALS = 6

# ===== CONNECT =====
client = Tron(provider=HTTPProvider(
    endpoint_uri="https://api.trongrid.io",
    api_key="YOUR_API_KEY"  # replace with your own key
))

private_key = PrivateKey(bytes.fromhex(PRIVATE_KEY))
sender = private_key.public_key.to_base58check_address()
print("Sender:", sender)

contract = client.get_contract(USDT_CONTRACT)

# ===== GET BALANCES =====
usdt_balance = contract.functions.balanceOf(sender)
human_usdt_balance = usdt_balance / (10 ** DECIMALS)
trx_balance = client.get_account_balance(sender)
print(f"USDT Balance: {human_usdt_balance}")
print(f"TRX Balance: {trx_balance}")

# Energy info
account_resource = client.get_account_resource(sender)
energy_available = account_resource.get("EnergyLimit", 0) - account_resource.get("EnergyUsed", 0)
bandwidth_available = account_resource.get("FreeNetLimit", 0) - account_resource.get("FreeNetUsed", 0)
print(f"Energy Available: {energy_available}")
print(f"Bandwidth Available: {bandwidth_available}")

# ===== SEND USDT =====
if usdt_balance > 0:
    try:
        usdt_input = float(input(f"Enter USDT amount to send (max {human_usdt_balance}): "))
    except ValueError:
        print("Invalid amount")
        usdt_input = 0

    if usdt_input > 0:
        if usdt_input > human_usdt_balance:
            print("Amount exceeds USDT balance")
        else:
            usdt_amount_in_units = int(usdt_input * (10 ** DECIMALS))
            try:
                txn = (
                    contract.functions.transfer(TO_ADDRESS, usdt_amount_in_units)
                    .with_owner(sender)
                    .fee_limit(30_000_000)  # use TRX to pay if no energy
                    .build()
                    .sign(private_key)
                )
                result = txn.broadcast().wait()
                tx_id = result["id"]
                print(f"Sent {usdt_input} USDT successfully")
                print("TX:", "https://tronscan.org/#/transaction/" + tx_id)
            except Exception as e:
                print("Error sending USDT:", e)
else:
    print("No USDT to send, skipping...")

# ===== SEND TRX =====
try:
    trx_input = float(input(f"Enter TRX amount to send (max {trx_balance}): "))
except ValueError:
    print("Invalid amount")
    trx_input = 0

if trx_input > 0:
    if trx_input > trx_balance:
        print("Amount exceeds TRX balance")
    else:
        try:
            trx_tx = (
                client.trx.transfer(sender, TO_ADDRESS, int(trx_input * 1_000_000))
                .build()
                .sign(private_key)
            )
            result = trx_tx.broadcast().wait()
            tx_id = result["id"]
            print(f"Sent {trx_input} TRX successfully")
            print("TX:", "https://tronscan.org/#/transaction/" + tx_id)
        except Exception as e:
            print("Error sending TRX:", e)
else:
    print("No TRX to send")

4. Stake / Vote / Delegate (wallet_manipulate.py)

This script is a CLI control panel supporting the following actions:

  • stake — freeze TRX for ENERGY (default) or BANDWIDTH
  • unstake — initiate unfreeze
  • withdraw — withdraw unlocked TRX after the ~3-day lock period
  • claim — collect voting rewards
  • vote — vote for Super Representatives (earns TRX rewards)
  • delegate — delegate Energy to another address (lending)
  • undelegate — revoke delegation

1. Run examples

# Stake 100 TRX for Energy
python wallet_manipulate.py <PRIVATE_KEY_HEX> stake 100

# Unstake 50 TRX
python wallet_manipulate.py <PRIVATE_KEY_HEX> unstake 50

# Withdraw unlocked TRX (after ~3 days)
python wallet_manipulate.py <PRIVATE_KEY_HEX> withdraw

# Claim voting rewards
python wallet_manipulate.py <PRIVATE_KEY_HEX> claim

# Vote for two SRs
python wallet_manipulate.py <PRIVATE_KEY_HEX> vote TXXX...:10 TYYY...:20

# Delegate energy to an address
python wallet_manipulate.py <PRIVATE_KEY_HEX> delegate TReceiver...

# Revoke delegation
python wallet_manipulate.py <PRIVATE_KEY_HEX> undelegate TReceiver...

2. Built-in validation

  • Checks TRX balance before staking.
  • Reads tron_power (falls back to frozenV2 for Stake 2.0 accounts) before voting and rejects if votes exceed available power.
  • Vote format must be <SR_ADDRESS>:<VOTES>; any deviation raises an error before broadcast.

3. Resource parameter

Set RESOURCE at the top of the script based on what you need:

  • ENERGY: for TRC20 contract calls (e.g. USDT transfers) — one transfer consumes ~65,000 Energy.
  • BANDWIDTH: for plain TRX transfers — one transfer consumes ~300 Bandwidth.
  • Note: staking transactions themselves also consume a small amount of Bandwidth. If Bandwidth runs out, TRX will be burned to cover it.
RESOURCE = "ENERGY"   # default: for USDT transfers
# RESOURCE = "BANDWIDTH"  # switch to bandwidth mode

4. Full script (wallet_manipulate.py)

#!env python

from tronpy import Tron
from tronpy.keys import PrivateKey
from tronpy.providers import HTTPProvider
import sys

# https://tronscan.org/#/sr
# ===== CONFIG =====
TRONGRID_API_KEY = "YOUR_API_KEY"  # replace with your own key
CHAIN = "mainnet"
RESOURCE = "ENERGY"
# RESOURCE = "BANDWIDTH"

# ===== USAGE CHECK =====
if len(sys.argv) < 3:
    print("\nUsage:")
    print("  python wallet_manipulate.py <PRIVATE_KEY> stake <amount>")
    print("  python wallet_manipulate.py <PRIVATE_KEY> unstake <amount>")
    print("  python wallet_manipulate.py <PRIVATE_KEY> withdraw")
    print("  python wallet_manipulate.py <PRIVATE_KEY> claim")
    print("  python wallet_manipulate.py <PRIVATE_KEY> vote <SR_ADDRESS>:<VOTES> [...]")
    sys.exit()

PRIVATE_KEY = sys.argv[1]
ACTION = sys.argv[2].lower()

# ===== CONNECT =====
provider = HTTPProvider(api_key=TRONGRID_API_KEY)
client = Tron(provider=provider)

private_key = PrivateKey(bytes.fromhex(PRIVATE_KEY))
sender = private_key.public_key.to_base58check_address()
print("\nSender:", sender)

trx_balance = client.get_account_balance(sender)
print("TRX Balance:", trx_balance)

# =========================
# ===== STAKE TRX =====
# =========================
if ACTION == "stake":
    if len(sys.argv) < 4:
        raise Exception("Enter amount to stake")

    amount_trx = float(sys.argv[3])
    amount_sun = int(amount_trx * 1_000_000)

    if amount_trx > trx_balance:
        raise Exception("Not enough TRX")

    txn = (
        client.trx.freeze_balance(
            owner=sender,
            amount=amount_sun,
            resource=RESOURCE
        )
        .build()
        .sign(private_key)
    )

    result = txn.broadcast().wait()
    print("\nStaked", amount_trx, "TRX for", RESOURCE)
    print("TX ID:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

# =========================
# ===== UNSTAKE TRX =====
# =========================
elif ACTION == "unstake":
    if len(sys.argv) < 4:
        raise Exception("Enter amount to unstake")

    amount_trx = float(sys.argv[3])
    amount_sun = int(amount_trx * 1_000_000)

    txn = (
        client.trx.unfreeze_balance(
            owner=sender,
            resource=RESOURCE,
            unfreeze_balance=amount_sun
        )
        .build()
        .sign(private_key)
    )

    result = txn.broadcast().wait()
    print("\nUnstaking initiated:", amount_trx, "TRX")
    print("Lock period: ~3 days")
    print("TX ID:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

# =========================
# ===== WITHDRAW TRX =====
# =========================
elif ACTION == "withdraw":

    txn = (
        client.trx.withdraw_stake_balance(sender)
        .build()
        .sign(private_key)
    )

    result = txn.broadcast().wait()
    print("\nWithdrawn unlocked TRX")
    print("TX ID:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

# =========================
# ===== CLAIM REWARDS =====
# =========================
elif ACTION == "claim":

    txn = (
        client.trx.withdraw_rewards(sender)
        .build()
        .sign(private_key)
    )

    result = txn.broadcast().wait()
    print("\nRewards claimed!")
    print("TX ID:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

# =========================
# ===== VOTE FOR SR =====
# =========================
elif ACTION == "vote":
    if len(sys.argv) < 4:
        raise Exception("Enter at least one SR vote in the format <SR_ADDRESS>:<VOTES>")

    account_info = client.get_account(sender)
    print("\n=== ACCOUNT INFO ===")
    print(account_info)

    tron_power = account_info.get("tron_power", 0) / 1_000_000

    if tron_power == 0:
        tron_power = sum(
            item.get("amount", 0)
            for item in account_info.get("frozenV2", [])
            if isinstance(item, dict) and "amount" in item
        ) / 1_000_000

    print(f"\nTRON Power available: {tron_power}")

    if tron_power <= 0:
        raise Exception("No TRON Power available. Freeze TRX first to vote.")

    votes_input = sys.argv[3:]
    votes = []
    total_votes = 0

    for v in votes_input:
        parts = v.split(":")

        if len(parts) != 2:
            raise Exception(f"Invalid format: {v} (use <SR_ADDRESS>:<VOTES>)")

        sr_address = parts[0].strip()

        try:
            vote_count = int(parts[1])
        except ValueError:
            raise Exception(f"Invalid vote number in: {v}")

        if vote_count <= 0:
            raise Exception(f"Vote count must be > 0: {v}")

        votes.append((sr_address, vote_count))
        total_votes += vote_count

    print("\nVotes parsed:", votes)
    print("Total votes requested:", total_votes)

    if total_votes > tron_power:
        raise Exception(
            f"Total votes ({total_votes}) exceed TRON Power ({tron_power})"
        )

    votes = [(str(a), int(b)) for a, b in votes]
    txn = client.trx.vote_witness(sender, *votes).build().sign(private_key)

    result = txn.broadcast().wait()
    print("\nVote broadcasted successfully!")
    print("TX ID:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

elif ACTION == "delegate":
    if len(sys.argv) < 4:
        raise Exception("Usage: delegate <receiver_address>")

    receiver = sys.argv[3]
    amount_trx = 5000
    amount_sun = int(amount_trx * 1_000_000)

    txn = (
        client.trx.delegate_resource(
            owner=sender,
            receiver=receiver,
            balance=amount_sun,
            resource=RESOURCE
        )
        .build()
        .sign(private_key)
    )

    result = txn.broadcast().wait()
    print("\nEnergy delegated")
    print("To:", receiver)
    print("TRX staked:", amount_trx)
    print("TX:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

elif ACTION == "undelegate":
    if len(sys.argv) < 4:
        raise Exception("Usage: undelegate <receiver_address>")

    receiver = sys.argv[3]
    amount_trx = 5000
    amount_sun = int(amount_trx * 1_000_000)

    txn = (
        client.trx.undelegate_resource(
            owner=sender,
            receiver=receiver,
            balance=amount_sun,
            resource=RESOURCE
        )
        .build()
        .sign(private_key)
    )

    result = txn.broadcast().wait()
    print("\nEnergy undelegated")
    print("From:", receiver)
    print("TRX unstaked:", amount_trx)
    print("TX:", result["id"])
    print("https://tronscan.org/#/transaction/" + result["id"])

else:
    print("Invalid action. Use: stake | unstake | withdraw | claim | vote | delegate | undelegate")

If you plan to use these scripts long-term:

  1. Move private keys and API keys to environment variables (os.environ).
  2. Add a confirmation prompt before broadcasting (show address, amount, estimated fee).
  3. Encrypt generated CSV files (GPG or full-disk encryption).
  4. Build a batch sweep mode — read the CSV and automatically consolidate dust balances into a master wallet.
  5. Add structured logging + retry logic for network instability.

6. Troubleshooting

1) USDT balance exists but transfer fails

Common causes: insufficient Energy, fee_limit too low, node rate-limiting.

Steps:

  1. Check TRX balance — enough to burn as fee?
  2. Check account resource (Energy / FreeNet).
  3. Try a different node or raise fee_limit.

2) Vote fails: “No TRON Power”

The account has not staked yet, or the stake has not taken effect on-chain.

Run stake first, confirm it appears on Tronscan, then retry the vote.

3) Private key format error

Scripts expect a 64-character hex private key without a 0x prefix. Copy it directly from the generated CSV.


Summary

These three scripts cover the full TRON wallet core loop:

  • Address generation (generate_wallet.py)
  • Asset transfer and collection (wallet_transfer.py)
  • Resource and governance operations (wallet_manipulate.py)

If your goal — like mine — is something controllable, batchable, and automatable, this lightweight Python approach fits production use far better than any GUI wallet.

Next up: a batch sweep script with risk controls that consolidates an entire address pool into a single master wallet in one run.

友情链接

其它