Scam Alerts4 min read

Detecting fake USDT deposits in code: a developer's guide

Do not inspect tokens and compare symbol() to 'USDT' — that asks the attacker to self-certify. Subscribe to Transfer events emitted by one hardcoded contract address, credit only after a confirmation depth with a receipt re-check for reorgs, and enforce idempotency with a database unique constraint on (txHash, logIndex).

Human verification fails eventually — it is repetitive, and scammers are patient. Code does not get tired. Here is how to build a deposit watcher that counterfeit tokens are structurally invisible to, and the seven mistakes that make one exploitable.

The wrong way

A check like *“read symbol() from the token, and if it equals USDT then credit the user”* accepts every counterfeit ever deployed. symbol(), name() and decimals() all return values the deployer chose, and deploying a contract that returns "USDT" costs about two dollars — the mechanism is here.

The right way: filter at the log level

Do not scan transactions and inspect tokens. Subscribe to Transfer events emitted by one specific contract address. A counterfeit contract emits its own logs, from its own address, which your filter never matches. You are not detecting fakes — you are structurally incapable of seeing them.

  • Hardcode the contract address and decimals as constants. Not in editable config, not from an environment variable someone can typo — wrong here means crediting counterfeits.
  • Build the filter from the contract instance (usdt.filters.Transfer()) and query a block range, rather than scanning all transactions.
  • Assert log.address matches your constant anyway, as defence in depth, even though the filter already guarantees it.
  • Skip any log whose to is not one of your deposit addresses, and any whose value is zero — zero-value transfers are address-poisoning noise.
  • Record (transactionHash, logIndex) as the unique key for the deposit. Never the transaction hash alone: one transaction can carry several transfers.
  • Format the amount with formatUnits(value, DECIMALS) using your per-chain constant, never tx.value — which is always zero for token transfers.

Confirmations and reorgs

Seeing a log is not settlement. Blocks get reorganised, and a transfer that existed at height *N* can be absent at *N+2*. Record on sight, credit on depth. When the depth threshold is reached, re-fetch the receipt: if it is missing, or its status is not 1, or the block hash no longer matches what you stored, the deposit never happened — mark it orphaned rather than crediting it.

ChainSuggested depthApprox. wait
BNB Chain15~45 s
Ethereum12~2.5 min
Tron19~1 min
Polygon128~4 min (deeper reorgs observed)

Idempotency — the bug that costs more than fake tokens

Counterfeit tokens cost you one bad trade. A double-credit bug costs you your float. Restarts, retries, overlapping scan ranges and concurrent workers all replay the same log.

Enforce uniqueness in the database, not in application logic. A UNIQUE (tx_hash, log_index) constraint plus INSERT … ON CONFLICT DO NOTHING RETURNING id makes a replay a no-op. An application-level “check, then insert” races under concurrency; a unique constraint does not. Write the deposit row and the balance update inside one transaction, and only apply the balance change when the insert actually returned a row.

Seven mistakes that make a watcher exploitable

#MistakeWhat it lets throughFix
1Matching on symbol() / name()Every counterfeit tokenMatch the contract address only
2Contract address in editable configOne typo or insider = total lossHardcode; assert at boot
3Crediting on pendingDropped and replaced transactionsConfirmation depth + receipt re-check
4Assuming 18 decimals everywhere10¹² over-credit on a 6-decimal chainPer-chain constant, asserted at boot
5No unique constraint on (hash, logIndex)Double credits on any replayDB-level unique index
6Trusting tx.value for token transfersAlways 0 — silent mis-creditingRead the Transfer log args
7Ignoring zero-value transfersAddress-poisoning noise in user historySkip value == 0 explicitly

Operational hardening

  • Two independent RPC providers. Cross-check confirmations before crediting large amounts. A single compromised or lagging node should never authorise a credit alone.
  • Persist the last scanned block, and overlap. Re-scan the last ~50 blocks every pass. Idempotency makes overlap free; gaps make it necessary.
  • Alert on unknown-token transfers to deposit addresses. You will not credit them, but a spike is an early warning that your users are being targeted.
  • Cap auto-credit. Above a threshold, route to manual review — cheap insurance against a bug class you have not thought of yet.
  • Never expose a “manual credit by hash” endpoint without a second approval. Social engineering targets the admin panel once the code is solid.
  • Log the emitting contract address on every processed event. If you are ever wrong, you will want to know exactly what you accepted.

The principle

Everything above reduces to one sentence: identity is the contract address, and nothing else is identity. Names, symbols, logos, screenshots and user claims are all attacker-controlled. Build your system so a counterfeit token is not *rejected* but *invisible*, and you close the entire category permanently — including variants nobody has written yet.

Everything in this guide is production practice on FastXP2P, not a thought experiment — contract-address allowlisting, confirmation depth, reorg re-checks, decimal assertions and idempotent crediting on every deposit.

Trade USDT safely

Frequently asked questions

Why not just check the token symbol?

Because symbol() returns a string the contract deployer chose. A counterfeit token returns 'USDT' exactly as convincingly as the real one. Only the contract address is an identity the attacker cannot forge.

How many confirmations should I wait for?

As a starting point: 15 on BNB Chain, 12 on Ethereum, 19 on Tron and 128 on Polygon. Re-fetch the receipt at that depth and confirm the block hash still matches what you recorded, to catch reorgs.

References

Primary sources for the rules and mechanics described above. Rules change — check the original before you act on anything here.

  1. 1National Cyber Crime Reporting PortalMinistry of Home Affairs, Government of India
  2. 2How Tether worksTether
  3. 3Tether reserves and transparency reportsTether
  4. 4Sanchar Saathi — report fraud communicationsDepartment of Telecommunications, Government of India
  • engineering
  • web3
  • deposits
  • ethers.js

Keep reading

Scam Alerts3 min read

Verify real USDT in 30 seconds

Official USDT contract addresses for BNB Chain, Ethereum, Tron, Polygon, Arbitrum, Avalanche, Optimism and Solana — plus the check that exposes any fake USDT before you pay.

13 Aug 2026

Trade USDT with trade-lock protection

Verified merchants, UPI and IMPS, ~2-minute settlement, and a full record on every order. Start from ₹500.