four-flap-meme-sdk 1.1.99 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -5,6 +5,7 @@ import { ADDRESSES } from '../../utils/constants.js';
|
|
|
5
5
|
import { FourClient, buildLoginMessage } from '../../clients/four.js';
|
|
6
6
|
import { getErrorMessage, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit } from './config.js';
|
|
7
7
|
import { batchCheckAllowances } from '../../utils/erc20.js';
|
|
8
|
+
import { trySell } from '../tm.js';
|
|
8
9
|
// ==================== TokenManager2 ABI(仅需要的方法)====================
|
|
9
10
|
const TM2_ABI = [
|
|
10
11
|
'function createToken(bytes args, bytes signature) payable',
|
|
@@ -322,28 +323,35 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
322
323
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
323
324
|
const sellers = privateKeys.map((k) => new Wallet(k, provider));
|
|
324
325
|
const amountsWei = sellAmounts.map((a) => ethers.parseUnits(a, 18));
|
|
325
|
-
// ⚠️ Four.meme 内盘代币通过 TokenManager bonding curve
|
|
326
|
-
// ✅
|
|
326
|
+
// ⚠️ Four.meme 内盘代币通过 TokenManager bonding curve 定价
|
|
327
|
+
// ✅ 优先使用用户提供的 minOutputAmounts,否则自动调用 trySell 获取
|
|
327
328
|
let minOuts;
|
|
328
329
|
let quotedOutputs = [];
|
|
329
330
|
if (minOutputAmounts && minOutputAmounts.length === sellers.length) {
|
|
330
|
-
// 用户提供了 minOutputAmounts
|
|
331
|
+
// 用户提供了 minOutputAmounts,用于滑点保护和利润计算
|
|
331
332
|
minOuts = minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
|
|
332
|
-
quotedOutputs = minOuts.map(m => m * 100n / 95n);
|
|
333
|
-
console.log(`📝 使用用户提供的 minOutputAmounts`);
|
|
334
|
-
console.log(`💰 minOutputAmounts:`);
|
|
335
|
-
minOuts.forEach((minOut, i) => {
|
|
336
|
-
console.log(` 钱包 ${i}: ${ethers.formatEther(minOut)} BNB`);
|
|
337
|
-
});
|
|
333
|
+
quotedOutputs = minOuts.map(m => m * 100n / 95n);
|
|
338
334
|
}
|
|
339
335
|
else {
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
336
|
+
// ✅ 自动调用 trySell 获取每个钱包的预期收益
|
|
337
|
+
console.log('💰 自动获取卖出报价...');
|
|
338
|
+
const rpcUrl = config.customRpcUrl;
|
|
339
|
+
quotedOutputs = await Promise.all(amountsWei.map(async (amount, i) => {
|
|
340
|
+
try {
|
|
341
|
+
const result = await trySell('BSC', rpcUrl, tokenAddress, amount);
|
|
342
|
+
const quoted = result.funds; // 预期收益
|
|
343
|
+
console.log(` 钱包 ${i}: 卖出 ${ethers.formatUnits(amount, 18)} 代币 → 预期收益 ${ethers.formatEther(quoted)} BNB`);
|
|
344
|
+
return quoted;
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
console.log(` ⚠️ 钱包 ${i}: 报价失败,使用 minOut = 0`);
|
|
348
|
+
return 0n;
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
// minOuts 设为预期收益的 95%(作为滑点保护)
|
|
352
|
+
minOuts = quotedOutputs.map(q => q * 95n / 100n);
|
|
353
|
+
console.log('');
|
|
345
354
|
}
|
|
346
|
-
console.log('');
|
|
347
355
|
// ✅ Step 1: 检查代币余额和授权状态
|
|
348
356
|
const ERC20_ABI = [
|
|
349
357
|
'function approve(address spender, uint256 amount) returns (bool)',
|
|
@@ -351,60 +359,29 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
351
359
|
'function balanceOf(address owner) view returns (uint256)'
|
|
352
360
|
];
|
|
353
361
|
// 检查代币余额
|
|
354
|
-
console.log('🔍 检查代币余额...');
|
|
355
362
|
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
|
|
356
363
|
const balances = await Promise.all(sellers.map(w => tokenContract.balanceOf(w.address)));
|
|
357
364
|
for (let i = 0; i < sellers.length; i++) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
console.log(` 钱包 ${i} (${sellers[i].address.slice(0, 6)}...${sellers[i].address.slice(-4)}):`);
|
|
363
|
-
console.log(` 代币余额: ${balanceFormatted}`);
|
|
364
|
-
console.log(` 卖出数量: ${sellAmountFormatted}`);
|
|
365
|
-
if (balance < sellAmount) {
|
|
366
|
-
throw new Error(`钱包 ${i} (${sellers[i].address}) 代币余额不足\n` +
|
|
367
|
-
`需要: ${sellAmountFormatted}\n` +
|
|
368
|
-
`实际: ${balanceFormatted}`);
|
|
365
|
+
if (balances[i] < amountsWei[i]) {
|
|
366
|
+
throw new Error(`钱包 ${i} 代币余额不足:` +
|
|
367
|
+
`需要 ${ethers.formatUnits(amountsWei[i], 18)},` +
|
|
368
|
+
`实际 ${ethers.formatUnits(balances[i], 18)}`);
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
-
console.log('\n🔍 使用 Multicall3 批量检查授权状态...');
|
|
372
371
|
const allowances = await batchCheckAllowances(provider, tokenAddress, sellers.map(w => w.address), tmAddr);
|
|
373
372
|
// 找出需要授权的钱包索引
|
|
374
373
|
// ✅ 检查授权额度是否足够卖出
|
|
375
374
|
const needApprovalIndexes = [];
|
|
376
375
|
for (let i = 0; i < sellers.length; i++) {
|
|
377
|
-
|
|
378
|
-
const sellAmount = amountsWei[i];
|
|
379
|
-
const allowanceFormatted = allowance === ethers.MaxUint256 ? 'MAX (无限)' : ethers.formatUnits(allowance, 18);
|
|
380
|
-
const sellAmountFormatted = ethers.formatUnits(sellAmount, 18);
|
|
381
|
-
console.log(` 钱包 ${i}:`);
|
|
382
|
-
console.log(` 当前授权: ${allowanceFormatted}`);
|
|
383
|
-
console.log(` 需要授权: ${sellAmountFormatted}`);
|
|
384
|
-
// ✅ 检查授权额度是否 >= 卖出数量
|
|
385
|
-
if (allowance < sellAmount) {
|
|
376
|
+
if (allowances[i] < amountsWei[i]) {
|
|
386
377
|
needApprovalIndexes.push(i);
|
|
387
|
-
console.log(` ❌ 授权不足!`);
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
console.log(` ✅ 授权充足`);
|
|
391
378
|
}
|
|
392
379
|
}
|
|
393
|
-
|
|
394
|
-
console.log(` 总钱包数: ${sellers.length}`);
|
|
395
|
-
console.log(` 需要授权: ${needApprovalIndexes.length}`);
|
|
396
|
-
console.log(` 已有授权: ${sellers.length - needApprovalIndexes.length}\n`);
|
|
397
|
-
// ✅ Step 2: 批量授权(如果需要)
|
|
398
|
-
// ⚠️ 注意:授权需要单独提交,SDK只返回签名交易
|
|
380
|
+
// ✅ Step 2: 检查授权
|
|
399
381
|
if (needApprovalIndexes.length > 0) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
console.log(' ⚠️ 重要:请使用 amounts: ["max", "max", ...] 授权,避免重复授权');
|
|
403
|
-
throw new Error(`需要授权: ${needApprovalIndexes.length} 个钱包授权额度不足。\n` +
|
|
404
|
-
`解决方案:调用 approveFourTokenManagerBatch({ amounts: ['max', 'max', ...] })`);
|
|
382
|
+
throw new Error(`${needApprovalIndexes.length} 个钱包需要授权。` +
|
|
383
|
+
`请先调用 approveFourTokenManagerBatch({ amounts: ['max', ...] })`);
|
|
405
384
|
}
|
|
406
|
-
// ✅ Step 3: 批量卖出
|
|
407
|
-
console.log('💰 提交卖出交易...');
|
|
408
385
|
const nonceManager = new NonceManager(provider);
|
|
409
386
|
const signedTxs = [];
|
|
410
387
|
const tm2Contracts = sellers.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
@@ -423,17 +400,13 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
423
400
|
type: getTxType(config)
|
|
424
401
|
})));
|
|
425
402
|
signedTxs.push(...signedList);
|
|
426
|
-
// ✅
|
|
403
|
+
// ✅ 提取利润
|
|
427
404
|
const extractProfit = shouldExtractProfit(config);
|
|
428
405
|
if (extractProfit && quotedOutputs.length > 0) {
|
|
429
|
-
console.log(`💰 提取利润交易...`);
|
|
430
|
-
let totalProfitExtracted = 0n;
|
|
431
|
-
// 为每个钱包添加利润转账(基于完整报价金额,而非 minOut)
|
|
432
406
|
for (let i = 0; i < sellers.length; i++) {
|
|
433
|
-
if (quotedOutputs[i] > 0n) {
|
|
407
|
+
if (quotedOutputs[i] > 0n) {
|
|
434
408
|
const { profit } = calculateProfit(quotedOutputs[i], config);
|
|
435
409
|
if (profit > 0n) {
|
|
436
|
-
console.log(` ✅ 钱包 ${i}: 从 ${ethers.formatEther(quotedOutputs[i])} BNB 中提取 ${ethers.formatEther(profit)} BNB (${config.profitRateBps / 100}%)`);
|
|
437
410
|
const profitNonce = await nonceManager.getNextNonce(sellers[i]);
|
|
438
411
|
const profitTx = await sellers[i].signTransaction({
|
|
439
412
|
to: config.profitRecipient,
|
|
@@ -445,11 +418,9 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
445
418
|
type: getTxType(config)
|
|
446
419
|
});
|
|
447
420
|
signedTxs.push(profitTx);
|
|
448
|
-
totalProfitExtracted += profit;
|
|
449
421
|
}
|
|
450
422
|
}
|
|
451
423
|
}
|
|
452
|
-
console.log(` 💵 总利润: ${ethers.formatEther(totalProfitExtracted)} BNB → ${config.profitRecipient}\n`);
|
|
453
424
|
}
|
|
454
425
|
// ✅ 清理临时 nonce 缓存
|
|
455
426
|
nonceManager.clearTemp();
|
|
@@ -4,6 +4,7 @@ import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.j
|
|
|
4
4
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
5
5
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit } from './config.js';
|
|
6
6
|
import { batchCheckAllowances } from '../../utils/erc20.js';
|
|
7
|
+
import { trySell } from '../tm.js';
|
|
7
8
|
// ==================== TokenManager2 ABI(仅需要的方法)====================
|
|
8
9
|
const TM2_ABI = [
|
|
9
10
|
'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable',
|
|
@@ -229,16 +230,6 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
229
230
|
if (privateKeys.length !== amounts.length) {
|
|
230
231
|
throw new Error('privateKeys and amounts length mismatch');
|
|
231
232
|
}
|
|
232
|
-
// ⚠️ 友好提示:利润提取需要 minFundsEach
|
|
233
|
-
if (!minFundsEach && shouldExtractProfit(config)) {
|
|
234
|
-
console.log('\n⚠️ 重要提示:未传入 minFundsEach 参数,将无法提取利润!');
|
|
235
|
-
console.log(' Four.meme 内盘代币无法自动报价,需要手动估算预期收益');
|
|
236
|
-
console.log(' 建议步骤:');
|
|
237
|
-
console.log(' 1. 在 four.meme 网站查看卖出预期收益');
|
|
238
|
-
console.log(' 2. 传入 minFundsEach 参数');
|
|
239
|
-
console.log(' 示例: minFundsEach: 0.1 (预计每个钱包收到 0.1 BNB)');
|
|
240
|
-
console.log(' 如果只是测试卖出,可以忽略此提示\n');
|
|
241
|
-
}
|
|
242
233
|
const merkle = new MerkleClient({
|
|
243
234
|
apiKey: config.apiKey,
|
|
244
235
|
chainId: 56,
|
|
@@ -248,118 +239,76 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
248
239
|
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
249
240
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
250
241
|
const gasMultiplier = config.gasLimitMultiplier ?? 1.0;
|
|
251
|
-
const minOut = minFundsEach ?? 0n;
|
|
252
242
|
const sellGasLimit = BigInt(Math.ceil(800000 * gasMultiplier));
|
|
253
243
|
const signedTxs = [];
|
|
254
244
|
const nonceManager = new NonceManager(provider);
|
|
255
245
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
246
|
+
const amountsWei = amounts.map((a) => ethers.parseUnits(a, 18));
|
|
247
|
+
// ✅ 自动获取每个钱包的预期收益(用于利润计算和滑点保护)
|
|
248
|
+
let quotedOutputs;
|
|
249
|
+
let minOuts;
|
|
250
|
+
if (minFundsEach !== undefined) {
|
|
251
|
+
// 用户提供了 minFundsEach,所有钱包使用相同的 minOut
|
|
252
|
+
const minOutWei = typeof minFundsEach === 'string' ? ethers.parseEther(minFundsEach) : minFundsEach;
|
|
253
|
+
minOuts = new Array(wallets.length).fill(minOutWei);
|
|
254
|
+
quotedOutputs = minOuts.map(m => m * 100n / 95n);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// ✅ 自动调用 trySell 获取每个钱包的预期收益
|
|
258
|
+
console.log('💰 自动获取卖出报价...');
|
|
259
|
+
const rpcUrl = config.customRpcUrl;
|
|
260
|
+
quotedOutputs = await Promise.all(amountsWei.map(async (amount, i) => {
|
|
261
|
+
try {
|
|
262
|
+
const result = await trySell('BSC', rpcUrl, tokenAddress, amount);
|
|
263
|
+
const quoted = result.funds;
|
|
264
|
+
console.log(` 钱包 ${i}: 卖出 ${ethers.formatUnits(amount, 18)} 代币 → 预期收益 ${ethers.formatEther(quoted)} BNB`);
|
|
265
|
+
return quoted;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.log(` ⚠️ 钱包 ${i}: 报价失败,使用 minOut = 0`);
|
|
269
|
+
return 0n;
|
|
270
|
+
}
|
|
271
|
+
}));
|
|
272
|
+
// minOuts 设为预期收益的 95%(作为滑点保护)
|
|
273
|
+
minOuts = quotedOutputs.map(q => q * 95n / 100n);
|
|
274
|
+
console.log('');
|
|
275
|
+
}
|
|
266
276
|
// ✅ Step 0: 检查代币余额
|
|
267
|
-
console.log('🔍 检查代币余额...');
|
|
268
277
|
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
|
|
269
278
|
const balances = await Promise.all(wallets.map(w => tokenContract.balanceOf(w.address)));
|
|
270
279
|
for (let i = 0; i < wallets.length; i++) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
console.log(` 钱包 ${i} (${wallets[i].address.slice(0, 6)}...${wallets[i].address.slice(-4)}):`);
|
|
276
|
-
console.log(` 代币余额: ${balanceFormatted}`);
|
|
277
|
-
console.log(` 卖出数量: ${sellAmountFormatted}`);
|
|
278
|
-
if (balance < sellAmount) {
|
|
279
|
-
throw new Error(`钱包 ${i} (${wallets[i].address}) 代币余额不足\n` +
|
|
280
|
-
`需要: ${sellAmountFormatted}\n` +
|
|
281
|
-
`实际: ${balanceFormatted}`);
|
|
280
|
+
if (balances[i] < amountsWei[i]) {
|
|
281
|
+
throw new Error(`钱包 ${i} 代币余额不足:` +
|
|
282
|
+
`需要 ${ethers.formatUnits(amountsWei[i], 18)},` +
|
|
283
|
+
`实际 ${ethers.formatUnits(balances[i], 18)}`);
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
|
-
// ✅ Step 1:
|
|
285
|
-
console.log('\n🔍 使用 Multicall3 批量检查授权状态...');
|
|
286
|
+
// ✅ Step 1: 检查授权状态
|
|
286
287
|
const allowances = await batchCheckAllowances(provider, tokenAddress, wallets.map(w => w.address), tmAddr);
|
|
287
|
-
// 找出需要授权的钱包索引
|
|
288
|
-
// ✅ 检查授权额度是否足够卖出
|
|
289
288
|
const needApprovalIndexes = [];
|
|
290
289
|
for (let i = 0; i < wallets.length; i++) {
|
|
291
|
-
|
|
292
|
-
const sellAmount = amountsWei[i];
|
|
293
|
-
const allowanceFormatted = allowance === ethers.MaxUint256 ? 'MAX (无限)' : ethers.formatUnits(allowance, 18);
|
|
294
|
-
const sellAmountFormatted = ethers.formatUnits(sellAmount, 18);
|
|
295
|
-
console.log(` 钱包 ${i}:`);
|
|
296
|
-
console.log(` 当前授权: ${allowanceFormatted}`);
|
|
297
|
-
console.log(` 需要授权: ${sellAmountFormatted}`);
|
|
298
|
-
// ✅ 检查授权额度是否 >= 卖出数量
|
|
299
|
-
if (allowance < sellAmount) {
|
|
290
|
+
if (allowances[i] < amountsWei[i]) {
|
|
300
291
|
needApprovalIndexes.push(i);
|
|
301
|
-
console.log(` ❌ 授权不足!`);
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
console.log(` ✅ 授权充足`);
|
|
305
292
|
}
|
|
306
293
|
}
|
|
307
|
-
console.log(`\n 📊 授权统计:`);
|
|
308
|
-
console.log(` 总钱包数: ${wallets.length}`);
|
|
309
|
-
console.log(` 需要授权: ${needApprovalIndexes.length}`);
|
|
310
|
-
console.log(` 已有授权: ${wallets.length - needApprovalIndexes.length}\n`);
|
|
311
|
-
// ✅ Step 2: 如果需要授权,抛出错误提示
|
|
312
|
-
// ⚠️ SDK不再处理授权,需要前端先单独授权
|
|
313
294
|
if (needApprovalIndexes.length > 0) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
console.log(' ⚠️ 重要:请使用 amounts: ["max", "max", ...] 授权,避免重复授权');
|
|
317
|
-
throw new Error(`需要授权: ${needApprovalIndexes.length} 个钱包授权额度不足。\n` +
|
|
318
|
-
`解决方案:调用 approveFourTokenManagerBatch({ amounts: ['max', 'max', ...] })`);
|
|
295
|
+
throw new Error(`${needApprovalIndexes.length} 个钱包需要授权。` +
|
|
296
|
+
`请先调用 approveFourTokenManagerBatch({ amounts: ['max', ...] })`);
|
|
319
297
|
}
|
|
320
|
-
// ✅ Step
|
|
321
|
-
console.log('💰 检查 BNB 余额(用于支付 gas 费)...');
|
|
298
|
+
// ✅ Step 2: 检查 BNB 余额(用于支付 gas)
|
|
322
299
|
const bnbBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
|
|
323
300
|
const estimatedGasCost = sellGasLimit * gasPrice;
|
|
324
301
|
for (let i = 0; i < wallets.length; i++) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
console.log(` BNB 余额: ${bnbFormatted}`);
|
|
330
|
-
console.log(` 预计 Gas: ${gasFormatted}`);
|
|
331
|
-
if (bnbBalance < estimatedGasCost) {
|
|
332
|
-
console.log(` ❌ BNB 不足!`);
|
|
333
|
-
throw new Error(`钱包 ${i} BNB 余额不足支付 gas 费\n` +
|
|
334
|
-
`需要: ${gasFormatted} BNB\n` +
|
|
335
|
-
`实际: ${bnbFormatted} BNB\n` +
|
|
336
|
-
`缺少: ${ethers.formatEther(estimatedGasCost - bnbBalance)} BNB`);
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
const remaining = bnbBalance - estimatedGasCost;
|
|
340
|
-
console.log(` ✅ BNB 充足(剩余 ${ethers.formatEther(remaining)})`);
|
|
302
|
+
if (bnbBalances[i] < estimatedGasCost) {
|
|
303
|
+
throw new Error(`钱包 ${i} BNB 不足:` +
|
|
304
|
+
`需要 ${ethers.formatEther(estimatedGasCost)} BNB,` +
|
|
305
|
+
`实际 ${ethers.formatEther(bnbBalances[i])} BNB`);
|
|
341
306
|
}
|
|
342
307
|
}
|
|
343
|
-
// ✅ Step
|
|
344
|
-
console.log('\n💰 构建卖出交易...');
|
|
345
|
-
console.log(` TokenManager 合约: ${tmAddr}`);
|
|
346
|
-
console.log(` 代币地址: ${tokenAddress}`);
|
|
347
|
-
console.log(` minFundsEach (最小收益): ${minOut === 0n ? '0 (不限制)' : ethers.formatEther(minOut) + ' BNB'}`);
|
|
308
|
+
// ✅ Step 3: 构建卖出交易
|
|
348
309
|
const tm2Contracts = wallets.map((w) => new ethers.Contract(tmAddr, TM2_ABI, w));
|
|
349
|
-
|
|
350
|
-
console.log(`\n 📋 交易参数:`);
|
|
351
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
352
|
-
console.log(` 钱包 ${i}:`);
|
|
353
|
-
console.log(` 卖出数量: ${ethers.formatUnits(amountsWei[i], 18)} 代币`);
|
|
354
|
-
console.log(` 最小收益: ${minOut === 0n ? '0 BNB (接受任何价格)' : ethers.formatEther(minOut) + ' BNB'}`);
|
|
355
|
-
console.log(` 合约方法: sellToken(origin=0, token=${tokenAddress.slice(0, 8)}..., amount=${ethers.formatUnits(amountsWei[i], 18)}, minFunds=${minOut})`);
|
|
356
|
-
}
|
|
357
|
-
const sellUnsigned = await Promise.all(tm2Contracts.map((c, i) => c.sellToken.populateTransaction(0n, tokenAddress, amountsWei[i], minOut)));
|
|
310
|
+
const sellUnsigned = await Promise.all(tm2Contracts.map((c, i) => c.sellToken.populateTransaction(0n, tokenAddress, amountsWei[i], minOuts[i])));
|
|
358
311
|
const sellGasLimits = new Array(wallets.length).fill(sellGasLimit);
|
|
359
|
-
console.log(`\n ⛽ Gas 设置:`);
|
|
360
|
-
console.log(` Gas Limit: ${sellGasLimit}`);
|
|
361
|
-
console.log(` Gas Price: ${ethers.formatUnits(gasPrice, 'gwei')} Gwei`);
|
|
362
|
-
console.log(` 预计 Gas 费: ${ethers.formatEther(sellGasLimit * gasPrice)} BNB (每笔)\n`);
|
|
363
312
|
const sellNonces = await Promise.all(wallets.map((w) => nonceManager.getNextNonce(w)));
|
|
364
313
|
const signedSells = await Promise.all(sellUnsigned.map((unsigned, i) => wallets[i].signTransaction({
|
|
365
314
|
...unsigned,
|
|
@@ -371,42 +320,29 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
371
320
|
type: getTxType(config)
|
|
372
321
|
})));
|
|
373
322
|
signedTxs.push(...signedSells);
|
|
374
|
-
|
|
375
|
-
// ✅ 基于 minFunds 为每个钱包添加利润转账
|
|
323
|
+
// ✅ 提取利润(基于每个钱包的预期收益)
|
|
376
324
|
const extractProfit = shouldExtractProfit(config);
|
|
377
325
|
if (extractProfit) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
signedTxs.push(...profitTxs);
|
|
395
|
-
console.log(` ✅ 已添加 ${wallets.length} 个利润转账交易\n`);
|
|
326
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
327
|
+
if (quotedOutputs[i] > 0n) {
|
|
328
|
+
const { profit } = calculateProfit(quotedOutputs[i], config);
|
|
329
|
+
if (profit > 0n) {
|
|
330
|
+
const profitNonce = await nonceManager.getNextNonce(wallets[i]);
|
|
331
|
+
const profitTx = await wallets[i].signTransaction({
|
|
332
|
+
to: config.profitRecipient,
|
|
333
|
+
value: profit,
|
|
334
|
+
nonce: profitNonce,
|
|
335
|
+
gasPrice,
|
|
336
|
+
gasLimit: 21000n,
|
|
337
|
+
chainId: 56,
|
|
338
|
+
type: getTxType(config)
|
|
339
|
+
});
|
|
340
|
+
signedTxs.push(profitTx);
|
|
341
|
+
}
|
|
396
342
|
}
|
|
397
343
|
}
|
|
398
|
-
else {
|
|
399
|
-
console.log(`⚠️ minFundsEach = 0,无法提取利润(建议传入 minFundsEach 参数)\n`);
|
|
400
|
-
}
|
|
401
344
|
}
|
|
402
|
-
// ✅ 清理临时 nonce 缓存
|
|
403
345
|
nonceManager.clearTemp();
|
|
404
|
-
console.log(`📦 Bundle 摘要:`);
|
|
405
|
-
console.log(` 总交易数: ${signedTxs.length}`);
|
|
406
|
-
console.log(` - 卖出交易: ${signedSells.length}`);
|
|
407
|
-
console.log(` - 利润交易: ${signedTxs.length - signedSells.length}`);
|
|
408
|
-
console.log(`\n⚠️ 注意: 交易已签名,但尚未提交到链上`);
|
|
409
|
-
console.log(` 请将 signedTransactions 提交到服务器,由服务器调用 Merkle API\n`);
|
|
410
346
|
// ✅ 简化返回:只返回签名交易
|
|
411
347
|
return {
|
|
412
348
|
signedTransactions: signedTxs
|