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