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