four-flap-meme-sdk 1.3.89 → 1.3.90
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/core.js +3 -6
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +38 -30
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +1 -0
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +3 -4
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +3 -0
- package/dist/contracts/tm-bundle-merkle/swap.js +2 -2
- package/dist/flap/portal-bundle-merkle/core.js +6 -2
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +35 -55
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +11 -6
- package/dist/flap/portal-bundle-merkle/swap.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/swap.js +22 -10
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/pancake/bundle-buy-first.d.ts +1 -0
- package/dist/pancake/bundle-buy-first.js +9 -4
- package/dist/pancake/bundle-swap.d.ts +4 -0
- package/dist/pancake/bundle-swap.js +14 -7
- 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 +146 -0
- package/dist/sol/dex/meteora/dlmm.js +593 -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 +521 -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
|
@@ -151,6 +151,7 @@ export class BlockRazorClient {
|
|
|
151
151
|
const gasPrice = tx.gasPrice || 0n;
|
|
152
152
|
const minGasPrice = ethers.parseUnits(String(MIN_GAS_PRICE_GWEI), 'gwei');
|
|
153
153
|
if (gasPrice < minGasPrice) {
|
|
154
|
+
console.warn(`⚠️ 交易 Gas Price (${ethers.formatUnits(gasPrice, 'gwei')} Gwei) 低于最低要求 (${MIN_GAS_PRICE_GWEI} Gwei)`);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
catch {
|
|
@@ -412,8 +412,7 @@ async function resolveSellOutputs(params) {
|
|
|
412
412
|
try {
|
|
413
413
|
const result = await trySell('BSC', rpcUrl, tokenAddress, amountsWei[0]);
|
|
414
414
|
const quotedOutputs = [result.funds];
|
|
415
|
-
|
|
416
|
-
const minOuts = [0n];
|
|
415
|
+
const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
|
|
417
416
|
return { minOuts, quotedOutputs };
|
|
418
417
|
}
|
|
419
418
|
catch {
|
|
@@ -446,8 +445,7 @@ async function resolveSellOutputs(params) {
|
|
|
446
445
|
}
|
|
447
446
|
return 0n;
|
|
448
447
|
});
|
|
449
|
-
|
|
450
|
-
const minOuts = quotedOutputs.map(() => 0n);
|
|
448
|
+
const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
|
|
451
449
|
return { minOuts, quotedOutputs };
|
|
452
450
|
}
|
|
453
451
|
catch {
|
|
@@ -461,8 +459,7 @@ async function resolveSellOutputs(params) {
|
|
|
461
459
|
return 0n;
|
|
462
460
|
}
|
|
463
461
|
}));
|
|
464
|
-
|
|
465
|
-
const minOuts = quotedOutputs.map(() => 0n);
|
|
462
|
+
const minOuts = quotedOutputs.map((quote) => (quote * 95n) / 100n);
|
|
466
463
|
return { minOuts, quotedOutputs };
|
|
467
464
|
}
|
|
468
465
|
}
|
|
@@ -429,40 +429,48 @@ export async function fourPancakeProxyBatchSellMerkle(params) {
|
|
|
429
429
|
if (needApprovalIndexes.length > 0) {
|
|
430
430
|
throw new Error(`需要授权: ${needApprovalIndexes.length} 个钱包尚未授权。请先完成授权后再卖出。`);
|
|
431
431
|
}
|
|
432
|
-
// ✅
|
|
432
|
+
// ✅ 自动获取报价或使用用户提供的 minOutputAmounts
|
|
433
|
+
let minOuts;
|
|
433
434
|
let quotedOutputs;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
else if (routeType === 'v3-single') {
|
|
439
|
-
// V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
|
|
440
|
-
quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
|
|
441
|
-
try {
|
|
442
|
-
const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
|
|
443
|
-
const result = await quoter.quoteExactInputSingle.staticCall({
|
|
444
|
-
tokenIn: tokenAddress,
|
|
445
|
-
tokenOut: params.v3TokenOut,
|
|
446
|
-
amountIn: amount,
|
|
447
|
-
fee: params.v3Fee,
|
|
448
|
-
sqrtPriceLimitX96: 0
|
|
449
|
-
});
|
|
450
|
-
return result[0];
|
|
451
|
-
}
|
|
452
|
-
catch {
|
|
453
|
-
return 0n;
|
|
454
|
-
}
|
|
455
|
-
}));
|
|
456
|
-
}
|
|
457
|
-
else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
|
|
458
|
-
// V3 多跳:使用 V2 备选路由
|
|
459
|
-
quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
|
|
435
|
+
if (params.minOutputAmounts && params.minOutputAmounts.length === sellers.length) {
|
|
436
|
+
// 用户提供了 minOutputAmounts,跳过报价查询
|
|
437
|
+
minOuts = params.minOutputAmounts.map(m => typeof m === 'string' ? ethers.parseEther(m) : m);
|
|
438
|
+
quotedOutputs = minOuts.map(m => m * 100n / 95n); // 反推预期收益
|
|
460
439
|
}
|
|
461
440
|
else {
|
|
462
|
-
|
|
441
|
+
// ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
|
|
442
|
+
if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
|
|
443
|
+
quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
|
|
444
|
+
}
|
|
445
|
+
else if (routeType === 'v3-single') {
|
|
446
|
+
// V3 单跳:并行调用(V3 Quoter 是 non-view 函数,不支持 Multicall3)
|
|
447
|
+
quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
|
|
448
|
+
try {
|
|
449
|
+
const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
|
|
450
|
+
const result = await quoter.quoteExactInputSingle.staticCall({
|
|
451
|
+
tokenIn: tokenAddress,
|
|
452
|
+
tokenOut: params.v3TokenOut,
|
|
453
|
+
amountIn: amount,
|
|
454
|
+
fee: params.v3Fee,
|
|
455
|
+
sqrtPriceLimitX96: 0
|
|
456
|
+
});
|
|
457
|
+
return result[0];
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return 0n;
|
|
461
|
+
}
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
else if (routeType === 'v3-multi' && params.v2Path && params.v2Path.length >= 2) {
|
|
465
|
+
// V3 多跳:使用 V2 备选路由
|
|
466
|
+
quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
quotedOutputs = new Array(amountsWei.length).fill(0n);
|
|
470
|
+
}
|
|
471
|
+
// ✅ minOuts = 0,不设置滑点限制(大额交易更稳定)
|
|
472
|
+
minOuts = quotedOutputs.map(() => 0n);
|
|
463
473
|
}
|
|
464
|
-
// ✅ 已移除滑点保护:minOuts 固定为 0
|
|
465
|
-
const minOuts = new Array(sellers.length).fill(0n);
|
|
466
474
|
// ✅ 计算利润并找出收益最多的钱包
|
|
467
475
|
let totalProfit = 0n;
|
|
468
476
|
let maxRevenueIndex = 0;
|
|
@@ -7,6 +7,7 @@ import { CommonBundleConfig } from '../../utils/bundle-helpers.js';
|
|
|
7
7
|
import { FourSignConfig } from './types.js';
|
|
8
8
|
export interface FourBuyFirstSignConfig extends FourSignConfig {
|
|
9
9
|
reserveGasBNB?: number;
|
|
10
|
+
slippageBps?: number;
|
|
10
11
|
}
|
|
11
12
|
export interface FourBuyFirstConfig extends CommonBundleConfig {
|
|
12
13
|
apiKey: string;
|
|
@@ -82,8 +82,8 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
82
82
|
// ✅ 优化:第三批并行 - trySell、构建交易、获取 nonces
|
|
83
83
|
const tmBuyer = new Contract(TM_ADDRESS, TM_ABI, buyer);
|
|
84
84
|
const tmSeller = new Contract(TM_ADDRESS, TM_ABI, seller);
|
|
85
|
-
|
|
86
|
-
const minBuyAmount =
|
|
85
|
+
const slippageBps = config.slippageBps ?? 100;
|
|
86
|
+
const minBuyAmount = (estimatedTokenAmount * BigInt(10000 - slippageBps)) / 10000n;
|
|
87
87
|
// 预先规划 nonces
|
|
88
88
|
const extractProfit = true;
|
|
89
89
|
const profitRateBps = getProfitRateBps();
|
|
@@ -121,8 +121,7 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
121
121
|
]);
|
|
122
122
|
const { buyerNonces, sellerNonces } = noncesResult;
|
|
123
123
|
const estimatedSellFunds = sellResult.funds;
|
|
124
|
-
|
|
125
|
-
const minSellFunds = 0n;
|
|
124
|
+
const minSellFunds = (estimatedSellFunds * BigInt(10000 - slippageBps)) / 10000n;
|
|
126
125
|
const profitAmount = extractProfit ? (estimatedSellFunds * BigInt(profitRateBps)) / 10000n : 0n;
|
|
127
126
|
// 更新卖出交易的 minSellFunds
|
|
128
127
|
sellUnsigned.data = tmSeller.interface.encodeFunctionData('sellToken', [
|
|
@@ -29,6 +29,7 @@ export interface FourBundleSwapSignParams {
|
|
|
29
29
|
sellPercentage?: number;
|
|
30
30
|
buyerPrivateKey: string;
|
|
31
31
|
tokenAddress: string;
|
|
32
|
+
slippageTolerance?: number;
|
|
32
33
|
config: FourSwapSignConfig;
|
|
33
34
|
}
|
|
34
35
|
export interface FourBundleSwapParams {
|
|
@@ -37,6 +38,7 @@ export interface FourBundleSwapParams {
|
|
|
37
38
|
sellPercentage?: number;
|
|
38
39
|
buyerPrivateKey: string;
|
|
39
40
|
tokenAddress: string;
|
|
41
|
+
slippageTolerance?: number;
|
|
40
42
|
config: FourSwapConfig;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
@@ -67,6 +69,7 @@ export interface FourBatchSwapSignParams {
|
|
|
67
69
|
buyerPrivateKeys: string[];
|
|
68
70
|
buyerRatios?: number[];
|
|
69
71
|
tokenAddress: string;
|
|
72
|
+
slippageTolerance?: number;
|
|
70
73
|
config: FourSwapSignConfig;
|
|
71
74
|
}
|
|
72
75
|
/**
|
|
@@ -12,7 +12,7 @@ import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
|
12
12
|
* Four内盘捆绑换手
|
|
13
13
|
*/
|
|
14
14
|
export async function fourBundleSwapMerkle(params) {
|
|
15
|
-
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config } = params;
|
|
15
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, slippageTolerance = 0.5, config } = params;
|
|
16
16
|
const chainIdNum = config.chainId ?? 56;
|
|
17
17
|
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
18
18
|
chainId: chainIdNum,
|
|
@@ -185,7 +185,7 @@ export async function fourBundleSwapMerkle(params) {
|
|
|
185
185
|
* 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
|
|
186
186
|
*/
|
|
187
187
|
export async function fourBatchSwapMerkle(params) {
|
|
188
|
-
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config } = params;
|
|
188
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, slippageTolerance = 0.5, config } = params;
|
|
189
189
|
// ✅ 校验买方数量(最多 24 个)
|
|
190
190
|
const MAX_BUYERS = 24;
|
|
191
191
|
if (buyerPrivateKeys.length === 0) {
|
|
@@ -628,8 +628,12 @@ async function quoteSellOutputsWithQuote(portal, tokenAddress, amountsWei, outpu
|
|
|
628
628
|
}));
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
|
-
function resolveMinOutputs(
|
|
632
|
-
|
|
631
|
+
function resolveMinOutputs(provided, walletCount, _quotedOutputs) {
|
|
632
|
+
if (provided && provided.length === walletCount) {
|
|
633
|
+
return provided.map(m => typeof m === 'string' ? ethers.parseEther(m) : BigInt(m));
|
|
634
|
+
}
|
|
635
|
+
// ✅ 默认 minOutput = 0,不设置滑点限制
|
|
636
|
+
// 原因:大额交易时 5% 滑点可能不够,导致交易失败
|
|
633
637
|
return Array(walletCount).fill(0n);
|
|
634
638
|
}
|
|
635
639
|
// ✅ appendSellProfitTransaction 已内联到 batchSellWithBundleMerkle 中,避免 nonce 竞争问题
|
|
@@ -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);
|
|
@@ -371,18 +350,10 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
371
350
|
const finalGasLimit = getGasLimit(config);
|
|
372
351
|
const extractProfit = shouldExtractProfit(config);
|
|
373
352
|
const nonceManager = new NonceManager(provider);
|
|
374
|
-
// ✅ 优化:如果前端传入了 gasPrice 和 nonces,跳过 RPC 调用
|
|
375
|
-
const presetGasPrice = config.gasPrice;
|
|
376
|
-
const presetNonces = config.nonces;
|
|
377
353
|
// ✅ 优化:第一批并行 - gasPrice、tokenDecimals、allowances
|
|
378
354
|
const [gasPrice, tokenDecimals] = await Promise.all([
|
|
379
|
-
|
|
380
|
-
presetGasPrice !== undefined
|
|
381
|
-
? Promise.resolve(presetGasPrice)
|
|
382
|
-
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
383
|
-
// tokenDecimals:有缓存
|
|
355
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
384
356
|
getTokenDecimals(tokenAddress, provider),
|
|
385
|
-
// allowances:必须检查
|
|
386
357
|
ensureAllowances({
|
|
387
358
|
provider,
|
|
388
359
|
tokenAddress,
|
|
@@ -391,8 +362,8 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
391
362
|
})
|
|
392
363
|
]);
|
|
393
364
|
const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
|
|
365
|
+
// ✅ 优化:第二批并行 - resolveSellOutputs 和 buildSellTransactions
|
|
394
366
|
const proxies = createPancakeProxies(sellers, ADDRESSES.BSC.PancakeProxy);
|
|
395
|
-
// 获取报价(用于计算利润)
|
|
396
367
|
const { minOuts, quotedOutputs } = await resolveSellOutputs({
|
|
397
368
|
params,
|
|
398
369
|
provider,
|
|
@@ -421,12 +392,7 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
421
392
|
const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
|
|
422
393
|
let nonces;
|
|
423
394
|
let profitNonce;
|
|
424
|
-
|
|
425
|
-
if (presetNonces && presetNonces.length === sellers.length) {
|
|
426
|
-
nonces = presetNonces;
|
|
427
|
-
profitNonce = needProfitTx ? presetNonces[maxRevenueIndex] + 1 : undefined;
|
|
428
|
-
}
|
|
429
|
-
else if (needProfitTx) {
|
|
395
|
+
if (needProfitTx) {
|
|
430
396
|
// maxRevenueIndex 钱包需要 2 个连续 nonce(卖出 + 利润)
|
|
431
397
|
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], 2);
|
|
432
398
|
// 其他钱包各需要 1 个 nonce
|
|
@@ -495,8 +461,6 @@ export async function pancakeProxyBatchSellMerkle(params) {
|
|
|
495
461
|
// ✅ Provider 缓存(复用连接,减少初始化开销)
|
|
496
462
|
const providerCache = new Map();
|
|
497
463
|
const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
|
|
498
|
-
// ✅ Token Decimals 缓存(代币精度不会变化)
|
|
499
|
-
const tokenDecimalsCache = new Map();
|
|
500
464
|
function createChainContext(chain, rpcUrl) {
|
|
501
465
|
const chainId = CHAIN_ID_MAP[chain];
|
|
502
466
|
const cacheKey = `${chain}-${rpcUrl}`;
|
|
@@ -528,8 +492,10 @@ function findMaxAmountIndex(amounts) {
|
|
|
528
492
|
}
|
|
529
493
|
return maxIndex;
|
|
530
494
|
}
|
|
531
|
-
function resolveBuyMinOutputs(
|
|
532
|
-
|
|
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
|
+
}
|
|
533
499
|
return new Array(walletCount).fill(0n);
|
|
534
500
|
}
|
|
535
501
|
function createPancakeProxies(wallets, proxyAddress) {
|
|
@@ -610,18 +576,26 @@ async function ensureAllowances({ provider, tokenAddress, owners, spender }) {
|
|
|
610
576
|
}
|
|
611
577
|
}
|
|
612
578
|
/**
|
|
613
|
-
* ✅
|
|
614
|
-
* ✅ 已移除滑点保护:minOutput 固定为 0
|
|
579
|
+
* ✅ 使用 Multicall3 批量获取卖出报价(单次 RPC)
|
|
615
580
|
*/
|
|
616
581
|
async function resolveSellOutputs({ params, provider, tokenAddress, routeType, amountsWei }) {
|
|
617
|
-
//
|
|
618
|
-
|
|
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
|
+
}
|
|
619
590
|
// 如果只有 1 个,直接调用(避免 multicall 开销)
|
|
620
591
|
if (amountsWei.length === 1) {
|
|
621
592
|
const quotedOutput = await getSingleQuote(params, provider, tokenAddress, routeType, amountsWei[0]);
|
|
622
|
-
return {
|
|
593
|
+
return {
|
|
594
|
+
quotedOutputs: [quotedOutput],
|
|
595
|
+
minOuts: [0n] // ✅ minOutput = 0,不设置滑点限制
|
|
596
|
+
};
|
|
623
597
|
}
|
|
624
|
-
// ✅ 使用 Multicall3 批量获取报价(仅 V2
|
|
598
|
+
// ✅ 使用 Multicall3 批量获取报价(仅 V2 路由支持)
|
|
625
599
|
if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
|
|
626
600
|
try {
|
|
627
601
|
const v2RouterIface = new Interface(PANCAKE_V2_ROUTER_ABI);
|
|
@@ -645,7 +619,10 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
|
|
|
645
619
|
}
|
|
646
620
|
return 0n;
|
|
647
621
|
});
|
|
648
|
-
return {
|
|
622
|
+
return {
|
|
623
|
+
quotedOutputs,
|
|
624
|
+
minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
|
|
625
|
+
};
|
|
649
626
|
}
|
|
650
627
|
catch {
|
|
651
628
|
// Multicall 失败,回退到并行调用
|
|
@@ -653,7 +630,10 @@ async function resolveSellOutputs({ params, provider, tokenAddress, routeType, a
|
|
|
653
630
|
}
|
|
654
631
|
// 回退:并行调用(V3 路由或 Multicall 失败时)
|
|
655
632
|
const quotedOutputs = await Promise.all(amountsWei.map(amount => getSingleQuote(params, provider, tokenAddress, routeType, amount)));
|
|
656
|
-
return {
|
|
633
|
+
return {
|
|
634
|
+
quotedOutputs,
|
|
635
|
+
minOuts: quotedOutputs.map(() => 0n) // ✅ minOutput = 0,不设置滑点限制
|
|
636
|
+
};
|
|
657
637
|
}
|
|
658
638
|
/**
|
|
659
639
|
* 获取单个报价
|
|
@@ -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;
|
|
@@ -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 // ✅ 传递输入代币
|
|
@@ -299,31 +300,35 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
|
|
|
299
300
|
}
|
|
300
301
|
return { buyerFundsWei, buyerBalance };
|
|
301
302
|
}
|
|
302
|
-
async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
303
|
+
async function quoteBuyerOutput({ portalAddress, tokenAddress, buyerFundsWei, slippageBps, provider, skipQuoteOnError, inputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
303
304
|
}) {
|
|
304
305
|
let quotedToken = 0n;
|
|
306
|
+
let minOutToken = 0n;
|
|
305
307
|
const portal = new Contract(portalAddress, PORTAL_ABI, provider);
|
|
308
|
+
const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
|
|
306
309
|
try {
|
|
307
310
|
quotedToken = await portal.quoteExactInput.staticCall({
|
|
308
311
|
inputToken, // ✅ 使用动态输入代币
|
|
309
312
|
outputToken: tokenAddress,
|
|
310
313
|
inputAmount: buyerFundsWei
|
|
311
314
|
});
|
|
315
|
+
const keep = BigInt(10000 - safeSlippage);
|
|
316
|
+
minOutToken = (quotedToken * keep) / 10000n;
|
|
312
317
|
}
|
|
313
318
|
catch (error) {
|
|
314
319
|
if (skipQuoteOnError ?? true) {
|
|
315
320
|
quotedToken = 0n;
|
|
321
|
+
minOutToken = 0n;
|
|
316
322
|
}
|
|
317
323
|
else {
|
|
318
324
|
throw new Error(`买入报价失败: ${error}`);
|
|
319
325
|
}
|
|
320
326
|
}
|
|
321
|
-
|
|
322
|
-
const sellAmountWei = quotedToken;
|
|
327
|
+
const sellAmountWei = minOutToken > 0n ? minOutToken : quotedToken;
|
|
323
328
|
if (sellAmountWei <= 0n) {
|
|
324
|
-
throw new Error('卖方卖出数量为 0
|
|
329
|
+
throw new Error('卖方卖出数量为 0:报价失败或滑点过高');
|
|
325
330
|
}
|
|
326
|
-
return { quotedToken, minOutToken
|
|
331
|
+
return { quotedToken, minOutToken, sellAmountWei };
|
|
327
332
|
}
|
|
328
333
|
async function ensureSellerBalance({ tokenAddress, provider, seller, sellAmountWei, skipBalanceCheck }) {
|
|
329
334
|
const erc20 = new Contract(tokenAddress, ERC20_BALANCE_ABI, provider);
|
|
@@ -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;
|
|
@@ -116,7 +116,7 @@ export async function flapBundleSwapMerkle(params) {
|
|
|
116
116
|
]);
|
|
117
117
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
118
118
|
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
119
|
-
// ✅ 优化:第二批并行 - approval、quote
|
|
119
|
+
// ✅ 优化:第二批并行 - approval、quote
|
|
120
120
|
const [approvalTx, quote] = await Promise.all([
|
|
121
121
|
config.skipApprovalCheck
|
|
122
122
|
? Promise.resolve(null)
|
|
@@ -134,6 +134,7 @@ export async function flapBundleSwapMerkle(params) {
|
|
|
134
134
|
tokenAddress,
|
|
135
135
|
sellAmountWei,
|
|
136
136
|
provider: chainContext.provider,
|
|
137
|
+
slippageBps: config.slippageBps,
|
|
137
138
|
skipQuoteOnError: config.skipQuoteOnError,
|
|
138
139
|
outputToken // ✅ 传递输出代币
|
|
139
140
|
})
|
|
@@ -143,6 +144,7 @@ export async function flapBundleSwapMerkle(params) {
|
|
|
143
144
|
buyer,
|
|
144
145
|
quotedNative: quote.quotedNative,
|
|
145
146
|
reserveGasEth: config.reserveGasETH,
|
|
147
|
+
slippageBps: config.slippageBps,
|
|
146
148
|
nativeToken: chainContext.nativeToken,
|
|
147
149
|
useNativeToken,
|
|
148
150
|
quoteToken,
|
|
@@ -299,28 +301,32 @@ async function buildApprovalTransaction({ tokenAddress, seller, provider, decima
|
|
|
299
301
|
type: txType
|
|
300
302
|
});
|
|
301
303
|
}
|
|
302
|
-
async function quoteSellOutput({ portalAddress, tokenAddress, sellAmountWei, provider, skipQuoteOnError, outputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
304
|
+
async function quoteSellOutput({ portalAddress, tokenAddress, sellAmountWei, provider, slippageBps, skipQuoteOnError, outputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
303
305
|
}) {
|
|
304
306
|
const portal = new Contract(portalAddress, PORTAL_ABI, provider);
|
|
307
|
+
const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
|
|
305
308
|
try {
|
|
306
309
|
const quotedNative = await portal.quoteExactInput.staticCall({
|
|
307
310
|
inputToken: tokenAddress,
|
|
308
311
|
outputToken, // ✅ 使用动态输出代币
|
|
309
312
|
inputAmount: sellAmountWei
|
|
310
313
|
});
|
|
311
|
-
|
|
312
|
-
|
|
314
|
+
const keep = BigInt(10000 - safeSlippage);
|
|
315
|
+
const minOutNative = (quotedNative * keep) / 10000n;
|
|
316
|
+
return { quotedNative, minOutNative };
|
|
313
317
|
}
|
|
314
318
|
catch (err) {
|
|
315
319
|
if (skipQuoteOnError ?? true) {
|
|
320
|
+
console.warn(`⚠️ 报价失败,使用 minOut = 0: ${err}`);
|
|
316
321
|
return { quotedNative: 0n, minOutNative: 0n };
|
|
317
322
|
}
|
|
318
323
|
throw new Error(`卖出报价失败: ${err}`);
|
|
319
324
|
}
|
|
320
325
|
}
|
|
321
326
|
const ERC20_BALANCE_OF_ABI = ['function balanceOf(address) view returns (uint256)'];
|
|
322
|
-
async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
|
|
327
|
+
async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, slippageBps, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
|
|
323
328
|
const reserveGas = ethers.parseEther((reserveGasEth || 0.0005).toString());
|
|
329
|
+
const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
|
|
324
330
|
// ✅ 根据是否使用原生代币获取不同的余额
|
|
325
331
|
let buyerBalance;
|
|
326
332
|
if (useNativeToken) {
|
|
@@ -331,8 +337,11 @@ async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, nativeTo
|
|
|
331
337
|
const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
|
|
332
338
|
buyerBalance = await erc20.balanceOf(buyer.address);
|
|
333
339
|
}
|
|
334
|
-
|
|
335
|
-
|
|
340
|
+
let estimatedBuyerNeed = 0n;
|
|
341
|
+
if (quotedNative > 0n) {
|
|
342
|
+
const increase = BigInt(10000 + safeSlippage);
|
|
343
|
+
estimatedBuyerNeed = (quotedNative * increase) / 10000n;
|
|
344
|
+
}
|
|
336
345
|
// ✅ 原生代币需要预留 Gas,ERC20 不需要
|
|
337
346
|
const buyerNeedTotal = useNativeToken
|
|
338
347
|
? estimatedBuyerNeed + reserveGas
|
|
@@ -474,7 +483,7 @@ export async function flapBatchSwapMerkle(params) {
|
|
|
474
483
|
]);
|
|
475
484
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
476
485
|
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
477
|
-
// ✅
|
|
486
|
+
// ✅ 并行获取:授权、报价
|
|
478
487
|
const [approvalTx, quote] = await Promise.all([
|
|
479
488
|
config.skipApprovalCheck
|
|
480
489
|
? Promise.resolve(null)
|
|
@@ -492,12 +501,15 @@ export async function flapBatchSwapMerkle(params) {
|
|
|
492
501
|
tokenAddress,
|
|
493
502
|
sellAmountWei,
|
|
494
503
|
provider: chainContext.provider,
|
|
504
|
+
slippageBps: config.slippageBps,
|
|
495
505
|
skipQuoteOnError: config.skipQuoteOnError,
|
|
496
506
|
outputToken
|
|
497
507
|
})
|
|
498
508
|
]);
|
|
499
|
-
// ✅
|
|
500
|
-
const
|
|
509
|
+
// ✅ 计算每个买方的买入金额(按比例分配)
|
|
510
|
+
const safeSlippage = Math.max(0, Math.min(5000, config.slippageBps ?? 100));
|
|
511
|
+
const increase = BigInt(10000 + safeSlippage);
|
|
512
|
+
const totalBuyAmount = (quote.quotedNative * increase) / 10000n;
|
|
501
513
|
let buyAmountsWei;
|
|
502
514
|
if (params.buyerRatios && params.buyerRatios.length === buyers.length) {
|
|
503
515
|
// 按比例分配
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * as Abis from './abis/index.js';
|
|
2
|
+
export * as Sol from './sol/index.js';
|
|
2
3
|
export { ADDRESSES, CHAIN } from './utils/constants.js';
|
|
3
4
|
export { isExclusiveOnChain, isExclusiveOffChain } from './utils/mpcExclusive.js';
|
|
4
|
-
export { ensureSellApprovalV1, checkSellApprovalV1, ensureSellApprovalV2, checkSellApprovalV2, ensureSellApproval, checkSellApproval, ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch, checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch, checkAllowanceRaw, approveTokenRaw, checkAllowanceBatchRaw, approveTokenBatchRaw, type EnsureAllowanceBatchItemResult, type ApproveTokenBatchParams, type ApproveTokenBatchRawParams, type ApproveTokenBatchResult
|
|
5
|
+
export { ensureSellApprovalV1, checkSellApprovalV1, ensureSellApprovalV2, checkSellApprovalV2, ensureSellApproval, checkSellApproval, ensureFlapSellApproval, checkFlapSellApproval, ensureFlapSellApprovalBatch, checkFlapSellApprovalBatch, checkAllowance, approveToken, checkAllowanceBatch, approveTokenBatch, checkAllowanceRaw, approveTokenRaw, checkAllowanceBatchRaw, approveTokenBatchRaw, type EnsureAllowanceBatchItemResult, type ApproveTokenBatchParams, type ApproveTokenBatchRawParams, type ApproveTokenBatchResult } from './utils/erc20.js';
|
|
5
6
|
export { parseFourError, type FourErrorCode } from './utils/errors.js';
|
|
6
7
|
export { getTokenManagerV1, getTokenManagerV2, getTokenManagerHelper3, getTokenManagerV1Writer, getTokenManagerV2Writer, getTokenManagerHelper3Writer, getTokenManagerAddress, type ChainName } from './utils/contract-factory.js';
|
|
7
8
|
export { FourClient, buildLoginMessage, type FourConfig, type GenerateNonceReq, type LoginReq, type CreateTokenReq, type CreateTokenResp } from './clients/four.js';
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,8 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
98
98
|
const buyerNeed = calculateBuyerNeed({
|
|
99
99
|
quotedNative,
|
|
100
100
|
buyerBalance: buyerFundsInfo.buyerBalance,
|
|
101
|
-
reserveGas: buyerFundsInfo.reserveGas
|
|
101
|
+
reserveGas: buyerFundsInfo.reserveGas,
|
|
102
|
+
slippageBps: config.slippageBps
|
|
102
103
|
});
|
|
103
104
|
const finalGasLimit = getGasLimit(config);
|
|
104
105
|
const gasPrice = await getGasPrice(context.provider, config);
|
|
@@ -302,9 +303,13 @@ async function quoteSellerNative({ provider, tokenAddress, sellAmountToken }) {
|
|
|
302
303
|
return 0n;
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
|
-
function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas, slippageBps }) {
|
|
307
|
+
let estimatedBuyerNeed = 0n;
|
|
308
|
+
if (quotedNative > 0n) {
|
|
309
|
+
const safeSlippage = Math.max(0, Math.min(5000, slippageBps ?? 100));
|
|
310
|
+
const increase = BigInt(10000 + safeSlippage);
|
|
311
|
+
estimatedBuyerNeed = (quotedNative * increase) / 10000n;
|
|
312
|
+
}
|
|
308
313
|
const buyerNeedTotal = estimatedBuyerNeed + reserveGas;
|
|
309
314
|
if (buyerBalance < buyerNeedTotal) {
|
|
310
315
|
throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(buyerNeedTotal)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
|