UNPKG

four-flap-meme-sdk

Version:

SDK for Flap bonding curve and four.meme TokenManager

615 lines (614 loc) 28 kB
import { Contract, Wallet, JsonRpcProvider, Interface, parseUnits } from 'ethers'; import { ADDRESSES, ZERO_ADDRESS } from './constants.js'; import { NonceManager } from './bundle-helpers.js'; import { ERC20_ABI, MULTICALL3_ABI } from '../abis/common.js'; /** * 验证合约地址是否有效(是否部署了代码) */ async function validateContractAddress(provider, address, label) { try { // ✅ 先规范化地址(转为 checksum 格式),避免 ethers v6 的严格校验报错 const normalizedAddress = address.toLowerCase().startsWith('0x') ? address.toLowerCase() : `0x${address.toLowerCase()}`; const code = await provider.getCode(normalizedAddress); if (code === '0x' || code.length <= 2) { throw new Error(`❌ ${label} 地址无效或未部署合约: ${address}`); } } catch (error) { if (error.message.includes('无效或未部署')) { throw error; } // ✅ 如果是 checksum 错误,提供更友好的提示 if (error.message.includes('bad address checksum') || error.code === 'INVALID_ARGUMENT') { throw new Error(`❌ ${label} 地址格式错误,请使用正确的 checksum 格式: ${address}`); } throw new Error(`❌ 无法验证 ${label} 地址 ${address}: ${error.message}`); } } /** * 通用 ERC20 授权方法(内部辅助函数) * 自动检查当前授权额度,只在不足时才发送交易 */ async function ensureAllowance(rpcUrl, privateKey, token, owner, spender, required) { const provider = new JsonRpcProvider(rpcUrl); const signer = new Wallet(privateKey, provider); // ✅ 验证 token 和 spender 地址 await validateContractAddress(provider, token, 'Token'); await validateContractAddress(provider, spender, 'Spender'); const erc20 = new Contract(token, ERC20_ABI, signer); // ✅ 自动检查当前授权额度,只在不足时才发送交易 let current; try { current = await erc20.allowance(owner, spender); } catch (error) { throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`); } if (current >= required) { return { alreadyApproved: true, currentAllowance: current, requiredAllowance: required, }; } const tx = await erc20.approve(spender, required); const receipt = await tx.wait(); return { alreadyApproved: false, currentAllowance: current, requiredAllowance: required, txReceipt: receipt, }; } /** * 检查代币授权状态 - Four.meme V1(只读,不发送交易) * @returns 是否已授权足够的额度 */ export async function checkSellApprovalV1(chain, rpcUrl, token, owner, amount) { const proxyAddresses = { BSC: ADDRESSES.BSC.TokenManagerV1, // FourMeme 代理合约 V1 (BSC) BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE) ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM) }; const provider = new JsonRpcProvider(rpcUrl); // ✅ 验证 token 和代理合约地址 await validateContractAddress(provider, token, 'Token'); await validateContractAddress(provider, proxyAddresses[chain], `Four.meme V1 Proxy (${chain})`); const erc20 = new Contract(token, ERC20_ABI, provider); let current; try { current = await erc20.allowance(owner, proxyAddresses[chain]); } catch (error) { throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`); } return { isApproved: current >= amount, currentAllowance: current, requiredAllowance: amount, }; } /** * 确保代币已授权给 Four.meme V1 代理合约 * 用于 Four.meme V1 代币的卖出操作 * @returns 授权结果,包含是否已授权、当前额度、交易回执等信息 */ export async function ensureSellApprovalV1(chain, rpcUrl, privateKey, token, owner, amount) { // ✅ 授权给 FourMeme 代理合约 V1(收费版) const proxyAddresses = { BSC: ADDRESSES.BSC.TokenManagerV1, // FourMeme 代理合约 V1 (BSC) BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE) ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM) }; return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount); } /** * 检查代币授权状态 - Four.meme V2(只读,不发送交易) * @returns 是否已授权足够的额度 */ export async function checkSellApprovalV2(chain, rpcUrl, token, owner, amount) { const proxyAddresses = { BSC: ADDRESSES.BSC.TokenManagerV2, // FourMeme 代理合约 V2 (BSC) BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE) ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM) }; const provider = new JsonRpcProvider(rpcUrl); // ✅ 验证 token 和代理合约地址 await validateContractAddress(provider, token, 'Token'); await validateContractAddress(provider, proxyAddresses[chain], `Four.meme V2 Proxy (${chain})`); const erc20 = new Contract(token, ERC20_ABI, provider); let current; try { current = await erc20.allowance(owner, proxyAddresses[chain]); } catch (error) { throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`); } return { isApproved: current >= amount, currentAllowance: current, requiredAllowance: amount, }; } /** * 确保代币已授权给 Four.meme V2 代理合约 * 用于 Four.meme V2 代币的卖出操作 * @returns 授权结果,包含是否已授权、当前额度、交易回执等信息 */ export async function ensureSellApprovalV2(chain, rpcUrl, privateKey, token, owner, amount) { // ✅ 授权给 FourMeme 代理合约 V2(收费版) const proxyAddresses = { BSC: ADDRESSES.BSC.TokenManagerV2, // FourMeme 代理合约 V2 (BSC) BASE: ADDRESSES.BASE.TokenManagerHelper3, // FourMeme 代理合约 (BASE) ARBITRUM_ONE: ADDRESSES.ARBITRUM_ONE.TokenManagerHelper3, // FourMeme 代理合约 (ARBITRUM) }; return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount); } /** * 检查代币授权状态 - Four.meme(通用,默认使用 V2) * @deprecated 建议使用明确版本的方法:checkSellApprovalV1 或 checkSellApprovalV2 */ export async function checkSellApproval(chain, rpcUrl, token, owner, amount) { return checkSellApprovalV2(chain, rpcUrl, token, owner, amount); } /** * 确保代币已授权给 Four.meme 代理合约(通用,默认使用 V2) * @deprecated 建议使用明确版本的方法:ensureSellApprovalV1 或 ensureSellApprovalV2 */ export async function ensureSellApproval(chain, rpcUrl, privateKey, token, owner, amount) { return ensureSellApprovalV2(chain, rpcUrl, privateKey, token, owner, amount); } /** * 检查代币授权状态 - Flap Protocol(只读,不发送交易) * @returns 是否已授权足够的额度 */ export async function checkFlapSellApproval(chain, rpcUrl, token, owner, amount) { const proxyAddresses = { BSC: ADDRESSES.BSC.FlapPortal, // Flap Portal 代理合约 (BSC) BASE: ADDRESSES.BASE.FlapPortal, // Flap Portal 代理合约 (BASE) XLAYER: ADDRESSES.XLAYER.FlapPortal, // Flap Portal 代理合约 (XLAYER) MORPH: ADDRESSES.MORPH.FlapPortal, // Flap Portal 代理合约 (MORPH) MONAD: ADDRESSES.MONAD.FlapPortal, // Flap Portal 代理合约 (MONAD) }; const provider = new JsonRpcProvider(rpcUrl); // ✅ 验证 token 和代理合约地址 await validateContractAddress(provider, token, 'Token'); await validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`); const erc20 = new Contract(token, ERC20_ABI, provider); let current; try { current = await erc20.allowance(owner, proxyAddresses[chain]); } catch (error) { throw new Error(`❌ 调用 allowance 失败(Token 可能不是 ERC20): ${error.message}`); } return { isApproved: current >= amount, currentAllowance: current, requiredAllowance: amount, }; } /** * 确保代币已授权给 Flap Protocol 代理合约 * 用于 Flap Protocol 代币的卖出操作 * @returns 授权结果,包含是否已授权、当前额度、交易回执等信息 */ export async function ensureFlapSellApproval(chain, rpcUrl, privateKey, token, owner, amount) { // ✅ 授权给 Flap Portal 代理合约(收费版) const proxyAddresses = { BSC: ADDRESSES.BSC.FlapPortal, // Flap Portal 代理合约 (BSC) BASE: ADDRESSES.BASE.FlapPortal, // Flap Portal 代理合约 (BASE) XLAYER: ADDRESSES.XLAYER.FlapPortal, // Flap Portal 代理合约 (XLAYER) MORPH: ADDRESSES.MORPH.FlapPortal, // Flap Portal 代理合约 (MORPH) MONAD: ADDRESSES.MONAD.FlapPortal, // Flap Portal 代理合约 (MONAD) }; return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount); } /** * 批量确保代币已授权给 Flap Protocol 代理合约(默认授权上限 2^256-1) * @param privateKeys 拥有者私钥数组(每个地址需自行签名) * @returns 每个地址的授权结果(包含 owner 与交易回执等信息) */ export async function ensureFlapSellApprovalBatch(chain, rpcUrl, privateKeys, token, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) { if (!privateKeys || privateKeys.length === 0) return []; // ✅ 并行处理所有授权 const results = await Promise.all(privateKeys.map(async (pk) => { const owner = new Wallet(pk).address; const r = await ensureFlapSellApproval(chain, rpcUrl, pk, token, owner, required); return { owner, ...r }; })); return results; } /** * 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断) * ✅ 使用 Multicall3 批量查询,减少 RPC 调用次数 */ export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, required = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')) { if (!owners || owners.length === 0) return []; const proxyAddresses = { BSC: ADDRESSES.BSC.FlapPortal, BASE: ADDRESSES.BASE.FlapPortal, XLAYER: ADDRESSES.XLAYER.FlapPortal, MORPH: ADDRESSES.MORPH.FlapPortal, MONAD: ADDRESSES.MONAD.FlapPortal, }; const provider = new JsonRpcProvider(rpcUrl); // ✅ 并行验证 token 和代理合约地址 await Promise.all([ validateContractAddress(provider, token, 'Token'), validateContractAddress(provider, proxyAddresses[chain], `Flap Portal (${chain})`) ]); // ✅ 使用 batchCheckAllowances 批量查询(Multicall3) const allowances = await batchCheckAllowances(provider, token, owners, proxyAddresses[chain]); return owners.map((owner, i) => ({ owner, isApproved: allowances[i] >= required, currentAllowance: allowances[i], requiredAllowance: required })); } /** * 使用 Multicall3 批量查询 ERC20 授权额度 * @param provider - Provider 实例 * @param tokenAddress - ERC20 代币地址 * @param owners - 所有者地址数组 * @param spender - 被授权的 spender 地址 * @returns 每个地址的授权额度数组 */ export async function batchCheckAllowances(provider, tokenAddress, owners, spender) { // ✅ 使用公共常量和 ABI const multicall3Address = ADDRESSES.BSC.Multicall3; const multicall3 = new Contract(multicall3Address, MULTICALL3_ABI, provider); // 编码 allowance(owner, spender) 调用数据 const erc20Interface = new Contract(tokenAddress, ERC20_ABI, provider).interface; const calls = owners.map(owner => ({ target: tokenAddress, allowFailure: true, callData: erc20Interface.encodeFunctionData('allowance', [owner, spender]) })); // 批量调用 const results = await multicall3.aggregate3(calls); // 解析结果 return results.map((result, index) => { if (!result.success) { return 0n; } try { const decoded = erc20Interface.decodeFunctionResult('allowance', result.returnData); return decoded[0]; } catch (error) { return 0n; } }); } /** * 🔧 内部辅助函数:根据链和平台自动解析 spender 地址 */ function resolveSpenderAddress(chain, platform) { const spenderMap = { flap: { BSC: ADDRESSES.BSC.FlapPortal, BASE: ADDRESSES.BASE.FlapPortal, XLAYER: ADDRESSES.XLAYER.FlapPortal, MORPH: ADDRESSES.MORPH.FlapPortal, MONAD: ADDRESSES.MONAD.FlapPortal, // ✅ Monad Flap Portal }, four: { // Four.meme 使用 TokenManagerV2Proxy(默认) BSC: ADDRESSES.BSC.TokenManagerV2Proxy, BASE: ADDRESSES.BASE.TokenManagerHelper3, XLAYER: ZERO_ADDRESS, // XLAYER 暂不支持 Four.meme MORPH: ZERO_ADDRESS, // MORPH 暂不支持 Four.meme MONAD: ZERO_ADDRESS, // MONAD 不支持 Four.meme }, 'pancake-v2': { // PancakeSwap V2 Router 地址 BSC: ADDRESSES.BSC.PancakeV2Router, BASE: '0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb', // BASE PancakeSwap V2 Router XLAYER: ZERO_ADDRESS, // XLAYER 暂不支持 MORPH: ZERO_ADDRESS, // MORPH 暂不支持 MONAD: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // Monad PancakeSwap V2 Router }, 'pancake-v3': { // PancakeSwap V3 SmartRouter 地址 BSC: ADDRESSES.BSC.PancakeV3Router, BASE: '0x678Aa4bF4E210cf2166753e054d5b7c31cc7fa86', // BASE PancakeSwap V3 SmartRouter XLAYER: ZERO_ADDRESS, // XLAYER 暂不支持 MORPH: ZERO_ADDRESS, // MORPH 暂不支持 MONAD: '0x1b81d678ffb9c0263b24a97847620c99d213eb14', // Monad PancakeSwap V3 Router }, }; const spender = spenderMap[platform]?.[chain]; if (!spender || spender === ZERO_ADDRESS) { throw new Error(`❌ 不支持的链或平台: ${chain} / ${platform}`); } return spender; } /** * ✅ 智能路由:检查单个 ERC20 授权额度(自动选择 spender) * * @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD') * @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3') * @param rpcUrl - RPC 节点地址 * @param tokenAddress - 代币合约地址 * @param ownerAddress - 代币持有者地址 * @returns 当前授权额度(bigint) */ export async function checkAllowance(chain, platform, rpcUrl, tokenAddress, ownerAddress) { const spenderAddress = resolveSpenderAddress(chain, platform); return checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spenderAddress); } /** * ✅ 底层方法:检查 ERC20 代币授权额度(手动指定 spender) * 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等) * * @param rpcUrl - RPC 节点地址 * @param tokenAddress - 代币合约地址 * @param ownerAddress - 代币持有者地址 * @param spenderAddress - 被授权的合约地址(如 Router、Portal、TokenManager 等) * @returns 当前授权额度(bigint) */ export async function checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spenderAddress) { const provider = new JsonRpcProvider(rpcUrl); // ✅ 规范化地址(转为小写,避免 checksum 错误) const normalizedToken = tokenAddress.toLowerCase(); const normalizedOwner = ownerAddress.toLowerCase(); const normalizedSpender = spenderAddress.toLowerCase(); // 验证地址 await validateContractAddress(provider, normalizedToken, 'Token'); await validateContractAddress(provider, normalizedSpender, 'Spender'); const erc20 = new Contract(normalizedToken, ERC20_ABI, provider); try { const allowance = await erc20.allowance(normalizedOwner, normalizedSpender); return allowance; } catch (error) { throw new Error(`❌ 查询授权额度失败: ${error.message}`); } } /** * ✅ 智能路由:授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易) * * @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD') * @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3') * @param rpcUrl - RPC 节点地址 * @param privateKey - 私钥 * @param tokenAddress - 代币合约地址 * @param amount - 授权数量(bigint),传 'max' 表示最大授权 * @returns 授权结果 */ export async function approveToken(chain, platform, rpcUrl, privateKey, tokenAddress, amount) { const spenderAddress = resolveSpenderAddress(chain, platform); return approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderAddress, amount); } /** * ✅ 底层方法:授权 ERC20 代币给指定合约(手动指定 spender) * 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等) * * @param rpcUrl - RPC 节点地址 * @param privateKey - 私钥 * @param tokenAddress - 代币合约地址 * @param spenderAddress - 被授权的合约地址(如 Router、Portal、TokenManager 等) * @param amount - 授权数量(bigint),传 'max' 或 ethers.MaxUint256 表示最大授权 * @returns 授权结果 */ export async function approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderAddress, amount) { const provider = new JsonRpcProvider(rpcUrl); const signer = new Wallet(privateKey, provider); const ownerAddress = signer.address; // ✅ 规范化地址(转为小写,避免 checksum 错误) const normalizedToken = tokenAddress.toLowerCase(); const normalizedSpender = spenderAddress.toLowerCase(); // 验证地址 await validateContractAddress(provider, normalizedToken, 'Token'); await validateContractAddress(provider, normalizedSpender, 'Spender'); const erc20 = new Contract(normalizedToken, ERC20_ABI, signer); const requiredAmount = amount === 'max' ? BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') : amount; // 检查当前授权额度 let currentAllowance; try { currentAllowance = await erc20.allowance(ownerAddress, normalizedSpender); } catch (error) { throw new Error(`❌ 查询授权额度失败: ${error.message}`); } // 如果已经授权足够,直接返回 if (currentAllowance >= requiredAmount) { return { alreadyApproved: true, currentAllowance, requiredAllowance: requiredAmount }; } // 发送授权交易 try { const tx = await erc20.approve(normalizedSpender, requiredAmount); const receipt = await tx.wait(); return { alreadyApproved: false, currentAllowance, requiredAllowance: requiredAmount, txReceipt: receipt }; } catch (error) { throw new Error(`❌ 授权交易失败: ${error.message}`); } } /** * ✅ 智能路由:批量检查多个钱包的授权额度(自动选择 spender) * * @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD') * @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3') * @param rpcUrl - RPC 节点地址 * @param tokenAddress - 代币合约地址 * @param ownerAddresses - 代币持有者地址数组 * @returns 每个地址的授权额度数组 */ export async function checkAllowanceBatch(chain, platform, rpcUrl, tokenAddress, ownerAddresses) { const spenderAddress = resolveSpenderAddress(chain, platform); return checkAllowanceBatchRaw(rpcUrl, tokenAddress, ownerAddresses, spenderAddress); } /** * ✅ 底层方法:批量检查多个钱包的授权额度(手动指定 spender) * 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等) * * @param rpcUrl - RPC 节点地址 * @param tokenAddress - 代币合约地址 * @param ownerAddresses - 代币持有者地址数组 * @param spenderAddress - 被授权的合约地址 * @returns 每个地址的授权额度数组 */ export async function checkAllowanceBatchRaw(rpcUrl, tokenAddress, ownerAddresses, spenderAddress) { const provider = new JsonRpcProvider(rpcUrl); // ✅ 规范化地址(转为小写,避免 checksum 错误) const normalizedToken = tokenAddress.toLowerCase(); const normalizedOwners = ownerAddresses.map(addr => addr.toLowerCase()); const normalizedSpender = spenderAddress.toLowerCase(); // 验证地址 await validateContractAddress(provider, normalizedToken, 'Token'); await validateContractAddress(provider, normalizedSpender, 'Spender'); return batchCheckAllowances(provider, normalizedToken, normalizedOwners, normalizedSpender); } export async function approveTokenBatch(params) { const { chain, platform, rpcUrl, privateKeys, tokenAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId } = params; const spenderAddress = resolveSpenderAddress(chain, platform); return approveTokenBatchRaw({ rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId }); } export async function approveTokenBatchRaw(params) { const { rpcUrl, privateKeys, tokenAddress, spenderAddress, amounts, signOnly, gasPriceGwei, gasLimit, chainId = 56, nonceManager: externalNonceManager } = params; if (privateKeys.length === 0 || amounts.length !== privateKeys.length) { throw new Error('❌ 私钥数量和授权数量必须匹配'); } const provider = new JsonRpcProvider(rpcUrl); // ✅ 规范化地址(转为小写,避免 checksum 错误) const normalizedToken = tokenAddress.toLowerCase(); const normalizedSpender = spenderAddress.toLowerCase(); // 验证地址 await validateContractAddress(provider, normalizedToken, 'Token'); await validateContractAddress(provider, normalizedSpender, 'Spender'); // ✅ 优化:批量创建钱包和合约实例 const wallets = privateKeys.map(key => new Wallet(key, provider)); const ownerAddresses = wallets.map(w => w.address); const requiredAmounts = amounts.map(amount => amount === 'max' ? BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') : amount); // ==================== signOnly=true:只签名不提交 ==================== if (signOnly) { // ✅ 使用 NonceManager 管理 nonce(和买卖交易一样) const nonceManager = externalNonceManager || new NonceManager(provider); // ✅ 并行获取:当前授权额度 + nonces + gasPrice const [currentAllowances, nonces, fetchedGasPrice] = await Promise.all([ batchCheckAllowances(provider, normalizedToken, ownerAddresses, normalizedSpender), nonceManager.getNextNoncesForWallets(wallets), // ✅ 使用 NonceManager 批量获取 nonce gasPriceGwei ? Promise.resolve(parseUnits(gasPriceGwei.toString(), 'gwei')) : provider.getFeeData().then(fee => fee.gasPrice || parseUnits('3', 'gwei')) ]); const finalGasPrice = fetchedGasPrice; const finalGasLimit = BigInt(gasLimit || 100000); // ✅ ERC20 approve 函数的 ABI 编码 const erc20Interface = new Interface(ERC20_ABI); // ✅ 并行签名所有需要授权的交易 const signPromises = wallets.map(async (wallet, i) => { const ownerAddress = ownerAddresses[i]; const currentAllowance = currentAllowances[i]; const requiredAmount = requiredAmounts[i]; // 如果已经授权足够,跳过 if (currentAllowance >= requiredAmount) { return { owner: ownerAddress, alreadyApproved: true, currentAllowance, requiredAllowance: requiredAmount, signedTx: undefined }; } // 构建并签名交易 const txData = erc20Interface.encodeFunctionData('approve', [normalizedSpender, requiredAmount]); const signedTx = await wallet.signTransaction({ to: normalizedToken, data: txData, nonce: nonces[i], // ✅ NonceManager 已经处理好递增 gasLimit: finalGasLimit, gasPrice: finalGasPrice, chainId, type: 0 // Legacy 交易 }); return { owner: ownerAddress, alreadyApproved: false, currentAllowance, requiredAllowance: requiredAmount, signedTx }; }); const results = await Promise.all(signPromises); // ✅ 提取所有签名交易(过滤掉已授权的) const signedTransactions = results .filter(r => !r.alreadyApproved && r.signedTx) .map(r => r.signedTx); const alreadyApprovedCount = results.filter(r => r.alreadyApproved).length; const needApproveCount = results.filter(r => !r.alreadyApproved).length; return { signedTransactions, results, needApproveCount, alreadyApprovedCount }; } // ==================== signOnly=false(默认):直接发送交易 ==================== // ✅ 优化:并行检查所有钱包的授权额度 const currentAllowances = await batchCheckAllowances(provider, normalizedToken, ownerAddresses, normalizedSpender); // ✅ 优化:并行发送所有需要授权的交易 const approvalPromises = wallets.map(async (wallet, i) => { const ownerAddress = ownerAddresses[i]; const currentAllowance = currentAllowances[i]; const requiredAmount = requiredAmounts[i]; try { // 如果已经授权足够,跳过 if (currentAllowance >= requiredAmount) { return { owner: ownerAddress, alreadyApproved: true, currentAllowance, requiredAllowance: requiredAmount }; } // 发送授权交易 const erc20 = new Contract(normalizedToken, ERC20_ABI, wallet); const tx = await erc20.approve(normalizedSpender, requiredAmount); const receipt = await tx.wait(); return { owner: ownerAddress, alreadyApproved: false, currentAllowance, requiredAllowance: requiredAmount, txHash: receipt.hash }; } catch (error) { return { owner: ownerAddress, alreadyApproved: false, currentAllowance, requiredAllowance: requiredAmount, error: error.message }; } }); // ✅ 优化:并行等待所有授权交易完成 const results = await Promise.all(approvalPromises); // 统计结果 const approvedCount = results.filter(r => !r.alreadyApproved && !r.error).length; const errorCount = results.filter(r => r.error).length; // ✅ 只要没有错误,就算成功(包括所有钱包都已授权的情况) return { success: errorCount === 0, approvedCount, results }; }