代币余额差一百倍:Decimals读取错误怎么修正
余额差一百倍,不是代币丢了,是你在看余额的时候没按代币的 decimals(小数位数)换算。 链上存的永远是原始整数,显示的时候才除以 10 ** decimals。USDC 是 6 位,WBTC 是 8 位,大多数代币是 18 位。读错位数,显示就会差 10 的 n 次方倍。
1. 先确认问题根源:你用的是"原始值"还是"显示值"
代币余额在链上以最小单位存储——USDC 存的是"微美元"(1 USDC = 1,000,000 单位),WBTC 存的是"聪"(1 WBTC = 100,000,000 单位)。钱包和区块浏览器会自动帮你换算,但如果你自己调 RPC 读数据、写脚本,或者在看合约返回值时忘了换算,就会看到一串很长的整数,看起来像"多了好几个零"。
前置条件:你有一笔交易的链上数据(raw balance),或者在钱包里看到了跟区块浏览器对不上的余额。
2. 第一步:用区块浏览器确认真实余额
步骤1:查区块浏览器上的代币余额
做什么:打开对应链的区块浏览器(以太坊用 Etherscan,BSC 用 BSCScan),把钱包地址贴进去查余额。
怎么做:MetaMask 官方文档里也列了这个操作——如果钱包显示不对,先拿区块浏览器上的余额做基准。
做到什么程度算完成:浏览器上显示的余额和你预期的一致。如果不一致,说明你的钱包或代码读错了精度。
步骤2:看浏览器上"换算后的值"和"原始值"
做什么:在代币详情页或交易详情里,找到原始值(Raw Value)或"Value in Wei"类型的字段。
怎么做:对比浏览器显示的友好数值和原始数值之间的倍数关系。比如 USDC,原始值
1000000显示为1.000000;如果是 18 位代币,原始值1000000000000000000显示为1。如果你代码里读到的原始值除以10 ** 18但实际 decimals 是 6,就会多出 10^12 倍——显示成"一百倍"完全可能。做到什么程度算完成:你确认了该代币的 decimals 到底是多少,以及你的换算公式是否匹配。
3. 第二步:修正读取代币小数位数的方法
步骤3:动态读取代币的 decimals() 方法,不要写死 18
做什么:在你的代码或脚本里,调用代币合约的
decimals()函数获取真实精度,而不是假设所有代币都是 18 位。怎么做:
情况A(用 ethers.js / Web3.js):
await tokenContract.decimals()获取返回值,然后用parseUnits(value, decimals)或formatUnits(balance, decimals)来转换。情况B(手动调试):在区块浏览器的"Contract" → "Read Contract"里找
decimals函数,看返回值。
做到什么程度算完成:你的代码不再用
18这个硬编码数字,而是动态从链上拿真实值。
步骤4:检查钱包里添加的代币合约地址是否正确
做什么:如果钱包显示不对,可能是添加了错误的代币合约地址(比如旧合约或冒牌代币)。
怎么做:
删除钱包里显示异常的那个代币。
从 CoinMarketCap 或项目官方文档复制正确的合约地址,重新添加。
做到什么程度算完成:重新添加后,钱包显示的余额与区块浏览器一致。
风险提醒:恶意代币的
decimals()返回的值可以和实际转账逻辑不一致,或者根据调用地址返回不同数值。如果你在 DeFi 协议里依赖某个代币的decimals()做金额计算,而该代币未经验证,存在被操控的风险。审计机构 Zokyo 强调,永远不要假设所有代币都是 18 位,这已经是被记录在案的漏洞类型。
常见失败原因
"钱包显示的余额和浏览器对不上,但 decimals 设置没错"
这种情况可能不止是精度问题。MetaMask 官方列举了其他可能性:
RPC 节点延迟或缓存:切换到网络设置里的另一个 RPC URL 再试。
代币本身有内置机制:rebase 代币(供应量自动调整)或"转账付费"代币(每笔交易扣税)的余额会动态变化,不一定是钱包读错了。去查项目的白皮书,确认有没有这种机制。
做完以上修正,怎么确认改对了?
在你自己的脚本或钱包里,对同一个钱包地址,对比修正前和修正后显示的余额——如果现在显示的数字除以之前显示的数字,刚好等于 10 ** (18 - 实际decimals)(比如 USDC 就是 10^12 倍),说明你之前就是读错了小数位数。下一步,如果要在合约里做金额计算,务必用 parseUnits 把用户输入的"显示值"转成"链上原始值"再传参,不要直接传带小数的数字。
