four-flap-meme-sdk
Version:
SDK for Flap bonding curve and four.meme TokenManager
204 lines (203 loc) • 8.65 kB
JavaScript
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 };
}