four-flap-meme-sdk 1.3.93 → 1.3.94
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.
- package/dist/clients/blockrazor.js +1 -0
- package/dist/contracts/tm-bundle-merkle/config.d.ts +0 -5
- package/dist/contracts/tm-bundle-merkle/config.js +0 -10
- package/dist/contracts/tm-bundle-merkle/core.js +24 -92
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +54 -103
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +1 -0
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +6 -36
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +3 -0
- package/dist/contracts/tm-bundle-merkle/swap.js +6 -59
- package/dist/flap/portal-bundle-merkle/config.d.ts +0 -8
- package/dist/flap/portal-bundle-merkle/config.js +0 -17
- package/dist/flap/portal-bundle-merkle/core.js +68 -120
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +78 -136
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +30 -49
- package/dist/flap/portal-bundle-merkle/swap.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/swap.js +47 -75
- package/dist/flap/portal-bundle-merkle/types.d.ts +0 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/pancake/bundle-buy-first.d.ts +1 -1
- package/dist/pancake/bundle-buy-first.js +17 -49
- package/dist/pancake/bundle-swap.d.ts +4 -1
- package/dist/pancake/bundle-swap.js +33 -98
- package/dist/sol/constants.d.ts +126 -0
- package/dist/sol/constants.js +145 -0
- package/dist/sol/dex/index.d.ts +8 -0
- package/dist/sol/dex/index.js +12 -0
- package/dist/sol/dex/meteora/client.d.ts +76 -0
- package/dist/sol/dex/meteora/client.js +219 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
- package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
- package/dist/sol/dex/meteora/damm-v1.js +315 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
- package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
- package/dist/sol/dex/meteora/damm-v2.js +632 -0
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
- package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
- package/dist/sol/dex/meteora/dbc.d.ts +192 -0
- package/dist/sol/dex/meteora/dbc.js +619 -0
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
- package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
- package/dist/sol/dex/meteora/dlmm.d.ts +157 -0
- package/dist/sol/dex/meteora/dlmm.js +671 -0
- package/dist/sol/dex/meteora/index.d.ts +25 -0
- package/dist/sol/dex/meteora/index.js +65 -0
- package/dist/sol/dex/meteora/types.d.ts +787 -0
- package/dist/sol/dex/meteora/types.js +110 -0
- package/dist/sol/dex/orca/index.d.ts +10 -0
- package/dist/sol/dex/orca/index.js +16 -0
- package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
- package/dist/sol/dex/orca/orca-bundle.js +173 -0
- package/dist/sol/dex/orca/orca.d.ts +65 -0
- package/dist/sol/dex/orca/orca.js +474 -0
- package/dist/sol/dex/orca/types.d.ts +263 -0
- package/dist/sol/dex/orca/types.js +38 -0
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
- package/dist/sol/dex/orca/wavebreak-bundle.js +198 -0
- package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
- package/dist/sol/dex/orca/wavebreak-types.js +23 -0
- package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
- package/dist/sol/dex/orca/wavebreak.js +497 -0
- package/dist/sol/dex/pump/index.d.ts +9 -0
- package/dist/sol/dex/pump/index.js +14 -0
- package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
- package/dist/sol/dex/pump/pump-bundle.js +383 -0
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
- package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
- package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
- package/dist/sol/dex/pump/pump-swap.js +199 -0
- package/dist/sol/dex/pump/pump.d.ts +35 -0
- package/dist/sol/dex/pump/pump.js +352 -0
- package/dist/sol/dex/pump/types.d.ts +215 -0
- package/dist/sol/dex/pump/types.js +5 -0
- package/dist/sol/dex/raydium/index.d.ts +8 -0
- package/dist/sol/dex/raydium/index.js +12 -0
- package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
- package/dist/sol/dex/raydium/launchlab.js +210 -0
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
- package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
- package/dist/sol/dex/raydium/raydium.d.ts +40 -0
- package/dist/sol/dex/raydium/raydium.js +366 -0
- package/dist/sol/dex/raydium/types.d.ts +240 -0
- package/dist/sol/dex/raydium/types.js +5 -0
- package/dist/sol/index.d.ts +10 -0
- package/dist/sol/index.js +16 -0
- package/dist/sol/jito/bundle.d.ts +90 -0
- package/dist/sol/jito/bundle.js +263 -0
- package/dist/sol/jito/index.d.ts +7 -0
- package/dist/sol/jito/index.js +7 -0
- package/dist/sol/jito/tip.d.ts +51 -0
- package/dist/sol/jito/tip.js +83 -0
- package/dist/sol/jito/types.d.ts +100 -0
- package/dist/sol/jito/types.js +5 -0
- package/dist/sol/token/create-complete.d.ts +115 -0
- package/dist/sol/token/create-complete.js +235 -0
- package/dist/sol/token/create-token.d.ts +57 -0
- package/dist/sol/token/create-token.js +230 -0
- package/dist/sol/token/index.d.ts +9 -0
- package/dist/sol/token/index.js +14 -0
- package/dist/sol/token/metadata-upload.d.ts +86 -0
- package/dist/sol/token/metadata-upload.js +173 -0
- package/dist/sol/token/metadata.d.ts +92 -0
- package/dist/sol/token/metadata.js +274 -0
- package/dist/sol/token/types.d.ts +153 -0
- package/dist/sol/token/types.js +5 -0
- package/dist/sol/types.d.ts +176 -0
- package/dist/sol/types.js +7 -0
- package/dist/sol/utils/balance.d.ts +160 -0
- package/dist/sol/utils/balance.js +638 -0
- package/dist/sol/utils/connection.d.ts +78 -0
- package/dist/sol/utils/connection.js +168 -0
- package/dist/sol/utils/index.d.ts +9 -0
- package/dist/sol/utils/index.js +9 -0
- package/dist/sol/utils/lp-inspect.d.ts +129 -0
- package/dist/sol/utils/lp-inspect.js +900 -0
- package/dist/sol/utils/transfer.d.ts +125 -0
- package/dist/sol/utils/transfer.js +220 -0
- package/dist/sol/utils/wallet.d.ts +107 -0
- package/dist/sol/utils/wallet.js +210 -0
- package/dist/utils/erc20.d.ts +2 -108
- package/dist/utils/erc20.js +17 -65
- package/package.json +39 -4
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
- package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
|
|
2
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
3
3
|
import { ADDRESSES } from '../../utils/constants.js';
|
|
4
|
-
import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient
|
|
4
|
+
import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient } from './config.js';
|
|
5
5
|
import { batchCheckAllowances } from '../../utils/erc20.js';
|
|
6
6
|
const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
|
7
7
|
const MULTICALL3_ABI = [
|
|
@@ -56,27 +56,16 @@ function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
|
|
|
56
56
|
return BigInt(calculatedGas);
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* 查询代币 decimals
|
|
60
|
-
* ✅ 代币精度不会变化,缓存后永久有效
|
|
59
|
+
* 查询代币 decimals
|
|
61
60
|
*/
|
|
62
61
|
async function getTokenDecimals(tokenAddress, provider) {
|
|
63
|
-
const cacheKey = tokenAddress.toLowerCase();
|
|
64
|
-
// ✅ 检查缓存
|
|
65
|
-
const cached = tokenDecimalsCache.get(cacheKey);
|
|
66
|
-
if (cached !== undefined) {
|
|
67
|
-
return cached;
|
|
68
|
-
}
|
|
69
62
|
try {
|
|
70
63
|
const token = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
71
64
|
const decimals = await token.decimals();
|
|
72
|
-
|
|
73
|
-
// ✅ 缓存结果
|
|
74
|
-
tokenDecimalsCache.set(cacheKey, result);
|
|
75
|
-
return result;
|
|
65
|
+
return Number(decimals);
|
|
76
66
|
}
|
|
77
67
|
catch {
|
|
78
|
-
// 默认返回 18,兼容大部分 ERC20
|
|
79
|
-
tokenDecimalsCache.set(cacheKey, 18);
|
|
68
|
+
// 默认返回 18,兼容大部分 ERC20
|
|
80
69
|
return 18;
|
|
81
70
|
}
|
|
82
71
|
}
|
|
@@ -292,21 +281,11 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
292
281
|
const divisor = BigInt(10 ** decimalsDiff);
|
|
293
282
|
actualAmountsWei = remainingAmounts.map(amount => amount / divisor);
|
|
294
283
|
}
|
|
295
|
-
// ✅
|
|
296
|
-
const presetGasPrice = config.gasPrice;
|
|
297
|
-
const presetNonces = config.nonces;
|
|
298
|
-
// ✅ 只获取必需的数据(跳过已有的)
|
|
284
|
+
// ✅ 优化:第一批并行 - gasPrice、tokenDecimals、nonces(JSON-RPC 批量请求)
|
|
299
285
|
const [gasPrice, tokenDecimals, nonces] = await Promise.all([
|
|
300
|
-
|
|
301
|
-
presetGasPrice !== undefined
|
|
302
|
-
? Promise.resolve(presetGasPrice)
|
|
303
|
-
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
304
|
-
// tokenDecimals:有缓存
|
|
286
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
305
287
|
getTokenDecimals(tokenAddress, provider),
|
|
306
|
-
|
|
307
|
-
presetNonces && presetNonces.length === buyers.length
|
|
308
|
-
? Promise.resolve(presetNonces)
|
|
309
|
-
: allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
|
|
288
|
+
allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
|
|
310
289
|
]);
|
|
311
290
|
const minOuts = resolveBuyMinOutputs(params, buyers.length, tokenDecimals);
|
|
312
291
|
const needBNB = needSendBNB(routeType, params, useNativeToken);
|
|
@@ -322,26 +301,8 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
322
301
|
minOuts,
|
|
323
302
|
needBNB
|
|
324
303
|
});
|
|
325
|
-
// ✅
|
|
326
|
-
const
|
|
327
|
-
const bribeTxs = [];
|
|
328
|
-
if (bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0) {
|
|
329
|
-
const bribeNonce = nonces[maxFundsIndex];
|
|
330
|
-
const bribeTx = await buyers[maxFundsIndex].signTransaction({
|
|
331
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
332
|
-
value: bribeAmount,
|
|
333
|
-
nonce: bribeNonce,
|
|
334
|
-
gasPrice,
|
|
335
|
-
gasLimit: 21000n,
|
|
336
|
-
chainId,
|
|
337
|
-
type: getTxType(config)
|
|
338
|
-
});
|
|
339
|
-
bribeTxs.push(bribeTx);
|
|
340
|
-
// 调整 maxFundsIndex 钱包的 nonce(买入交易 +1)
|
|
341
|
-
nonces[maxFundsIndex] = bribeNonce + 1;
|
|
342
|
-
}
|
|
343
|
-
// ✅ 并行签名所有买入交易
|
|
344
|
-
const signedBuys = await Promise.all(unsignedBuys.map((unsigned, i) => {
|
|
304
|
+
// ✅ 并行签名所有交易
|
|
305
|
+
const signedTxs = await Promise.all(unsignedBuys.map((unsigned, i) => {
|
|
345
306
|
// ✅ ERC20 购买时 value 只需要 FLAT_FEE,不需要发送代币金额
|
|
346
307
|
const txValue = useNativeToken ? unsigned.value : FLAT_FEE;
|
|
347
308
|
return buyers[i].signTransaction({
|
|
@@ -355,25 +316,20 @@ export async function pancakeProxyBatchBuyMerkle(params) {
|
|
|
355
316
|
value: txValue
|
|
356
317
|
});
|
|
357
318
|
}));
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
type: getTxType(config)
|
|
370
|
-
});
|
|
371
|
-
profitTxs.push(profitTx);
|
|
372
|
-
}
|
|
319
|
+
await appendProfitTransfer({
|
|
320
|
+
extractProfit: shouldExtractProfitForBuy, // ✅ ERC20 购买时不提取利润
|
|
321
|
+
totalProfit: nativeProfitAmount,
|
|
322
|
+
wallets: buyers,
|
|
323
|
+
maxIndex: maxFundsIndex,
|
|
324
|
+
nonces,
|
|
325
|
+
gasPrice,
|
|
326
|
+
chainId,
|
|
327
|
+
config,
|
|
328
|
+
signedTxs
|
|
329
|
+
});
|
|
373
330
|
nonceManager.clearTemp();
|
|
374
|
-
// ✅ 组装顺序:贿赂 → 买入 → 利润
|
|
375
331
|
return {
|
|
376
|
-
signedTransactions:
|
|
332
|
+
signedTransactions: signedTxs
|
|
377
333
|
};
|
|
378
334
|
}
|
|
379
335
|
/**
|
|
@@ -394,18 +350,10 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
394
350
|
const finalGasLimit = getGasLimit(config);
|
|
395
351
|
const extractProfit = shouldExtractProfit(config);
|
|
396
352
|
const nonceManager = new NonceManager(provider);
|
|
397
|
-
// ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
|
|
398
|
-
const presetGasPrice = config.gasPrice;
|
|
399
|
-
const presetNonces = config.nonces;
|
|
400
353
|
// ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
|
|
401
354
|
const [gasPrice, tokenDecimals] = await Promise.all([
|
|
402
|
-
|
|
403
|
-
presetGasPrice !== undefined
|
|
404
|
-
? Promise.resolve(presetGasPrice)
|
|
405
|
-
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
406
|
-
// tokenDecimals:有缓存
|
|
355
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
407
356
|
getTokenDecimals(tokenAddress, provider),
|
|
408
|
-
// allowances:必须检查
|
|
409
357
|
ensureAllowances({
|
|
410
358
|
provider,
|
|
411
359
|
tokenAddress,
|
|
@@ -414,8 +362,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
414
362
|
})
|
|
415
363
|
]);
|
|
416
364
|
const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
|
|
365
|
+
// ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
|
|
417
366
|
const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
|
|
418
|
-
// 获取报价(用于计算利润)
|
|
419
367
|
const { minOuts, quotedOutputs } = await resolveSellOutputs({
|
|
420
368
|
params,
|
|
421
369
|
provider,
|
|
@@ -440,27 +388,13 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
440
388
|
}
|
|
441
389
|
}
|
|
442
390
|
}
|
|
443
|
-
// ✅
|
|
444
|
-
const bribeAmount = getBribeAmount(config);
|
|
445
|
-
const needBribeTx = bribeAmount > 0n && maxRevenueIndex >= 0;
|
|
391
|
+
// ✅ 修复:根据是否需要利润交易,统一分配 nonces
|
|
446
392
|
const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
|
|
447
|
-
// 计算 maxRevenueIndex 钱包需要的 nonce 数量:贿赂(可选) + 卖出 + 利润(可选)
|
|
448
|
-
const maxRevenueNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
|
|
449
393
|
let nonces;
|
|
450
|
-
let bribeNonce;
|
|
451
394
|
let profitNonce;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (needBribeTx) {
|
|
456
|
-
bribeNonce = nonces[maxRevenueIndex];
|
|
457
|
-
nonces[maxRevenueIndex] = bribeNonce + 1; // 卖出交易 nonce +1
|
|
458
|
-
}
|
|
459
|
-
profitNonce = needProfitTx ? nonces[maxRevenueIndex] + 1 : undefined;
|
|
460
|
-
}
|
|
461
|
-
else if (maxRevenueNonceCount > 1 && maxRevenueIndex >= 0) {
|
|
462
|
-
// maxRevenueIndex 钱包需要多个连续 nonce
|
|
463
|
-
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], maxRevenueNonceCount);
|
|
395
|
+
if (needProfitTx) {
|
|
396
|
+
// maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
|
|
397
|
+
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
|
|
464
398
|
// 其他钱包各需要 1 个 nonce
|
|
465
399
|
const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
|
|
466
400
|
const otherNonces = otherSellers.length > 0
|
|
@@ -469,40 +403,20 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
469
403
|
// 组装最终的 nonces 数组(保持原顺序)
|
|
470
404
|
nonces = [];
|
|
471
405
|
let otherIdx = 0;
|
|
472
|
-
let nonceIdx = 0;
|
|
473
|
-
if (needBribeTx) {
|
|
474
|
-
bribeNonce = maxRevenueNonces[nonceIdx++]; // 贿赂交易用第一个 nonce
|
|
475
|
-
}
|
|
476
406
|
for (let i = 0; i < sellers.length; i++) {
|
|
477
407
|
if (i === maxRevenueIndex) {
|
|
478
|
-
nonces.push(maxRevenueNonces[
|
|
408
|
+
nonces.push(maxRevenueNonces[0]); // 卖出交易用第一个 nonce
|
|
479
409
|
}
|
|
480
410
|
else {
|
|
481
411
|
nonces.push(otherNonces[otherIdx++]);
|
|
482
412
|
}
|
|
483
413
|
}
|
|
484
|
-
|
|
485
|
-
profitNonce = maxRevenueNonces[nonceIdx]; // 利润交易用最后一个 nonce
|
|
486
|
-
}
|
|
414
|
+
profitNonce = maxRevenueNonces[1]; // 利润交易用第二个 nonce
|
|
487
415
|
}
|
|
488
416
|
else {
|
|
489
|
-
//
|
|
417
|
+
// 不需要利润交易,所有钱包各 1 个 nonce
|
|
490
418
|
nonces = await nonceManager.getNextNoncesForWallets(sellers);
|
|
491
419
|
}
|
|
492
|
-
// ✅ 贿赂交易放在首位
|
|
493
|
-
const bribeTxs = [];
|
|
494
|
-
if (needBribeTx && bribeNonce !== undefined) {
|
|
495
|
-
const bribeTx = await sellers[maxRevenueIndex].signTransaction({
|
|
496
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
497
|
-
value: bribeAmount,
|
|
498
|
-
nonce: bribeNonce,
|
|
499
|
-
gasPrice,
|
|
500
|
-
gasLimit: 21000n,
|
|
501
|
-
chainId,
|
|
502
|
-
type: getTxType(config)
|
|
503
|
-
});
|
|
504
|
-
bribeTxs.push(bribeTx);
|
|
505
|
-
}
|
|
506
420
|
const unsignedSells = await buildSellTransactions({
|
|
507
421
|
routeType,
|
|
508
422
|
params,
|
|
@@ -512,8 +426,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
512
426
|
amountsWei,
|
|
513
427
|
minOuts
|
|
514
428
|
});
|
|
515
|
-
// ✅
|
|
516
|
-
const
|
|
429
|
+
// ✅ 并行签名所有交易
|
|
430
|
+
const signedTxs = await Promise.all(unsignedSells.map((unsigned, i) => {
|
|
517
431
|
const txValue = unsigned.value;
|
|
518
432
|
return sellers[i].signTransaction({
|
|
519
433
|
...unsigned,
|
|
@@ -526,8 +440,7 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
526
440
|
value: txValue
|
|
527
441
|
});
|
|
528
442
|
}));
|
|
529
|
-
// ✅
|
|
530
|
-
const profitTxs = [];
|
|
443
|
+
// ✅ 添加利润交易(使用预先分配的 profitNonce)
|
|
531
444
|
if (needProfitTx && profitNonce !== undefined) {
|
|
532
445
|
const profitTx = await sellers[maxRevenueIndex].signTransaction({
|
|
533
446
|
to: getProfitRecipient(),
|
|
@@ -538,19 +451,16 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
538
451
|
chainId,
|
|
539
452
|
type: getTxType(config)
|
|
540
453
|
});
|
|
541
|
-
|
|
454
|
+
signedTxs.push(profitTx);
|
|
542
455
|
}
|
|
543
456
|
nonceManager.clearTemp();
|
|
544
|
-
// ✅ 组装顺序:贿赂 → 卖出 → 利润
|
|
545
457
|
return {
|
|
546
|
-
signedTransactions:
|
|
458
|
+
signedTransactions: signedTxs
|
|
547
459
|
};
|
|
548
460
|
}
|
|
549
461
|
// ✅ Provider 缓存(复用连接,减少初始化开销)
|
|
550
462
|
const providerCache = new Map();
|
|
551
463
|
const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
|
|
552
|
-
// ✅ Token Decimals 缓存(代币精度不会变化)
|
|
553
|
-
const tokenDecimalsCache = new Map();
|
|
554
464
|
function createChainContext(chain, rpcUrl) {
|
|
555
465
|
const chainId = CHAIN_ID_MAP[chain];
|
|
556
466
|
const cacheKey = `${chain}-${rpcUrl}`;
|
|
@@ -582,8 +492,10 @@ function findMaxAmountIndex(amounts) {
|
|
|
582
492
|
}
|
|
583
493
|
return maxIndex;
|
|
584
494
|
}
|
|
585
|
-
function resolveBuyMinOutputs(
|
|
586
|
-
|
|
495
|
+
function resolveBuyMinOutputs(params, walletCount, tokenDecimals) {
|
|
496
|
+
if (params.minOutputAmounts && params.minOutputAmounts.length === walletCount) {
|
|
497
|
+
return params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseUnits(m, tokenDecimals) : m);
|
|
498
|
+
}
|
|
587
499
|
return new Array(walletCount).fill(0n);
|
|
588
500
|
}
|
|
589
501
|
function createPancakeProxies(wallets, proxyAddress) {
|
|
@@ -639,6 +551,22 @@ async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, total
|
|
|
639
551
|
}
|
|
640
552
|
return nonces;
|
|
641
553
|
}
|
|
554
|
+
async function appendProfitTransfer({ extractProfit, totalProfit, wallets, maxIndex, nonces, gasPrice, chainId, config, signedTxs }) {
|
|
555
|
+
if (!extractProfit || totalProfit === 0n || wallets.length === 0 || maxIndex < 0) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const profitNonce = (nonces[maxIndex] ?? 0) + 1;
|
|
559
|
+
const profitTx = await wallets[maxIndex].signTransaction({
|
|
560
|
+
to: getProfitRecipient(),
|
|
561
|
+
value: totalProfit,
|
|
562
|
+
nonce: profitNonce,
|
|
563
|
+
gasPrice,
|
|
564
|
+
gasLimit: 21000n,
|
|
565
|
+
chainId,
|
|
566
|
+
type: getTxType(config)
|
|
567
|
+
});
|
|
568
|
+
signedTxs.push(profitTx);
|
|
569
|
+
}
|
|
642
570
|
async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
|
|
643
571
|
const allowances = await batchCheckAllowances(provider, tokenAddress, owners, spender);
|
|
644
572
|
const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
|
|
@@ -648,18 +576,26 @@ async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
|
|
|
648
576
|
}
|
|
649
577
|
}
|
|
650
578
|
/**
|
|
651
|
-
* ✅
|
|
652
|
-
* ✅ 已移除滑点保护:minOutput 固定为 0
|
|
579
|
+
* ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
|
|
653
580
|
*/
|
|
654
581
|
async function resolveSellOutputs({ params, provider, tokenAddress, routeType, amountsWei }) {
|
|
655
|
-
//
|
|
656
|
-
|
|
582
|
+
// 如果已提供 minOutputAmounts,直接使用,跳过报价查询
|
|
583
|
+
if (params.minOutputAmounts && params.minOutputAmounts.length === amountsWei.length) {
|
|
584
|
+
const minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
|
|
585
|
+
return {
|
|
586
|
+
minOuts,
|
|
587
|
+
quotedOutputs: minOuts.map(m => m * 100n / 95n)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
657
590
|
// 如果只有 1 个,直接调用(避免 multicall 开销)
|
|
658
591
|
if (amountsWei.length === 1) {
|
|
659
592
|
const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
|
|
660
|
-
return {
|
|
593
|
+
return {
|
|
594
|
+
quotedOutputs: [quotedOutput],
|
|
595
|
+
minOuts: [0n] // ✅ minOutput = 0,不设置滑点限制
|
|
596
|
+
};
|
|
661
597
|
}
|
|
662
|
-
// ✅ 使用 Multicall3 批量获取报价(仅 V2
|
|
598
|
+
// ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
|
|
663
599
|
if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
|
|
664
600
|
try {
|
|
665
601
|
const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
|
|
@@ -683,7 +619,10 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
|
|
|
683
619
|
}
|
|
684
620
|
return 0n;
|
|
685
621
|
});
|
|
686
|
-
return {
|
|
622
|
+
return {
|
|
623
|
+
quotedOutputs,
|
|
624
|
+
minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
|
|
625
|
+
};
|
|
687
626
|
}
|
|
688
627
|
catch {
|
|
689
628
|
// Multicall 失败,回退到并行调用
|
|
@@ -691,7 +630,10 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
|
|
|
691
630
|
}
|
|
692
631
|
// 回退:并行调用(V3 路由或 Multicall 失败时)
|
|
693
632
|
const quotedOutputs = await Promise.all(amountsWei.map(amount => getSingleQuote(params, provider, tokenAddress, routeType, amount)));
|
|
694
|
-
return {
|
|
633
|
+
return {
|
|
634
|
+
quotedOutputs,
|
|
635
|
+
minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
|
|
636
|
+
};
|
|
695
637
|
}
|
|
696
638
|
/**
|
|
697
639
|
* 获取单个报价
|
|
@@ -759,4 +701,4 @@ async function buildSellTransactions({ routeType, params, proxies, wallets, toke
|
|
|
759
701
|
}
|
|
760
702
|
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
761
703
|
}
|
|
762
|
-
// ✅
|
|
704
|
+
// ✅ appendSellProfitTransfer 已内联到 pancakeProxyBatchSellMerkle 中,避免 nonce 竞争问题
|
|
@@ -8,6 +8,7 @@ import { FlapSignConfig } from './config.js';
|
|
|
8
8
|
export type FlapChain = 'bsc' | 'xlayer' | 'base';
|
|
9
9
|
export interface FlapBuyFirstSignConfig extends FlapSignConfig {
|
|
10
10
|
reserveGasETH?: number;
|
|
11
|
+
slippageBps?: number;
|
|
11
12
|
skipQuoteOnError?: boolean;
|
|
12
13
|
}
|
|
13
14
|
export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
@@ -15,6 +16,7 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
|
15
16
|
customRpcUrl?: string;
|
|
16
17
|
bundleBlockOffset?: number;
|
|
17
18
|
reserveGasETH?: number;
|
|
19
|
+
slippageBps?: number;
|
|
18
20
|
skipQuoteOnError?: boolean;
|
|
19
21
|
waitForConfirmation?: boolean;
|
|
20
22
|
waitTimeoutMs?: number;
|
|
@@ -7,7 +7,7 @@ import { ethers, Contract, Wallet } from 'ethers';
|
|
|
7
7
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
8
8
|
import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
|
|
9
9
|
import { PROFIT_CONFIG } from '../../utils/constants.js';
|
|
10
|
-
import { getGasPriceConfig, getTxType, getProfitRecipient
|
|
10
|
+
import { getGasPriceConfig, getTxType, getProfitRecipient } from './config.js';
|
|
11
11
|
// Portal ABI
|
|
12
12
|
const PORTAL_ABI = [
|
|
13
13
|
'function swapExactInput((address inputToken, address outputToken, uint256 inputAmount, uint256 minOutputAmount, bytes permitData)) payable returns (uint256)',
|
|
@@ -124,11 +124,12 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
124
124
|
]);
|
|
125
125
|
const { buyerFundsWei, buyerBalance } = buyerFundsResult;
|
|
126
126
|
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
127
|
-
// ✅
|
|
127
|
+
// ✅ 获取报价
|
|
128
128
|
const quoteResult = await quoteBuyerOutput({
|
|
129
129
|
portalAddress: chainContext.portalAddress,
|
|
130
130
|
tokenAddress,
|
|
131
131
|
buyerFundsWei,
|
|
132
|
+
slippageBps: config.slippageBps,
|
|
132
133
|
provider: chainContext.provider,
|
|
133
134
|
skipQuoteOnError: config.skipQuoteOnError,
|
|
134
135
|
inputToken // ✅ 传递输入代币
|
|
@@ -176,16 +177,12 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
176
177
|
gasPrice,
|
|
177
178
|
txType
|
|
178
179
|
});
|
|
179
|
-
// ✅
|
|
180
|
-
const bribeAmount = getBribeAmount(config);
|
|
181
|
-
const needBribeTx = bribeAmount > 0n;
|
|
182
|
-
// ✅ 修复:根据实际的授权情况规划 nonce(包含贿赂交易)
|
|
180
|
+
// ✅ 修复:根据实际的授权情况规划 nonce
|
|
183
181
|
const noncePlan = await planNonces({
|
|
184
182
|
buyer,
|
|
185
183
|
seller,
|
|
186
184
|
approvalExists: approvalTx !== null, // ✅ 使用实际值
|
|
187
185
|
extractProfit: true,
|
|
188
|
-
needBribeTx, // ✅ 新增:是否需要贿赂交易
|
|
189
186
|
sameAddress,
|
|
190
187
|
nonceManager
|
|
191
188
|
});
|
|
@@ -219,38 +216,21 @@ export async function flapBundleBuyFirstMerkle(params) {
|
|
|
219
216
|
txType,
|
|
220
217
|
value: 0n // ✅ 卖出交易不发送原生代币
|
|
221
218
|
});
|
|
222
|
-
// ✅
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
219
|
+
// ✅ 并行签名所有交易
|
|
220
|
+
const [signedBuy, signedSell, profitTx] = await Promise.all([
|
|
221
|
+
buyer.signTransaction(buyTx),
|
|
222
|
+
seller.signTransaction(sellTx),
|
|
223
|
+
buildProfitTransaction({
|
|
224
|
+
seller,
|
|
225
|
+
profitAmount: nativeProfitAmount, // ✅ 使用转换后的原生代币利润
|
|
226
|
+
profitNonce: noncePlan.profitNonce,
|
|
229
227
|
gasPrice,
|
|
230
|
-
gasLimit: 21000n,
|
|
231
228
|
chainId: chainContext.chainId,
|
|
232
|
-
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
// ✅ 并行签名买入和卖出交易
|
|
236
|
-
const [signedBuy, signedSell] = await Promise.all([
|
|
237
|
-
buyer.signTransaction(buyTx),
|
|
238
|
-
seller.signTransaction(sellTx)
|
|
229
|
+
txType
|
|
230
|
+
})
|
|
239
231
|
]);
|
|
240
|
-
// ✅ 利润交易放在末尾
|
|
241
|
-
const profitTx = await buildProfitTransaction({
|
|
242
|
-
seller,
|
|
243
|
-
profitAmount: nativeProfitAmount, // ✅ 使用转换后的原生代币利润
|
|
244
|
-
profitNonce: noncePlan.profitNonce,
|
|
245
|
-
gasPrice,
|
|
246
|
-
chainId: chainContext.chainId,
|
|
247
|
-
txType
|
|
248
|
-
});
|
|
249
232
|
nonceManager.clearTemp();
|
|
250
|
-
// ✅ 组装顺序:贿赂 → 授权 → 买入 → 卖出 → 利润
|
|
251
233
|
const allTransactions = [];
|
|
252
|
-
if (bribeTx)
|
|
253
|
-
allTransactions.push(bribeTx);
|
|
254
234
|
if (approvalTx)
|
|
255
235
|
allTransactions.push(approvalTx);
|
|
256
236
|
allTransactions.push(signedBuy, signedSell);
|
|
@@ -320,31 +300,35 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
|
|
|
320
300
|
}
|
|
321
301
|
return { buyerFundsWei, buyerBalance };
|
|
322
302
|
}
|
|
323
|
-
async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
303
|
+
async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, slippageBps, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
324
304
|
}) {
|
|
325
305
|
let quotedToken = 0n;
|
|
306
|
+
let minOutToken = 0n;
|
|
326
307
|
const portal = new Contract(portalAddress, PORTAL_ABI, provider);
|
|
308
|
+
const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
|
|
327
309
|
try {
|
|
328
310
|
quotedToken = await portal.quoteExactInput.staticCall({
|
|
329
311
|
inputToken, // ✅ 使用动态输入代币
|
|
330
312
|
outputToken: tokenAddress,
|
|
331
313
|
inputAmount: buyerFundsWei
|
|
332
314
|
});
|
|
315
|
+
const keep = BigInt(10000 - safeSlippage);
|
|
316
|
+
minOutToken = (quotedToken * keep) / 10000n;
|
|
333
317
|
}
|
|
334
318
|
catch (error) {
|
|
335
319
|
if (skipQuoteOnError ?? true) {
|
|
336
320
|
quotedToken = 0n;
|
|
321
|
+
minOutToken = 0n;
|
|
337
322
|
}
|
|
338
323
|
else {
|
|
339
324
|
throw new Error(`买入报价失败: ${error}`);
|
|
340
325
|
}
|
|
341
326
|
}
|
|
342
|
-
|
|
343
|
-
const sellAmountWei = quotedToken;
|
|
327
|
+
const sellAmountWei = minOutToken > 0n ? minOutToken : quotedToken;
|
|
344
328
|
if (sellAmountWei <= 0n) {
|
|
345
|
-
throw new Error('卖方卖出数量为 0
|
|
329
|
+
throw new Error('卖方卖出数量为 0:报价失败或滑点过高');
|
|
346
330
|
}
|
|
347
|
-
return { quotedToken, minOutToken
|
|
331
|
+
return { quotedToken, minOutToken, sellAmountWei };
|
|
348
332
|
}
|
|
349
333
|
async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountWei, skipBalanceCheck }) {
|
|
350
334
|
const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, provider);
|
|
@@ -416,38 +400,35 @@ async function validateNativeBalances({ sameAddress, buyerBalance, buyerFundsWei
|
|
|
416
400
|
}
|
|
417
401
|
/**
|
|
418
402
|
* ✅ 优化:使用批量 nonce 获取
|
|
419
|
-
* 交易顺序:贿赂 → 授权 → 买入 → 卖出 → 利润
|
|
420
403
|
*/
|
|
421
|
-
async function planNonces({ buyer, seller, approvalExists, extractProfit,
|
|
404
|
+
async function planNonces({ buyer, seller, approvalExists, extractProfit, sameAddress, nonceManager }) {
|
|
422
405
|
if (sameAddress) {
|
|
423
406
|
// 同一地址:使用 getNextNonceBatch 获取连续 nonce
|
|
424
|
-
const txCount = countTruthy([
|
|
407
|
+
const txCount = countTruthy([approvalExists, true, true, extractProfit]);
|
|
425
408
|
const nonces = await nonceManager.getNextNonceBatch(buyer, txCount);
|
|
426
409
|
let idx = 0;
|
|
427
|
-
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
428
410
|
if (approvalExists)
|
|
429
411
|
idx++;
|
|
430
412
|
const buyerNonce = nonces[idx++];
|
|
431
413
|
const sellerNonce = nonces[idx++];
|
|
432
414
|
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
433
|
-
return { buyerNonce, sellerNonce,
|
|
415
|
+
return { buyerNonce, sellerNonce, profitNonce };
|
|
434
416
|
}
|
|
435
|
-
if (
|
|
436
|
-
// 卖方需要多个 nonce
|
|
437
|
-
const sellerTxCount = countTruthy([
|
|
417
|
+
if (approvalExists || extractProfit) {
|
|
418
|
+
// 卖方需要多个 nonce:使用 getNextNonceBatch
|
|
419
|
+
const sellerTxCount = countTruthy([approvalExists, true, extractProfit]);
|
|
438
420
|
// ✅ 优化:并行获取 buyer 和 seller 的 nonce(JSON-RPC 批量请求)
|
|
439
421
|
const [sellerNonces, buyerNonces] = await Promise.all([
|
|
440
422
|
nonceManager.getNextNonceBatch(seller, sellerTxCount),
|
|
441
423
|
nonceManager.getNextNoncesForWallets([buyer])
|
|
442
424
|
]);
|
|
443
425
|
let idx = 0;
|
|
444
|
-
const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
|
|
445
426
|
if (approvalExists)
|
|
446
427
|
idx++;
|
|
447
428
|
const sellerNonce = sellerNonces[idx++];
|
|
448
429
|
const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
|
|
449
430
|
const buyerNonce = buyerNonces[0];
|
|
450
|
-
return { buyerNonce, sellerNonce,
|
|
431
|
+
return { buyerNonce, sellerNonce, profitNonce };
|
|
451
432
|
}
|
|
452
433
|
// ✅ 优化:使用 getNextNoncesForWallets 批量获取(单次网络往返)
|
|
453
434
|
const nonces = await nonceManager.getNextNoncesForWallets([buyer, seller]);
|
|
@@ -13,6 +13,7 @@ export interface FlapSwapSignConfig {
|
|
|
13
13
|
txType?: 0 | 2;
|
|
14
14
|
chainId?: number;
|
|
15
15
|
reserveGasETH?: number;
|
|
16
|
+
slippageBps?: number;
|
|
16
17
|
skipQuoteOnError?: boolean;
|
|
17
18
|
skipApprovalCheck?: boolean;
|
|
18
19
|
}
|
|
@@ -22,6 +23,7 @@ export interface FlapSwapConfig extends CommonBundleConfig {
|
|
|
22
23
|
customRpcUrl?: string;
|
|
23
24
|
bundleBlockOffset?: number;
|
|
24
25
|
reserveGasETH?: number;
|
|
26
|
+
slippageBps?: number;
|
|
25
27
|
skipQuoteOnError?: boolean;
|
|
26
28
|
waitForConfirmation?: boolean;
|
|
27
29
|
waitTimeoutMs?: number;
|