使用Ethers、MetaMask在BSC转账USDT时随机出现Internal JSON RPC错误
Dapp USDT转账异常:MetaMask显示失败但链上实际成功
问题详情
开发的Dapp实现钱包A向钱包B转账USDT功能,流程为唤起MetaMask授权,用户确认后调用sendTransaction方法。但偶尔出现Internal JSON RPC错误:MetaMask提示交易失败,然而链上(BSC)交易已成功,USDT完成转账。
- 异常随机出现,约每10-20次交易触发1次
- 本次异常交易nonce为349,使用Ankr付费RPC节点
相关代码
let nonce = 0; try { nonce = await tmpProvider.getTransactionCount(await signer.getAddress()); console.log('Nonce:', nonce); } catch (error) { console.error('Error fetching nonce:', error); } const burnTx = await signer.populateTransaction({ to: usdtContractAddress, value: 0, data: usdtContract.interface.encodeFunctionData("transfer", [recipientAddress, amount]), nonce: nonce }) const estimatedGas = await signer.estimateGas(burnTx); const gasLimit = estimatedGas + 1000000n; burnTx.gasLimit = gasLimit; const tx = await signer.sendTransaction(burnTx);
异常表现
- MetaMask界面显示交易失败
- 链上浏览器显示交易成功
- 报错信息为「Internal JSON RPC错误」
可能原因
- RPC节点同步延迟:Ankr节点在交易上链后未及时同步状态,MetaMask查询交易状态时返回错误,但实际链上已确认。
- MetaMask状态不同步:MetaMask本地缓存的交易状态与链上数据不一致,导致误判交易失败。
- Nonce获取逻辑漏洞:仅获取已确认交易的nonce,理论上可能因未确认交易导致冲突,但本次异常交易已成功,此可能性较低。
解决方案
1. 优化Nonce获取
获取包含未确认交易的nonce,避免潜在冲突:
// 增加'pending'参数,获取包含未确认交易的nonce nonce = await tmpProvider.getTransactionCount(await signer.getAddress(), 'pending');
2. 主动轮询交易确认
不依赖MetaMask返回,主动等待交易上链确认:
const tx = await signer.sendTransaction(burnTx); // 等待1个区块确认,确保交易已上链 const confirmedTx = await tx.wait(1); console.log('交易已确认,区块高度:', confirmedTx.blockNumber);
3. 多RPC节点 fallback
配置多个可靠RPC节点(如BSC官方节点+Ankr),单个节点异常时自动切换:
// 示例:简单多节点切换逻辑 const providers = [ new ethers.providers.JsonRpcProvider('BSC官方节点URL'), new ethers.providers.JsonRpcProvider('Ankr节点URL') ]; async function getActiveProvider() { for (const provider of providers) { try { await provider.getBlockNumber(); return provider; } catch (e) { continue; } } throw new Error('所有RPC节点不可用'); } // 使用时替换tmpProvider const tmpProvider = await getActiveProvider();
4. 错误后校验链上状态
捕获错误后,主动查询链上对应nonce的交易状态:
try { const tx = await signer.sendTransaction(burnTx); await tx.wait(1); } catch (error) { const userAddress = await signer.getAddress(); // 查询该地址最近10笔交易,检查对应nonce的交易状态 const transactions = await tmpProvider.getHistory(userAddress, 10); const targetTx = transactions.find(tx => tx.nonce === nonce); if (targetTx && targetTx.status === 1) { // 交易实际已成功,执行成功逻辑 console.log('交易已成功,TxHash:', targetTx.hash); } else { // 交易确实失败,处理错误 console.error('交易失败:', error); } }
内容的提问来源于stack exchange,提问作者Alvin Low
相关产品推荐
相关产品推荐

