链上一笔 USDT 转账≠能加余额:USDT TRC20 入账前的五道过滤
做自动收款(代充、数字商品、软件授权)出海,最常见的一步是“监听 TRON 链上 USDT TRC20 到账,然后入账”。但链上出现一笔转账,跟“可以给用户加余额”是两回事。TRON 的出块、打包与最终确认之间,有几处可以被利用的缝隙:交易失败也会被打包,假币可以同名,未确认的交易可能被回滚。只监听事件不做过滤,等于给攻击者留了一个假充值入口。入账前需要先过五道过滤。
先对齐链上事实
USDT TRC20 是智能合约,合约地址固定为 TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t,decimals = 6。链上 value 是最小单位整数,3000000000 才等于 3000 USDT。
入账监听的是 Transfer(address indexed from, address indexed to, uint256 value) 事件,topics[0] 为:
ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
它由 keccak256("Transfer(address,address,uint256)") 得到,注意签名串不能带空格。
监听方式按场景选:
- 全节点扫块:solidity 触发,只拿固化块。适合已有自建节点、交易量大、需要强确认的场景。
- TronGrid 事件 API:
getEventsByContractAddress轮询,返回的是解码后的事件。初创期低吞吐可用。 - Webhook / 事件订阅:TronGrid account trigger。
确认策略要提前说明:事件出现在最新块,不等于不可逆。TRON 约 3 秒一个块,19 块确认约 57 秒才是 finality。solidity 区块相当于至少 19 个活跃 SR 已在它之上或更高处出块。
入账前的五道过滤
从上到下依次检查,前一道不过直接丢弃。
1. 去重:同一笔转账会被反复扫到
补块、重试、多节点都会导致同一笔 Transfer 被重复扫到。幂等键用 chain:blockNumber:txID:logIndex 存库并建唯一索引。注意不能只对 txID 去重——一笔交易里可能携带多个 Transfer log,只取跟你相关的那个 logIndex。
scanned_logs 表参考字段:
id VARCHAR(128) PRIMARY KEY, -- tron:::
tx_hash,
to_address,
amount DECIMAL(30,6),
block_num BIGINT,
created_at
2. 校验合约地址:只认官方 USDT
有人会部署 symbol、name 也叫 USDT 的假币合约然后转给你。只判断“是 Transfer 事件 + 代币叫 USDT”,会把垃圾币当真钱。
合约地址必须硬编码等于:
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
TRON 返回的日志地址会去掉 0x41 前缀(40 位 hex)。处理方式有两种:补回 41 再做 Base58 还原成 T 开头地址比较;或者统一在 hex 层比较。不相等的一律丢弃。
3. 回执必须 SUCCESS
攻击者可以让一笔 USDT 转账“上链但失败”:不给够能量触发 OUT_OF_ENERGY,或让合约 REVERT。失败交易同样会被打包进区块,扫块时能看到,但实际没转成功。只看“存在这笔交易”就加款,等于被 0 元刷单。
原生扫块看回执 ret.contractRet,必须为 SUCCESS。TronGrid 事件 API 即使带 onlyConfirmed: true,仍然要核对回执。OUT_OF_ENERGY / REVERT / 非 SUCCESS 一律拒绝。
4. 确认数 ≥ 19,防回滚
不要等交易出现在最新块就立刻入账。攻击者可以借分叉、重放把未固化交易抹掉或双花。用当前已固化块高度减去交易所在块高度,确认数 >= 19(约 1 分钟)才进账。
不要自己推最新块高度,直接用节点 solidity(固化块)高度做差。阈值可配:调低了要承担孤块/回滚风险,调到 30+ 更稳,但用户到账更慢。
5. 金额过滤 + 精度校验
设置最小入账金额,过滤掉 0 金额、测试币、骚扰小额。value 是最小单位整数,比较时用整数,不要用浮点:3000000000 才等于 3000。
只认 value,不要信 memo/备注——TRC20 的 memo 很多钱包根本不填,不能作为匹配依据。
关联订单匹配:同地址同额订单会碰撞
多笔同额订单共用同一个收款地址时,链上金额会碰撞。做法:
- 不下发原始金额,而是下发
actual_amount给用户; - 同地址同额订单用
+0.01递增制造金额差,扫到链上记录后按金额精确匹配; - 用幂等表防止同一笔转账匹配到两笔订单;
- 匹配不上且订单未过期,进人工核对队列,不要静默丢单。
现成实现
- BEpusdt:多链 USDT 收款网关,内置区块扫描、金额合法范围过滤、回调。
- tronecho:TRON 实时地址转账监听,事件带
chain:block:txid:logIndex稳定幂等 ID,走 NATS 分发。
两个项目都未逐行验证,接入前自行核对代码。
不适合这么做的场景
交易量极高、要秒级入账的业务,轮询事件 API 有延迟和限流,应该接自建固化节点事件订阅或 TronGrid webhook。对账要求严格,建议事件驱动 + 独立事务流水,另跑日对账,用链上余额/交易兜底。私钥托管安全是另一条线,不在这个方案里。
上线前先验证
建议先在 Shasta 测试网自己发一笔失败交易和一笔确认交易,看代码能否分别正确拦截与放行。确认阈值 19 是社区与多套开源网关通行的默认值,需要按自身业务实测对齐。