UNPKG

four-flap-meme-sdk

Version:

SDK for Flap bonding curve and four.meme TokenManager

800 lines (799 loc) 32.8 kB
import { ethers, Wallet, JsonRpcProvider } from 'ethers'; import { Club48Client, sendBatchPrivateTransactions, BUILDER_CONTROL_EOA } from '../clients/club48.js'; import { ADDRESSES, PROFIT_CONFIG } from '../utils/constants.js'; import { buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../utils/bundle-helpers.js'; import TM2Abi from '../abis/TokenManager2.json' with { type: 'json' }; import { FourClient, buildLoginMessage } from '../clients/four.js'; import { trySell } from './tm.js'; /** * four.meme Bundle 交易方法 * 使用 48.club Bundle 服务实现原子化交易 */ // 错误信息国际化 const ERRORS = { NO_PRIVATE_KEY: { en: 'At least 1 private key (creator) is required', zh: '至少需要 1 个私钥(创建者)' }, AMOUNT_MISMATCH: { en: (buyCount, buyerCount) => `Buy amount count (${buyCount}) must equal buyer count (${buyerCount})`, zh: (buyCount, buyerCount) => `购买金额数量(${buyCount})必须等于买家数量(${buyerCount})` }, IMAGE_REQUIRED: { en: 'Either imageUrl or imageFile must be provided', zh: '必须提供 imageUrl 或 imageFile' }, KEY_AMOUNT_MISMATCH: { en: 'Private key count and amount count must match', zh: '私钥和购买金额数量必须一致' }, SELL_KEY_AMOUNT_MISMATCH: { en: 'Private key count and sell amount count must match', zh: '私钥和卖出数量必须一致' } }; // 获取错误信息(根据环境语言) function getErrorMessage(key, ...args) { const lang = (process.env.LANG || process.env.LANGUAGE || 'en').toLowerCase().includes('zh') ? 'zh' : 'en'; const message = ERRORS[key][lang]; return typeof message === 'function' ? message(...args) : message; } // ======================================== // 辅助函数 // ======================================== /** * 等待交易上链(不使用 club48 客户端) */ async function waitForBundleWithProvider(provider, firstTxSigned, bundleUuid) { const maxAttempts = 60; // 最多等待 60 秒 for (let i = 0; i < maxAttempts; i++) { await new Promise(resolve => setTimeout(resolve, 1000)); try { const txHash = ethers.keccak256(firstTxSigned); const receipt = await provider.getTransactionReceipt(txHash); if (receipt) { if (receipt.status === 1) { return { status: 'INCLUDED', includedBlock: receipt.blockNumber }; } else { return { status: 'FAILED' }; } } } catch (error) { // 继续等待 } } return { status: 'PENDING' }; } // ======================================== // 辅助函数:估算 gas 并应用配置 // ======================================== /** * 估算 gas 并应用用户配置的倍数 */ async function estimateGasWithMultiplier(provider, txRequest, multiplier) { const estimated = await provider.estimateGas(txRequest); const mult = multiplier ?? 1.2; // 默认 20% 安全余量 return BigInt(Math.ceil(Number(estimated) * mult)); } /** * 获取交易类型(用户可配置) */ function getTxType(config) { return config.txType ?? 0; // 默认 Legacy (type 0),适用于 48.club/BSC } /** * 构建完整的交易请求(包含 gas 估算和交易类型) */ async function buildTxRequest(provider, unsigned, from, nonce, gasPrice, config, value) { const gasLimit = await estimateGasWithMultiplier(provider, { ...unsigned, from, value }, config.gasLimitMultiplier); return { ...unsigned, from, nonce, gasLimit, gasPrice, chainId: 56, type: getTxType(config) }; } /** * four.meme: 创建代币 + 捆绑购买 * * 用户只需传入: * - dev 私钥 + N 个买家私钥 * - 对应的购买金额 * - 代币基本信息 */ export async function createTokenWithBundleBuy(params) { const { privateKeys, buyAmounts, tokenInfo, config } = params; if (privateKeys.length === 0) { throw new Error(getErrorMessage('NO_PRIVATE_KEY')); } if (buyAmounts.length !== privateKeys.length - 1) { throw new Error(getErrorMessage('AMOUNT_MISMATCH', buyAmounts.length, privateKeys.length - 1)); } const provider = new JsonRpcProvider(config.rpcUrl); const devWallet = new Wallet(privateKeys[0], provider); // ✅ 只在没有 customSubmitFn 时创建 club48 客户端 let club48 = null; if (!config.customSubmitFn) { club48 = new Club48Client({ endpoint: 'https://puissant-bsc.48.club', explorerEndpoint: config.club48ExplorerEndpoint }); } const fourClient = new FourClient({ baseUrl: config.fourApiUrl }); // 1. 登录 four.meme const nonce = await fourClient.generateNonce({ accountAddress: devWallet.address, verifyType: 'LOGIN', networkCode: 'BSC' }); const loginSignature = await devWallet.signMessage(buildLoginMessage(nonce)); const accessToken = await fourClient.loginDex({ region: 'WEB', langType: 'EN', verifyInfo: { address: devWallet.address, networkCode: 'BSC', signature: loginSignature, verifyType: 'LOGIN' }, walletName: 'MetaMask' }); // 2. 上传图片(如果需要) let imgUrl = tokenInfo.imageUrl || ''; if (!imgUrl && tokenInfo.imageFile) { imgUrl = await fourClient.uploadImage(accessToken, tokenInfo.imageFile); } if (!imgUrl) { throw new Error(getErrorMessage('IMAGE_REQUIRED')); } // 3. 获取创建参数 const createResp = await fourClient.createToken(accessToken, { // 可自定义参数 name: tokenInfo.name, shortName: tokenInfo.symbol, desc: tokenInfo.description, imgUrl, launchTime: Date.now(), label: tokenInfo.label || 'Meme', webUrl: tokenInfo.webUrl, twitterUrl: tokenInfo.twitterUrl, telegramUrl: tokenInfo.telegramUrl, preSale: tokenInfo.preSale || '0', onlyMPC: false, // 固定参数 lpTradingFee: 0.0025, symbol: 'BNB', totalSupply: 1000000000, raisedAmount: 24, saleRate: 0.8, reserveRate: 0, funGroup: false, clickFun: false, }); // 4. 构建交易 const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; // ✅ 获取 gas price let gasPrice; if (club48) { gasPrice = await club48.getMinGasPrice(); } else { // 使用 provider 获取 gas price 并增加 20% const currentGasPrice = (await provider.getFeeData()).gasPrice || 3000000000n; gasPrice = (currentGasPrice * 120n) / 100n; } const signedTxs = []; const nextNonceMap = new Map(); const getNextNonce = async (w) => { const key = w.address.toLowerCase(); const cached = nextNonceMap.get(key); if (cached !== undefined) { const n = cached; nextNonceMap.set(key, cached + 1); return n; } const onchain = await w.getNonce(); nextNonceMap.set(key, onchain + 1); return onchain; }; // four.meme 只支持 BNB,固定使用 0 地址 const tokenAddress = '0x0000000000000000000000000000000000000000'; // 4.1 创建交易 // 根据文档:b0Amount(初始金额,默认 8 BNB)+ preSale(预购金额) const b0AmountStr = params.b0Amount ?? '8'; const b0AmountWei = ethers.parseEther(b0AmountStr); const preSaleWei = tokenInfo.preSale ? ethers.parseEther(tokenInfo.preSale) : 0n; const valueWei = b0AmountWei + preSaleWei; const tm2 = new ethers.Contract(tmAddr, TM2Abi, devWallet); const createTxUnsigned = await tm2.createToken.populateTransaction(createResp.createArg, createResp.signature, { value: valueWei }); // 估算 gas 并添加 20% 安全余量 const estimatedCreateGas = await provider.estimateGas({ ...createTxUnsigned, from: devWallet.address, value: valueWei }); const createGasLimit = (estimatedCreateGas * 120n) / 100n; const createTxRequest = { ...createTxUnsigned, from: devWallet.address, nonce: await getNextNonce(devWallet), gasLimit: createGasLimit, gasPrice: gasPrice, chainId: 56, type: 0 // Legacy transaction (required for BSC on 48.club) }; const signedCreateTx = await devWallet.signTransaction(createTxRequest); signedTxs.push(signedCreateTx); // 4.2 购买交易 const buyTxs = []; const profitTxs = []; const buyAmountsWei = buyAmounts.map(a => ethers.parseEther(a)); // 🔍 找出买入金额最多的买家(由他支付所有利润) let maxBuyerIndex = 0; let maxAmount = buyAmountsWei[0]; for (let i = 1; i < buyAmountsWei.length; i++) { if (buyAmountsWei[i] > maxAmount) { maxAmount = buyAmountsWei[i]; maxBuyerIndex = i; } } // ✅ 并行构建所有购买交易 const buyerWallets = privateKeys.slice(1).map(pk => new Wallet(pk, provider)); // 并行获取所有买家的 nonce const buyerNonces = await Promise.all(buyerWallets.map(w => getNextNonce(w))); // 并行构建未签名交易和估算 gas const buyTxDataList = await Promise.all(buyAmountsWei.map(async (fundsWei, i) => { const buyerWallet = buyerWallets[i]; const buyTm2 = new ethers.Contract(tmAddr, TM2Abi, buyerWallet); const buyTxUnsigned = await buyTm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyerWallet.address, fundsWei, 0n, { value: fundsWei }); // 估算 gas 并添加 20% 安全余量 let buyGasLimit; try { const estimatedBuyGas = await provider.estimateGas({ ...buyTxUnsigned, from: buyerWallet.address, value: fundsWei }); buyGasLimit = (estimatedBuyGas * 120n) / 100n; } catch { buyGasLimit = 600000n; // 兜底值 } return { buyTxUnsigned, buyGasLimit, buyerWallet, fundsWei }; })); // 并行签名所有购买交易 const signedBuyTxList = await Promise.all(buyTxDataList.map(async (data, i) => { const buyTxRequest = { ...data.buyTxUnsigned, from: data.buyerWallet.address, nonce: buyerNonces[i], gasLimit: data.buyGasLimit, gasPrice: gasPrice, chainId: 56 }; return data.buyerWallet.signTransaction(buyTxRequest); })); signedTxs.push(...signedBuyTxList); buyTxs.push(...signedBuyTxList); // ✅ 聚合利润:由买入最多的人支付总利润(强制 2 跳中转) const totalBuyAmount = buyAmountsWei.reduce((sum, amount) => sum + amount, 0n); const totalProfitAmount = (totalBuyAmount * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n; if (totalProfitAmount > 0n) { const payerWallet = new Wallet(privateKeys[maxBuyerIndex + 1], provider); const profitNonce = await getNextNonce(payerWallet); const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet, profitAmount: totalProfitAmount, profitRecipient: PROFIT_CONFIG.RECIPIENT, hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: 56, txType: 0, startNonce: profitNonce }); signedTxs.push(...profitHopResult.signedTransactions); profitTxs.push(...profitHopResult.signedTransactions); } // 可选 tipTx if (config.tipAmountWei && config.tipAmountWei > 0n) { const tipTx = await devWallet.signTransaction({ to: BUILDER_CONTROL_EOA, value: config.tipAmountWei, gasPrice, gasLimit: 21000n, chainId: 56, type: 0, // Legacy transaction nonce: await getNextNonce(devWallet) }); signedTxs.push(tipTx); } // 5. 提交 Bundle let bundleUuid; if (config.customSubmitFn) { // ✅ 使用自定义提交函数(通过服务器代理) bundleUuid = await config.customSubmitFn(signedTxs); } else if (config.spPrivateKey) { await sendBatchPrivateTransactions(signedTxs, config.spPrivateKey, config.club48Endpoint || 'https://puissant-bsc.48.club', { spMode: config.spMode ?? 'timestampPersonalSign', spVMode: config.spVMode }); // 批量提交不返回 bundleUuid,使用第一笔交易的 hash 作为标识 bundleUuid = ethers.keccak256(signedTxs[0]); } else { // ✅ 直接提交到 48.club Bundle(仅服务器端可用) if (!club48) { throw new Error('❌ 浏览器环境必须提供 customSubmitFn 或 spPrivateKey 来提交 Bundle(避免 CORS 限制)'); } bundleUuid = await club48.sendBundle({ txs: signedTxs, maxBlockNumber: (await provider.getBlockNumber()) + (config.bundleBlockOffset ?? 100), noMerge: config.noMerge }, { spMode: config.spMode ?? 'timestampPersonalSign', spPrivateKey: config.spPrivateKey || privateKeys[0], spVMode: config.spVMode }); } // 6. 等待完成 let status; if (club48) { status = await club48.waitForBundle(bundleUuid); } else { // 使用 provider 等待交易上链 status = await waitForBundleWithProvider(provider, signedTxs[0], bundleUuid); } // 7. 解析 TokenCreate 事件获取创建的代币地址 let createdTokenAddress; if (status.status === 'INCLUDED' || status.status === 'PENDING') { try { // 从创建交易中解析代币地址 const createTxHash = ethers.Transaction.from(signedCreateTx).hash; if (createTxHash) { const receipt = await provider.getTransactionReceipt(createTxHash); if (receipt && receipt.logs) { const tm2Interface = new ethers.Interface(TM2Abi); for (const log of receipt.logs) { try { const parsed = tm2Interface.parseLog({ topics: log.topics, data: log.data }); if (parsed && parsed.name === 'TokenCreate') { createdTokenAddress = parsed.args.token; break; } } catch (e) { // 忽略无法解析的日志 } } } } } catch (e) { console.warn('Failed to parse token address from receipt:', e); } } return { bundleUuid, tokenAddress: createdTokenAddress, status, createTx: signedCreateTx, buyTxs, profitTxs }; } /** * four.meme: 批量购买 */ export async function batchBuyWithBundle(params) { const { privateKeys, buyAmounts, tokenAddress, config } = params; if (privateKeys.length === 0 || buyAmounts.length !== privateKeys.length) { throw new Error(getErrorMessage('KEY_AMOUNT_MISMATCH')); } const provider = new JsonRpcProvider(config.rpcUrl); const club48 = new Club48Client({ endpoint: config.club48Endpoint, explorerEndpoint: config.club48ExplorerEndpoint }); const blockOffset = config.bundleBlockOffset ?? 100; const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; const gasPrice = await club48.getMinGasPrice(); const signedTxs = []; const nextNonceMap = new Map(); const getNextNonce = async (w) => { const key = w.address.toLowerCase(); const cached = nextNonceMap.get(key); if (cached !== undefined) { const n = cached; nextNonceMap.set(key, cached + 1); return n; } const onchain = await w.getNonce(); nextNonceMap.set(key, onchain + 1); return onchain; }; // ✅ 并行处理所有买家 const wallets = privateKeys.map(pk => new Wallet(pk, provider)); const fundsWeiList = buyAmounts.map(a => ethers.parseEther(a)); // 找出买入最多的买家(由他支付所有利润) let maxBuyerIndex = 0; let maxFunds = fundsWeiList[0]; for (let i = 1; i < fundsWeiList.length; i++) { if (fundsWeiList[i] > maxFunds) { maxFunds = fundsWeiList[i]; maxBuyerIndex = i; } } // 并行获取所有钱包的初始 nonce const initialNonces = await Promise.all(wallets.map(w => w.getNonce())); // 为每个钱包分配 nonce(普通钱包1个,利润支付者2个) const nonceMap = new Map(); wallets.forEach((w, i) => { nonceMap.set(w.address.toLowerCase(), initialNonces[i]); }); const getNextNonceLocal = (w) => { const key = w.address.toLowerCase(); const n = nonceMap.get(key); nonceMap.set(key, n + 1); return n; }; // 并行构建未签名交易和估算 gas const txDataList = await Promise.all(wallets.map(async (buyerWallet, i) => { const fundsWei = fundsWeiList[i]; const tm2 = new ethers.Contract(tmAddr, TM2Abi, buyerWallet); const buyTxUnsigned = await tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyerWallet.address, fundsWei, 0n, { value: fundsWei }); let gasLimit; try { const estimatedGas = await provider.estimateGas({ ...buyTxUnsigned, from: buyerWallet.address, value: fundsWei }); gasLimit = (estimatedGas * 120n) / 100n; } catch { gasLimit = 600000n; } return { buyTxUnsigned, gasLimit, buyerWallet, fundsWei }; })); // 并行签名所有买入交易 const signedBuyTxList = await Promise.all(txDataList.map(async (data, i) => { const buyTxRequest = { ...data.buyTxUnsigned, from: data.buyerWallet.address, nonce: getNextNonceLocal(data.buyerWallet), gasLimit: data.gasLimit, gasPrice: gasPrice, chainId: 56 }; return data.buyerWallet.signTransaction(buyTxRequest); })); signedTxs.push(...signedBuyTxList); // ✅ 聚合利润:由买入最多的人支付总利润(强制 2 跳中转) const totalFunds = fundsWeiList.reduce((sum, f) => sum + f, 0n); const totalProfit = (totalFunds * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n; if (totalProfit > 0n) { const payerWallet = wallets[maxBuyerIndex]; const profitNonce = getNextNonceLocal(payerWallet); const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet, profitAmount: totalProfit, profitRecipient: PROFIT_CONFIG.RECIPIENT, hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: 56, txType: 0, startNonce: profitNonce }); signedTxs.push(...profitHopResult.signedTransactions); } // 可选 tipTx(置前) if (config.tipAmountWei && config.tipAmountWei > 0n) { const tipWallet = new Wallet(privateKeys[0], provider); const tipTx = await tipWallet.signTransaction({ to: BUILDER_CONTROL_EOA, value: config.tipAmountWei, gasPrice, gasLimit: 21000n, chainId: 56, type: 0, // Legacy transaction nonce: await getNextNonce(tipWallet) }); signedTxs.unshift(tipTx); } const bundleUuid = await club48.sendBundle({ txs: signedTxs, maxBlockNumber: (await provider.getBlockNumber()) + blockOffset, noMerge: config.noMerge }, { spMode: config.spMode ?? 'timestampPersonalSign', spPrivateKey: config.spPrivateKey || privateKeys[0], spVMode: config.spVMode }); const status = await club48.waitForBundle(bundleUuid); return { bundleUuid, status, buyTxs: signedTxs }; } /** * four.meme: 批量卖出 */ export async function batchSellWithBundle(params) { const { privateKeys, sellAmounts, tokenAddress, config } = params; if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) { throw new Error(getErrorMessage('SELL_KEY_AMOUNT_MISMATCH')); } const provider = new JsonRpcProvider(config.rpcUrl); const club48 = new Club48Client({ endpoint: config.club48Endpoint, explorerEndpoint: config.club48ExplorerEndpoint }); const blockOffset = config.bundleBlockOffset ?? 100; const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; const gasPrice = await club48.getMinGasPrice(); const signedTxs = []; const nextNonceMap = new Map(); const getNextNonce = async (w) => { const key = w.address.toLowerCase(); const cached = nextNonceMap.get(key); if (cached !== undefined) { const n = cached; nextNonceMap.set(key, cached + 1); return n; } const onchain = await w.getNonce(); nextNonceMap.set(key, onchain + 1); return onchain; }; // ✅ 并行处理所有卖家 const wallets = privateKeys.map(pk => new Wallet(pk, provider)); const amountsWei = sellAmounts.map(a => ethers.parseUnits(a, 18)); // 并行预估所有卖出能获得的 BNB const estimatedBnbOuts = await Promise.all(amountsWei.map(async (amountWei) => { try { const result = await trySell('BSC', config.rpcUrl, tokenAddress, amountWei); return result.funds; } catch { return 0n; } })); // 找出收益最高的卖家(由他支付所有利润) let maxSellerIndex = 0; let maxBnbOut = estimatedBnbOuts[0]; for (let i = 1; i < estimatedBnbOuts.length; i++) { if (estimatedBnbOuts[i] > maxBnbOut) { maxBnbOut = estimatedBnbOuts[i]; maxSellerIndex = i; } } // 并行获取所有钱包的初始 nonce const initialNonces = await Promise.all(wallets.map(w => w.getNonce())); const nonceMap = new Map(); wallets.forEach((w, i) => { nonceMap.set(w.address.toLowerCase(), initialNonces[i]); }); const getNextNonceLocal = (w) => { const key = w.address.toLowerCase(); const n = nonceMap.get(key); nonceMap.set(key, n + 1); return n; }; // 并行构建未签名交易和估算 gas const txDataList = await Promise.all(wallets.map(async (sellerWallet, i) => { const amountWei = amountsWei[i]; const tm2 = new ethers.Contract(tmAddr, TM2Abi, sellerWallet); const sellTxUnsigned = await tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, 0n); let gasLimit; try { const estimatedGas = await provider.estimateGas({ ...sellTxUnsigned, from: sellerWallet.address }); gasLimit = (estimatedGas * 120n) / 100n; } catch { gasLimit = 600000n; } return { sellTxUnsigned, gasLimit, sellerWallet }; })); // 并行签名所有卖出交易 const signedSellTxList = await Promise.all(txDataList.map(async (data, i) => { const sellTxRequest = { ...data.sellTxUnsigned, from: data.sellerWallet.address, nonce: getNextNonceLocal(data.sellerWallet), gasLimit: data.gasLimit, gasPrice: gasPrice, chainId: 56 }; return data.sellerWallet.signTransaction(sellTxRequest); })); signedTxs.push(...signedSellTxList); // ✅ 聚合利润:由收益最高的人支付总利润(强制 2 跳中转) const totalBnbOut = estimatedBnbOuts.reduce((sum, b) => sum + b, 0n); const totalProfit = (totalBnbOut * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n; if (totalProfit > 0n) { const payerWallet = wallets[maxSellerIndex]; const profitNonce = getNextNonceLocal(payerWallet); const profitHopResult = await buildProfitHopTransactions({ provider, payerWallet, profitAmount: totalProfit, profitRecipient: PROFIT_CONFIG.RECIPIENT, hopCount: PROFIT_HOP_COUNT, gasPrice, chainId: 56, txType: 0, startNonce: profitNonce }); signedTxs.push(...profitHopResult.signedTransactions); } // 可选 tipTx(置前) if (config.tipAmountWei && config.tipAmountWei > 0n) { const tipWallet = new Wallet(privateKeys[0], provider); const tipTx = await tipWallet.signTransaction({ to: BUILDER_CONTROL_EOA, value: config.tipAmountWei, gasPrice, gasLimit: 21000n, chainId: 56, type: 0, // Legacy transaction nonce: await getNextNonce(tipWallet) }); signedTxs.unshift(tipTx); } const bundleUuid = await club48.sendBundle({ txs: signedTxs, maxBlockNumber: (await provider.getBlockNumber()) + blockOffset, noMerge: config.noMerge }, { spMode: config.spMode ?? 'timestampPersonalSign', spPrivateKey: config.spPrivateKey || privateKeys[0], spVMode: config.spVMode }); const status = await club48.waitForBundle(bundleUuid); return { bundleUuid, status, sellTxs: signedTxs }; } export async function fourPrivateBuy(params) { const { rpcUrl, privateKey, tokenAddress, funds, to, club48Endpoint, club48ExplorerEndpoint, spPrivateKey } = params; const provider = new JsonRpcProvider(rpcUrl); const wallet = new Wallet(privateKey, provider); const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint }); const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; const gasPrice = await club48.getMinGasPrice(); const fundsWei = ethers.parseEther(funds); const minAmount = 0n; const tm2 = new ethers.Contract(tmAddr, TM2Abi, wallet); const unsigned = await tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, to ?? wallet.address, fundsWei, minAmount, { value: fundsWei }); // 估算 gas 并添加 20% 安全余量 const estimatedGas = await provider.estimateGas({ ...unsigned, from: wallet.address, value: fundsWei }); const gasLimit = (estimatedGas * 120n) / 100n; const req = { ...unsigned, from: wallet.address, nonce: await wallet.getNonce(), gasLimit, gasPrice, chainId: 56, type: 0 }; const signed = await wallet.signTransaction(req); if (spPrivateKey) return await club48.sendPrivateTransactionWith48SP(signed, spPrivateKey); return await club48.sendPrivateTransaction(signed); } export async function fourPrivateSell(params) { const { rpcUrl, privateKey, tokenAddress, amount, minFunds, club48Endpoint, club48ExplorerEndpoint, spPrivateKey } = params; const provider = new JsonRpcProvider(rpcUrl); const wallet = new Wallet(privateKey, provider); const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint }); const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; const gasPrice = await club48.getMinGasPrice(); const amountWei = ethers.parseUnits(amount, 18); const minOut = minFunds ?? 0n; const tm2 = new ethers.Contract(tmAddr, TM2Abi, wallet); const unsigned = await tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut); // 估算 gas 并添加 20% 安全余量 const estimatedGas = await provider.estimateGas({ ...unsigned, from: wallet.address }); const gasLimit = (estimatedGas * 120n) / 100n; const req = { ...unsigned, from: wallet.address, nonce: await wallet.getNonce(), gasLimit, gasPrice, chainId: 56, type: 0 }; const signed = await wallet.signTransaction(req); if (spPrivateKey) return await club48.sendPrivateTransactionWith48SP(signed, spPrivateKey); return await club48.sendPrivateTransaction(signed); } export async function fourBatchPrivateBuy(params) { const { rpcUrl, privateKeys, fundsList, tokenAddress, club48Endpoint, club48ExplorerEndpoint, spPrivateKey } = params; if (privateKeys.length !== fundsList.length) throw new Error('privateKeys and fundsList length mismatch'); const provider = new JsonRpcProvider(rpcUrl); const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint }); const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; // ✅ 并行处理 const wallets = privateKeys.map(pk => new Wallet(pk, provider)); const fundsWeiList = fundsList.map(f => ethers.parseEther(f)); // 并行获取 gasPrice 和所有 nonce const [gasPrice, nonces] = await Promise.all([ club48.getMinGasPrice(), Promise.all(wallets.map(w => w.getNonce())) ]); // 并行构建未签名交易和估算 gas const txDataList = await Promise.all(wallets.map(async (w, i) => { const fundsWei = fundsWeiList[i]; const tm2 = new ethers.Contract(tmAddr, TM2Abi, w); const unsigned = await tm2.buyTokenAMAP.populateTransaction(0n, tokenAddress, w.address, fundsWei, 0n, { value: fundsWei }); let gasLimit; try { const estimatedGas = await provider.estimateGas({ ...unsigned, from: w.address, value: fundsWei }); gasLimit = (estimatedGas * 120n) / 100n; } catch { gasLimit = 600000n; } return { unsigned, gasLimit, wallet: w, fundsWei }; })); // 并行签名所有交易 const signedTxs = await Promise.all(txDataList.map(async (data, i) => { const req = { ...data.unsigned, from: data.wallet.address, nonce: nonces[i], gasLimit: data.gasLimit, gasPrice, chainId: 56, type: 0 }; return data.wallet.signTransaction(req); })); if (spPrivateKey) { return await sendBatchPrivateTransactions(signedTxs, spPrivateKey, club48Endpoint || 'https://puissant-bsc.48.club'); } // 无 48SP:走 bundle 提交通道 const bundleUuid = await club48.sendBundle({ txs: signedTxs, maxBlockNumber: (await provider.getBlockNumber()) + 100 }); return !!bundleUuid; } export async function fourBatchPrivateSell(params) { const { rpcUrl, privateKeys, amounts, tokenAddress, club48Endpoint, club48ExplorerEndpoint, spPrivateKey, minFundsEach } = params; if (privateKeys.length !== amounts.length) throw new Error('privateKeys and amounts length mismatch'); const provider = new JsonRpcProvider(rpcUrl); const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint }); const tmAddr = ADDRESSES.BSC.TokenManagerV2Proxy; const minOut = minFundsEach ?? 0n; // ✅ 并行处理 const wallets = privateKeys.map(pk => new Wallet(pk, provider)); const amountsWei = amounts.map(a => ethers.parseUnits(a, 18)); // 并行获取 gasPrice 和所有 nonce const [gasPrice, nonces] = await Promise.all([ club48.getMinGasPrice(), Promise.all(wallets.map(w => w.getNonce())) ]); // 并行构建未签名交易和估算 gas const txDataList = await Promise.all(wallets.map(async (w, i) => { const amountWei = amountsWei[i]; const tm2 = new ethers.Contract(tmAddr, TM2Abi, w); const unsigned = await tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut); let gasLimit; try { const estimatedGas = await provider.estimateGas({ ...unsigned, from: w.address }); gasLimit = (estimatedGas * 120n) / 100n; } catch { gasLimit = 600000n; } return { unsigned, gasLimit, wallet: w }; })); // 并行签名所有交易 const signedTxs = await Promise.all(txDataList.map(async (data, i) => { const req = { ...data.unsigned, from: data.wallet.address, nonce: nonces[i], gasLimit: data.gasLimit, gasPrice, chainId: 56, type: 0 }; return data.wallet.signTransaction(req); })); return await sendBatchPrivateTransactions(signedTxs, spPrivateKey, club48Endpoint || 'https://puissant-bsc.48.club'); }