Build Your Own TRON Wallet Toolkit (Batch Address Generation / USDT Transfer / Staking & Voting)
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:
- Batch generate wallet addresses (ETH + TRON)
- Sweep USDT / TRX to a collection address
- Stake, unstake, vote, delegate energy
The three scripts are:
generate_wallet.py— multi-process batch generation of private key / public key / ETH address / TRON addresswallet_transfer.py— query balances and transfer USDT + TRXwallet_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:
- Fast confirmations: Block interval is ~3 seconds; on-chain transactions typically confirm within seconds.
- 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 prepends0x41to 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 keycoincurvederives the public keykeccakhashes 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 // cputo 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:
- Derives the sender address from the private key
- Queries USDT (TRC20) balance + TRX balance + Energy / Bandwidth
- Interactively prompts for amounts and sends USDT and TRX
1. Configure first
Edit these constants before running:
TO_ADDRESS— your collection / destination addressUSDT_CONTRACT— mainnet USDT contract:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6tapi_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 BANDWIDTHunstake— initiate unfreezewithdraw— withdraw unlocked TRX after the ~3-day lock periodclaim— collect voting rewardsvote— 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 tofrozenV2for 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")
5. Recommended Improvements
If you plan to use these scripts long-term:
- Move private keys and API keys to environment variables (
os.environ). - Add a confirmation prompt before broadcasting (show address, amount, estimated fee).
- Encrypt generated CSV files (GPG or full-disk encryption).
- Build a batch sweep mode — read the CSV and automatically consolidate dust balances into a master wallet.
- 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:
- Check TRX balance — enough to burn as fee?
- Check account resource (Energy / FreeNet).
- 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.
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/en/build_own_tron_wallet.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。