UNPKG

four-flap-meme-sdk

Version:

SDK for Flap bonding curve and four.meme TokenManager

204 lines (203 loc) 8.65 kB
import { ethers, JsonRpcProvider } from 'ethers'; async function getErc20Decimals(provider, token) { try { const erc20 = new ethers.Contract(token, ['function decimals() view returns (uint8)'], provider); const d = await erc20.decimals(); if (!Number.isFinite(d) || d < 0 || d > 36) return 18; return d; } catch { return 18; } } /** * 分散(仅签名版本 - 不提交 Bundle) * ✅ 精简版:只负责签名交易,不提交 */ export async function disperseWithBundle(params) { const { rpcUrl, chainId, fromPrivateKey, recipients, amount, amounts, tokenAddress, gasPriceGwei, transferGasLimit = 65000n, nativeGasLimit = 21000n } = params; if (!recipients || recipients.length === 0) { return { signedTransactions: [] }; } const provider = new JsonRpcProvider(rpcUrl, chainId); const wallet = new ethers.Wallet(fromPrivateKey, provider); const gasPrice = gasPriceGwei && gasPriceGwei.trim().length > 0 ? ethers.parseUnits(gasPriceGwei, 'gwei') : (await provider.getFeeData()).gasPrice ?? ethers.parseUnits('1', 'gwei'); // 规范化 amounts:优先使用数组;否则将单值扩展为数组 const useAmountsArray = Array.isArray(amounts) && amounts.length > 0; const effAmounts = useAmountsArray ? amounts : (typeof amount === 'string' && amount.trim().length > 0 ? new Array(recipients.length).fill(amount) : []); if (effAmounts.length !== recipients.length) { throw new Error('recipients and amounts length mismatch'); } let isNative = !tokenAddress || tokenAddress.trim().length === 0; const signedTxs = []; if (isNative) { const baseNonce = await provider.getTransactionCount(wallet.address, 'pending'); // ✅ 并行签名所有交易 const signedTxList = await Promise.all(recipients.map(async (to, i) => { const amountWei = ethers.parseEther(effAmounts[i]); return wallet.signTransaction({ to, value: amountWei, nonce: baseNonce + i, gasPrice, gasLimit: nativeGasLimit, chainId, type: 0 }); })); signedTxs.push(...signedTxList); } else { // ✅ 并行获取 decimals 和 nonce const [decimals, baseNonce] = await Promise.all([ getErc20Decimals(provider, tokenAddress), provider.getTransactionCount(wallet.address, 'pending') ]); const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']); // ✅ 并行签名所有交易 const signedTxList = await Promise.all(recipients.map(async (to, i) => { const amountWei = ethers.parseUnits(effAmounts[i], decimals); const data = iface.encodeFunctionData('transfer', [to, amountWei]); return wallet.signTransaction({ to: tokenAddress, data, value: 0n, nonce: baseNonce + i, gasPrice, gasLimit: transferGasLimit, chainId, type: 0 }); })); signedTxs.push(...signedTxList); } // ✅ 只返回签名交易,不提交到 Bundle return { signedTransactions: signedTxs }; } /** * 归集(仅签名版本 - 不提交 Bundle) * ✅ 精简版:只负责签名交易,不提交 */ export async function sweepWithBundle(params) { const { rpcUrl, chainId, sourcePrivateKeys, target, ratioPct, amount, tokenAddress, gasPriceGwei, transferGasLimit = 65000n, nativeGasLimit = 21000n, skipIfInsufficient = true } = params; if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) { return { signedTransactions: [] }; } const provider = new JsonRpcProvider(rpcUrl, chainId); const gasPrice = gasPriceGwei && gasPriceGwei.trim().length > 0 ? ethers.parseUnits(gasPriceGwei, 'gwei') : (await provider.getFeeData()).gasPrice ?? ethers.parseUnits('1', 'gwei'); let isNative = !tokenAddress || tokenAddress.trim().length === 0; const signedTxs = []; const clampRatio = (n) => { if (typeof n !== 'number' || !Number.isFinite(n)) return undefined; if (n < 0) return 0; if (n > 100) return 100; return Math.floor(n); }; const ratio = clampRatio(ratioPct); if (isNative) { // ✅ 并行处理所有源钱包 const wallets = sourcePrivateKeys.map(pk => new ethers.Wallet(pk, provider)); const gasCost = nativeGasLimit * gasPrice; // 并行获取所有余额和 nonce const [balances, nonces] = await Promise.all([ Promise.all(wallets.map(w => provider.getBalance(w.address).catch(() => 0n))), Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'pending'))) ]); // 计算每个钱包的发送金额 const sendAmounts = balances.map(bal => { if (ratio !== undefined) { const want = (bal * BigInt(ratio)) / 100n; const maxSendable = bal > gasCost ? (bal - gasCost) : 0n; return want > maxSendable ? maxSendable : want; } else if (amount && amount.trim().length > 0) { const amountWei = ethers.parseEther(amount); const needed = amountWei + gasCost; if (!skipIfInsufficient || bal >= needed) return amountWei; } return 0n; }); // 并行签名所有有效交易 const validIndices = sendAmounts.map((amt, i) => amt > 0n ? i : -1).filter(i => i >= 0); const signedTxList = await Promise.all(validIndices.map(i => wallets[i].signTransaction({ to: target, value: sendAmounts[i], nonce: nonces[i], gasPrice, gasLimit: nativeGasLimit, chainId, type: 0 }))); signedTxs.push(...signedTxList); } else { const wallets = sourcePrivateKeys.map(pk => new ethers.Wallet(pk, provider)); const iface = new ethers.Interface(['function balanceOf(address) view returns (uint256)', 'function transfer(address,uint256) returns (bool)']); // ✅ 并行获取 decimals、所有代币余额和 nonce const [decimals, balanceResults, nonces] = await Promise.all([ getErc20Decimals(provider, tokenAddress), Promise.all(wallets.map(async (w) => { try { const balData = iface.encodeFunctionData('balanceOf', [w.address]); const balRaw = await provider.call({ to: tokenAddress, data: balData }); const [bal] = iface.decodeFunctionResult('balanceOf', balRaw); return bal; } catch { return 0n; } })), Promise.all(wallets.map(w => provider.getTransactionCount(w.address, 'pending'))) ]); // 计算每个钱包的发送金额 const sendAmounts = balanceResults.map(bal => { if (ratio !== undefined) { return (bal * BigInt(ratio)) / 100n; } else if (amount && amount.trim().length > 0) { const amountWei = ethers.parseUnits(amount, decimals); if (!skipIfInsufficient || bal >= amountWei) return amountWei; } return 0n; }); // 过滤余额不足的钱包 const validIndices = sendAmounts.map((amt, i) => { if (amt <= 0n) return -1; if (skipIfInsufficient && balanceResults[i] < amt) return -1; return i; }).filter(i => i >= 0); // 并行签名所有有效交易 const signedTxList = await Promise.all(validIndices.map(i => { const data = iface.encodeFunctionData('transfer', [target, sendAmounts[i]]); return wallets[i].signTransaction({ to: tokenAddress, data, value: 0n, nonce: nonces[i], gasPrice, gasLimit: transferGasLimit, chainId, type: 0 }); })); signedTxs.push(...signedTxList); } // ✅ 只返回签名交易,不提交到 Bundle return { signedTransactions: signedTxs }; }