If your product handles USDT across networks, there's a good chance your token model treats it as one asset with several "networks." That model is wrong in ways that matter for accounting, risk and support.
In practice, what wallets display as "USDT" includes at least three structurally different things:
| Kind | Example chains | Issued by | How it moves cross-chain |
|---|---|---|---|
| Native USDT | Ethereum, Tron, Solana, TON, Avalanche | Tether | It doesn't. Each chain has its own supply |
| Exchange-pegged USDT | BNB Chain (Binance-Peg BSC-USD) | Binance, backed by USDT | Via the issuer |
| Omnichain USDT0 | Arbitrum, Optimism, Polygon, newer EVM chains | Everdawn Labs, backed by USDT locked on Ethereum | LayerZero burn-and-mint |
They're all worth about a dollar. They don't share an issuer, a redemption path or a risk profile.
1. Model issuer and kind, not just chain
A registry keyed only by (chain, contract) is necessary but not sufficient. Add the fields that describe what the token actually is:
{
"symbol": "USDT",
"chain": "arbitrum",
"contract": "0x…",
"decimals": 6,
"kind": "omnichain",
"issuer": "everdawn-labs",
"backing": "native-usdt-locked-ethereum",
"redeemable_with_tether": false
}
Why it matters:
- Accounting. Treating Binance-Peg USDT and USDT0 as interchangeable with native USDT in your books means your stablecoin exposure has counterparties you aren't tracking.
- Risk limits. A depeg, an exploit in a bridge's lockbox or an issuer problem hits one kind without touching the others. Exposure limits should be per kind.
- Support. "I sent USDT and received USDT0" is a real ticket. If your system can't tell them apart, neither can your agents.
2. Never resolve tokens by symbol
On most EVM chains, anyone can deploy a contract named "USDT." Explorers on BNB Chain list many of them. Combined with the legitimate variety above, symbol-based resolution is guaranteed to misfire eventually.
Keep an explicit allowlist of (chain, contract) pairs and reject everything else, including in admin tools and indexers.
3. Tron needs its own transfer logic
TRC20 isn't an EVM chain with a different address prefix. Its fee model is different:
- Bandwidth covers transaction size. Each account gets a small free daily allowance.
- Energy covers smart contract execution. A TRC20 transfer is a contract call, so it needs energy.
If the sending account hasn't staked TRX for energy, the network burns TRX to pay for it. If there isn't enough TRX, the transaction fails, and the TRX it consumed is still gone.
Practical consequences for payouts:
// Before a TRC20 payout, check the sender can actually pay for it.
const resources = await tronWeb.trx.getAccountResources(hotWallet);
const energyAvailable = (resources.EnergyLimit || 0) - (resources.EnergyUsed || 0);
if (energyAvailable < estimatedEnergy) {
const trxNeeded = estimateTrxBurn(estimatedEnergy - energyAvailable);
assertBalanceAtLeast(hotWallet, trxNeeded);
}
Also budget for first-transfer cost. Sending USDT to a Tron address that has never held it costs roughly double, because the transfer has to create the token balance. If you sweep deposits to many fresh addresses, this shows up in your costs.
Set feeLimit on TRC20 transactions deliberately. Too low and they fail mid-execution; too high and a bug can burn more than you meant.
4. Cross-network conversion is a trade, not a transfer
If your product lets users "move USDT from Tron to Ethereum," it's executing a swap between two different tokens, whatever the UI says. That means:
- Quote it like a swap. Show the amount received net of the destination network fee and any spread. It won't be exactly 1:1.
- Track it like a swap. Two legs, two chains, two transaction IDs, and a failure state for each.
- Label the output precisely. If the destination is USDT0, say USDT0. Don't let a user find out from their wallet.
5. Gas tokens on both sides
A user receiving USDT on a chain where they hold no native token can't move it. On Ethereum, that's ETH; on Tron, TRX; on BNB Chain, BNB.
If your product delivers USDT to a user's wallet on a network that's new for them, say so before they confirm. Better still, point them to how to get the gas token.
Checklist
- Registry fields for
kind,issuer,backingand redeemability - Allowlist by
(chain, contract); never resolve by symbol - Exposure limits per token kind, not per symbol
- Tron payouts check energy and TRX before sending;
feeLimitset deliberately - First-transfer costs to fresh Tron addresses budgeted
- Cross-network moves modeled as two-leg swaps, labeled with the actual output token
- Users warned when they'll receive on a chain without gas
Disclosure: I work on SwapCore (swapcore.exchange), a non-custodial instant exchange. Cross-network USDT conversions are one of the most common things people use us for, and most of the edge cases above come from them.
Top comments (0)