Support tickets all sound the same:
“My seed never left the phone. TronLink PIN is fine. USDT is gone. I got hacked.”
Most of the time they were not seed-compromised.
On TRON, the three highest-volume money-loss patterns leave the private key untouched:
- Unlimited
approve→ delayedtransferFrom - Address poisoning (lookalike dust in history)
- Clipboard hijack (malware rewrites paste)
If you build wallets, Telegram mini-apps, OTC desks, or support bots, mislabeling all three as “hacked seed” burns users and teaches the wrong fix.
This post is the field guide: how each path works on-chain, what calldata to look for, and a 60-second triage order.
Read-only tooling (no seed): tronsec.io/app — AML · approvals · TX decode · URL scan.
TL;DR triage
| Symptom | Likely path | First check |
|---|---|---|
| USDT left; user “never signed a transfer” | Approval drain | Historical approve + spender allowance |
| User “copied the right address” from history | Poisoning | Full 34-char compare; spam dust TXs |
| Paste looks wrong / changes after copy | Clipboard malware | Clean machine; verify on second device |
| Seed typed into a website | Actual key theft | New wallet + move residual (if any) |
Path 1 — Silent allowance (the classic)
TRC-20 mirrors ERC-20. The victim signs:
approve(spender, type(uint256).max);
Days later the spender pulls without a new popup:
transferFrom(victim, attacker, amount);
Closing the tab does nothing. Disconnecting WalletConnect does nothing. The allowance lives in the token contract.
Calldata you should recognize
4-byte selectors (hex, first 8 chars of data):
| Selector | Function | Risk |
|---|---|---|
095ea7b3 |
approve(address,uint256) |
High if amount is max / spender unknown |
23b872dd |
transferFrom(address,address,uint256) |
The actual drain |
a9059cbb |
transfer(address,uint256) |
Normal send — different story |
39509351 / d73dd623
|
increaseAllowance / increaseApproval
|
Same family as approve |
On TRON this sits inside TriggerSmartContract → data. Official USDT:
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
Why drains are delayed on purpose
Immediate empty → instant revoke + URL burn.
Delayed pull → victim refills USDT; attacker harvests later across a set.
Support mistake: only looking at the last outbound transfer.
Correct: list spenders with remaining allowance, especially unlimited.
Revoke = approve(spender, 0). It stops future pulls. It does not reverse past transferFrom.
Paste-only audit: tronsec.io/app/#approvals
Path 2 — Address poisoning (history lies)
Attacker generates an address that shares your prefix and/or suffix (cheap for short patterns — same math as vanity mining).
They send a tiny USDT/TRC-10 dust TX so the lookalike appears in TronScan / wallet activity.
Human habit: copy from history, glance at first/last 4 characters, confirm.
Why vanity culture makes this worse
Custom suffixes (…TRX, …PAY) help your brand — and also train people to trust ends of strings. Attackers mine the same habit.
Rule: never copy a counterparty from inbound spam. Use an address book / QR from a trusted channel. Compare all 34 Base58 chars.
Related deep-dive on our side: dust / lookalikes are not “lucky airdrops.”
Path 3 — Clipboard hijack (seed still offline)
Malware on Windows/macOS watches the clipboard. You copy a real deposit address; within ~10–15 ms it is replaced with an attacker address. You paste into TronLink and send.
No WalletConnect. No approve. No fake dApp. Just a wrong destination.
Delivery we keep seeing: fake Telegram installers (lookalike domains → ZIP → EXE that adds Defender exclusions, then the real Telegram runs so the desktop app “looks legit”).
Support mistake: “Must be seed leak.”
Correct: verify paste on a second clean device; check Defender exclusions / odd services; stop moving funds on the infected PC.
If funds already moved: archive TXIDs + attacker address, AML the hop, report to exchange if material.
Decode before you blame the seed
Before the next support reply, paste the TXID into a decoder:
Questions the decode should answer in under a minute:
- Is this
transfer,approve, ortransferFrom? - Which contract? Is it real USDT or a fake ticker?
- If
approve— spender + is amount max-uint? - If
transfer— destination equals what the user thought they pasted?
If the answer is approve to an unknown spender, the incident class is permission, not seed.
Minimal code: spot approve in hex
If you are writing an internal triage script, the first 4 bytes of contract data (after stripping 0x) are enough for a coarse label:
const SELECTORS = {
a9059cbb: 'transfer',
'095ea7b3': 'approve',
'23b872dd': 'transferFrom',
};
function labelCalldata(dataHex) {
const h = dataHex.replace(/^0x/i, '').toLowerCase();
const sel = h.slice(0, 8);
return SELECTORS[sel] || `unknown:${sel}`;
}
// approve(spender, amount) → spender is bytes 16..36 of the first arg word
function peekApproveSpender(dataHex) {
const h = dataHex.replace(/^0x/i, '').toLowerCase();
if (h.slice(0, 8) !== '095ea7b3') return null;
// ABI: 4 byte selector + 32-byte address word (left-padded)
const word = h.slice(8, 8 + 64);
const tronHex41 = '41' + word.slice(24); // 20-byte addr → TRON hex with 41 prefix
return tronHex41;
}
(Convert 41… hex → Base58Check for human UI — same as any TRON toolkit.)
Do not ship a “revoke helper” that asks for a seed. Revoke is a signed approve(0) in the user’s wallet.
Builder checklist (stop shipping footguns)
If you ship a TRON dApp:
- [ ] Prefer exact allowances when UX allows
- [ ] Show spender address + amount in plain language before wallet confirm
- [ ] Publish official router addresses in docs
- [ ] Never use copy like “Enable trading” for an unlimited USDT approve
- [ ] After one-off campaigns, remind users to revoke
If you run support:
- [ ] Ask for TXID first, not “did you share your seed?”
- [ ] Check approvals history before telling them to abandon the wallet
- [ ] Separate malware / poisoning playbooks from key-exfiltration playbooks
One workflow that actually scales
-
TXID → decode (
approvevstransfervstransferFrom) - Address → approvals list (unlimited + unknown spenders)
- Counterparty / hop → AML / risk if funds already left
- URL they clicked → phish heuristics before anyone reconnects
All four are free and paste-only here:
TRONSEC is a read-only TRON security terminal. We never ask for seed phrases.
Bottom line
“Seed intact + USDT gone” is usually not a cryptography failure. It is a product and habit failure: standing allowances, truncated addresses, and infected paste buffers.
Name the path correctly → pick the right fix → stop burning wallets that were never compromised.
If you only remember one habit: decode the TX before you reset the seed.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.