// 跨链USDT授权核心方法 - 修复版(无需外部ethers库) // 适用于支持EIP-1193标准的钱包(如MetaMask) interface ChainConfig { chainId: string; name: string; usdtAddress: string; rpcUrl: string; explorer: string; } // 链配置 const chains: Record = { eth: { chainId: '0x1', // Ethereum Mainnet name: 'Ethereum', usdtAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', rpcUrl: 'https://mainnet.infura.io/v3/', explorer: 'https://etherscan.io' }, bnb: { chainId: '0x38', // Binance Smart Chain Mainnet name: 'Binance Smart Chain', usdtAddress: '0x55d398326f99059fF775485246999027B3197955', rpcUrl: 'https://bsc-dataseed.binance.org/', explorer: 'https://bscscan.com' }, polygon: { chainId: '0x89', // Polygon Mainnet name: 'Polygon', usdtAddress: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', rpcUrl: 'https://polygon-rpc.com/', explorer: 'https://polygonscan.com' } }; // USDT ABI const USDT_ABI = [ "function approve(address spender, uint256 value) public returns (bool)", "function allowance(address owner, address spender) view returns (uint256)", "function balanceOf(address owner) view returns (uint256)" ]; // 将数字转换为十六进制字符串 function numberToHex(num: number | string): string { return '0x' + BigInt(num).toString(16); } // 将十进制数字转换为带指定小数位的整数形式 function parseUnits(value: string, decimals: number): string { const [integerPart = '0', decimalPart = ''] = value.split('.'); let result = integerPart.replace(/^0+/, '') || '0'; // 移除前导零 if (decimalPart.length > decimals) { throw new Error(`小数位数超过限制: ${decimals}`); } const paddedDecimal = decimalPart.padEnd(decimals, '0'); result += paddedDecimal; return result; } // 验证钱包连接 function validateWallet(): boolean { if (typeof window === 'undefined' || typeof (window as any).ethereum === 'undefined') { throw new Error('请在支持以太坊的钱包浏览器中运行此代码'); } return true; } // 获取当前钱包账户 async function getAccount(): Promise { try { const accounts = await (window as any).ethereum.request({ method: 'eth_requestAccounts' }); return accounts[0]; } catch (error) { console.error('用户拒绝授权:', error); throw error; } } // 获取当前网络ID async function getCurrentNetworkId(): Promise { try { const chainId = await (window as any).ethereum.request({ method: 'eth_chainId' }); return chainId; } catch (error) { console.error('获取网络ID失败:', error); throw error; } } // 切换网络 async function switchNetwork(chainId: string): Promise { const chainName = Object.values(chains).find(c => c.chainId === chainId)?.name || 'Unknown Network'; try { await (window as any).ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: chainId }], }); console.log(`已切换到${chainName}`); } catch (switchError: any) { if (switchError.code === 4902) { // 网络不存在,尝试添加网络 const config = Object.values(chains).find(c => c.chainId === chainId); if (!config) { throw new Error(`不支持的网络: ${chainId}`); } try { await (window as any).ethereum.request({ method: 'wallet_addEthereumChain', params: [{ chainId: chainId, chainName: config.name, nativeCurrency: { name: chainId === '0x38' ? 'BNB' : chainId === '0x89' ? 'MATIC' : 'ETH', symbol: chainId === '0x38' ? 'BNB' : chainId === '0x89' ? 'MATIC' : 'ETH', decimals: 18, }, rpcUrls: [config.rpcUrl], blockExplorerUrls: [config.explorer] }], }); console.log(`已添加${chainName}`); } catch (addError) { console.error('添加网络失败:', addError); throw addError; } } else { console.error('切换网络失败:', switchError); throw switchError; } } } // 编码函数调用数据 function encodeFunctionCall(abiFragment: string, args: any[]): string { // 简化的函数签名计算 const funcSignature = abiFragment.substring(0, abiFragment.indexOf('(')); const params = abiFragment.substring(abiFragment.indexOf('(') + 1, abiFragment.lastIndexOf(')')); // 为 approve(address,uint256) 函数构建调用数据 if (funcSignature === 'approve' && params === 'address,uint256') { // 函数选择器: keccak256("approve(address,uint256)") 的前4字节 // 已知为 0x095ea7b3 let data = '0x095ea7b3'; // 地址参数(补零到32字节) let addr = args[0].toLowerCase(); if (addr.startsWith('0x')) addr = addr.substring(2); data += addr.padStart(64, '0'); // 数量参数(补零到32字节) const amount = BigInt(args[1]).toString(16); data += amount.padStart(64, '0'); return data; } // 为 allowance(address,address) 函数构建调用数据 if (funcSignature === 'allowance' && params === 'address,address') { // 函数选择器: keccak256("allowance(address,address)") 的前4字节 // 已知为 0xdd62ed3e let data = '0xdd62ed3e'; // 第一个地址参数 let owner = args[0].toLowerCase(); if (owner.startsWith('0x')) owner = owner.substring(2); data += owner.padStart(64, '0'); // 第二个地址参数 let spender = args[1].toLowerCase(); if (spender.startsWith('0x')) spender = spender.substring(2); data += spender.padStart(64, '0'); return data; } // 为 balanceOf(address) 函数构建调用数据 if (funcSignature === 'balanceOf' && params === 'address') { // 函数选择器: keccak256("balanceOf(address)") 的前4字节 // 已知为 0x70a08231 let data = '0x70a08231'; // 地址参数(补零到32字节) let addr = args[0].toLowerCase(); if (addr.startsWith('0x')) addr = addr.substring(2); data += addr.padStart(64, '0'); return data; } throw new Error('不支持的ABI片段: ' + abiFragment); } // 授权USDT async function authorizeUSDT( chain: 'eth' | 'bnb' | 'polygon', spenderAddress: string, amount: string ): Promise<{ success: boolean; transactionHash?: string; error?: any }> { try { // 验证参数 if (!isValidAddress(spenderAddress)) { throw new Error('无效的被授权地址'); } if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) { throw new Error('授权金额必须大于0'); } // 获取钱包账户 const account = await getAccount(); // 获取当前网络 const currentNetworkId = await getCurrentNetworkId(); const targetChainConfig = chains[chain]; if (!targetChainConfig) throw new Error(`不支持的链: ${chain}`); // 检查是否需要切换网络 if (currentNetworkId !== targetChainConfig.chainId) { await switchNetwork(targetChainConfig.chainId); } // 将金额转换为带6位小数的单位(USDT有6位小数) const parsedAmount = parseUnits(amount, 6); // 构建交易数据 const data = encodeFunctionCall('approve(address,uint256)', [spenderAddress, parsedAmount]); // 构建交易对象 const tx = { from: account, to: targetChainConfig.usdtAddress, data: data }; // 发送交易 const transactionHash = await (window as any).ethereum.request({ method: 'eth_sendTransaction', params: [tx] }); console.log(`正在${targetChainConfig.name}上发送授权交易...`); console.log('交易哈希:', transactionHash); // 等待交易确认 // 注意:这里我们只返回交易哈希,实际等待确认需要轮询或监听 return { success: true, transactionHash: transactionHash }; } catch (error) { console.error('授权失败:', error); return { success: false, error: error }; } } // 查询USDT余额 async function getUSDTBalance(chain: 'eth' | 'bnb' | 'polygon', account: string): Promise { try { const targetChainConfig = chains[chain]; if (!targetChainConfig) throw new Error(`不支持的链: ${chain}`); // 构建调用数据 const data = encodeFunctionCall('balanceOf(address)', [account]); // 执行调用 const result = await (window as any).ethereum.request({ method: 'eth_call', params: [{ to: targetChainConfig.usdtAddress, data: data }, 'latest'] }); // 解析结果(十六进制转十进制) const balance = BigInt(result).toString(); // 转换为带6位小数的格式 if (balance.length <= 6) { return '0.' + balance.padStart(6, '0'); } else { const integerPart = balance.slice(0, -6); const decimalPart = balance.slice(-6).replace(/0+$/, ''); // 移除末尾零 return decimalPart ? `${integerPart}.${decimalPart}` : integerPart; } } catch (error) { console.error('查询余额失败:', error); throw error; } } // 查询当前授权额度 async function getAllowance( chain: 'eth' | 'bnb' | 'polygon', owner: string, spender: string ): Promise { try { const targetChainConfig = chains[chain]; if (!targetChainConfig) throw new Error(`不支持的链: ${chain}`); // 构建调用数据 const data = encodeFunctionCall('allowance(address,address)', [owner, spender]); // 执行调用 const result = await (window as any).ethereum.request({ method: 'eth_call', params: [{ to: targetChainConfig.usdtAddress, data: data }, 'latest'] }); // 解析结果(十六进制转十进制) const allowance = BigInt(result).toString(); // 转换为带6位小数的格式 if (allowance.length <= 6) { return '0.' + allowance.padStart(6, '0'); } else { const integerPart = allowance.slice(0, -6); const decimalPart = allowance.slice(-6).replace(/0+$/, ''); // 移除末尾零 return decimalPart ? `${integerPart}.${decimalPart}` : integerPart; } } catch (error) { console.error('查询授权额度失败:', error); throw error; } } // 验证地址格式 function isValidAddress(address: string): boolean { return /^0x[a-fA-F0-9]{40}$/.test(address); } // 导出方法供UI调用 export const CrossChainUSDTAuth = { authorizeUSDT, getUSDTBalance, getAllowance }; // 使用示例: // await CrossChainUSDTAuth.authorizeUSDT('eth', '0x1234...', '100') // await CrossChainUSDTAuth.getUSDTBalance('eth', '0x1234...') // await CrossChainUSDTAuth.getAllowance('eth', '0x1234...', '0x5678...')