A screenshot is not a payment: verifying a USDT transfer on chain in code
"Flash USDT" is sold on Telegram as software that sends Tether which "expires after 90 days". The demo looks convincing: the buyer gives a wallet address, a balance appears, the wallet shows the USDT logo. Then comes the
"Flash USDT" is sold on Telegram as software that sends Tether which "expires after 90 days". The demo looks convincing: the buyer gives a wallet address, a balance appears, the wallet shows the USDT logo. Then comes the licence fee, the activation key, the "network energy", the server slot. The files often carry a wallet stealer as a bonus.
The trick in the demo is simple. The balance is real, but it belongs to a token contract the seller deployed for a few dollars and named "Tether USD". Name and symbol are free text in any ERC-20. The only real identity of a token is its contract address. USDT balances live in Tether's own contract, and no program on your laptop can write to it.
Earlier in this series we looked at address poisoning, where the attacker fakes the recipient. This time the attacker fakes the payment. If you build a shop, a P2P desk, an invoicing tool or a payout bot, this is the check your code should make before anything is released.
The five checks a human should do
- Open the transaction yourself. Take the TxID and paste it into the explorer. Never trust a link or a screenshot from the payer.
- Status must be confirmed. Pending or failed means you have not been paid.
- Token contract must be Tether's. Compare it with the official address for that chain, character by character.
- Recipient and amount must match. Your own full address, the agreed amount. Payments to an "escrow" or third party do not count.
- Your official USDT balance must have gone up. On-chain balances do not expire, so any "it will appear in 24 hours" story is a red flag.
Official contracts:
| Chain | USDT contract |
|---|---|
| Ethereum | 0xdAC17F958D2ee523a2206206994597C13D831ec7 |
| Tron | TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t |
| BNB Smart Chain | 0x55d398326f99059fF775485246999027B3197955 |
| Polygon PoS | 0xc2132D05D31c914a87C6611C10748AEb04B58e8F |
Bookmark Tether's own list on tether.to instead of copying addresses from chats or search results.
Automating it (Ethereum, ethers v6)
import { ethers } from "ethers";
const USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; // Tether on Ethereum
const iface = new ethers.Interface([
"event Transfer(address indexed from, address indexed to, uint256 value)",
]);
export async function verifyUsdt(provider, txHash, myAddress, expectedUsdt, minConfirmations = 12) {
const receipt = await provider.getTransactionReceipt(txHash);
if (!receipt || receipt.status !== 1) return { ok: false, reason: "not confirmed or failed" };
const confirmations = (await provider.getBlockNumber()) - receipt.blockNumber + 1;
if (confirmations < minConfirmations) return { ok: false, reason: `only ${confirmations} confirmations` };
let received = 0n;
for (const log of receipt.logs) {
if (log.address.toLowerCase() !== USDT.toLowerCase()) continue; // the line that matters
let parsed;
try { parsed = iface.parseLog(log); } catch { continue; }
if (parsed?.name !== "Transfer") continue;
if (parsed.args.to.toLowerCase() !== myAddress.toLowerCase()) continue;
received += parsed.args.value;
}
const expected = ethers.parseUnits(String(expectedUsdt), 6); // USDT uses 6 decimals
return received >= expected
? { ok: true, received: ethers.formatUnits(received, 6) }
: { ok: false, reason: "no matching USDT transfer to your address" };
}
The contract filter is the whole defence. A fake token can copy the name, the symbol, the logo and even the first and last characters of Tether's address. It cannot emit a Transfer event from Tether's contract.
Two details that bite people in production:
- Sum the logs. One transaction can contain several transfers. Adding them up handles split payments and stops a tiny real transfer from "confirming" a large order.
- Wait for confirmations. A transaction that is mined but later reorganised away was never a payment. Pick a number that fits the order value.
Red flags to put in front of your staff
- A licence, activation, "energy" or unlock fee to receive a balance.
- A screenshot or video instead of a transaction hash.
- An explorer link on a domain that only looks like etherscan or tronscan.
- A token you have to "add manually" by pasting an address the payer sent you.
- Any request to connect a wallet or sign something to receive a payment. Receiving never needs a signature.
Our full write-up of how flash USDT sellers run the licence ladder: Flash USDT software sellers: the licence is the trap.
Question for you: if your product accepts stablecoins, do you check the token contract in the transfer log, or do you rely on a payment provider to do it for you?
If this was useful, a star on crypto-scam-glossary, our open glossary of scam patterns, helps other developers find it.
Written with AI assistance and fact checked by the CryptoStrapon team.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.