four-flap-meme-sdk 4.0.14 → 4.1.0

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.
@@ -131,8 +131,7 @@ async function resolveSellOutputs(params) {
131
131
  try {
132
132
  const result = await trySell('BSC', rpcUrl, tokenAddress, amountsWei[0]);
133
133
  const quotedOutputs = [result.funds];
134
- // ✅ 已移除滑点保护:minOuts 固定为 0
135
- const minOuts = [0n];
134
+ const minOuts = [(result.funds * 9900n) / 10000n];
136
135
  return { minOuts, quotedOutputs };
137
136
  }
138
137
  catch {
@@ -165,8 +164,7 @@ async function resolveSellOutputs(params) {
165
164
  }
166
165
  return 0n;
167
166
  });
168
- // ✅ 已移除滑点保护:minOuts 固定为 0
169
- const minOuts = quotedOutputs.map(() => 0n);
167
+ const minOuts = quotedOutputs.map((q) => (q * 9900n) / 10000n);
170
168
  return { minOuts, quotedOutputs };
171
169
  }
172
170
  catch {
@@ -180,8 +178,7 @@ async function resolveSellOutputs(params) {
180
178
  return 0n;
181
179
  }
182
180
  }));
183
- // ✅ 已移除滑点保护:minOuts 固定为 0
184
- const minOuts = quotedOutputs.map(() => 0n);
181
+ const minOuts = quotedOutputs.map((q) => (q * 9900n) / 10000n);
185
182
  return { minOuts, quotedOutputs };
186
183
  }
187
184
  }
@@ -31,7 +31,7 @@ export async function fourPrivateSellMerkle(params) {
31
31
  const [gasPrice, sellNonce, sellUnsigned] = await Promise.all([
32
32
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
33
33
  wallet.getNonce(),
34
- tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, 0n), // ✅ 已移除滑点保护:minOut 固定为 0
34
+ tm2.sellToken.populateTransaction(0n, tokenAddress, amountWei, minOut),
35
35
  ]);
36
36
  // ✅ 已移除授权检查(前端负责确保授权)
37
37
  // ✅ 利润计算(同步)
@@ -133,8 +133,8 @@ export async function fourBatchPrivateSellMerkle(params) {
133
133
  }
134
134
  })),
135
135
  ]);
136
- // ✅ 已移除滑点保护:minOuts 固定为 0
137
- const minOuts = new Array(wallets.length).fill(0n);
136
+ // 报价 × (1 - 100bps)
137
+ const minOuts = quotedOutputs.map((q) => (q * 9900n) / 10000n);
138
138
  // ✅ 计算总利润和找出收益最高的钱包(同步计算)
139
139
  const extractProfit = shouldExtractProfit();
140
140
  let totalProfit = 0n;
@@ -6,20 +6,22 @@ import type { MultiSwapTxParams, MultiSwapUnsignedResult, RouteParams } from './
6
6
  /**
7
7
  * ✅ 构建多笔买入和卖出交易
8
8
  */
9
- export declare function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, useNativeToken, v2RouterAddress, }: MultiSwapTxParams): Promise<MultiSwapUnsignedResult>;
10
- export declare function buildSingleBuyTx({ routeParams, buyAmount, buyer, useNativeToken, deadline, v2RouterAddress, }: {
9
+ export declare function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, useNativeToken, v2RouterAddress, buyAmountOutMins, sellAmountOutMins, }: MultiSwapTxParams): Promise<MultiSwapUnsignedResult>;
10
+ export declare function buildSingleBuyTx({ routeParams, buyAmount, buyer, useNativeToken, deadline, v2RouterAddress, amountOutMin, }: {
11
11
  routeParams: RouteParams;
12
12
  buyAmount: bigint;
13
13
  buyer: Wallet;
14
14
  useNativeToken: boolean;
15
15
  deadline: bigint;
16
16
  v2RouterAddress?: string;
17
+ amountOutMin?: bigint;
17
18
  }): Promise<ethers.TransactionLike>;
18
- export declare function buildSingleSellTx({ routeParams, sellAmount, seller, useNativeToken, deadline, v2RouterAddress, }: {
19
+ export declare function buildSingleSellTx({ routeParams, sellAmount, seller, useNativeToken, deadline, v2RouterAddress, amountOutMin, }: {
19
20
  routeParams: RouteParams;
20
21
  sellAmount: bigint;
21
22
  seller: Wallet;
22
23
  useNativeToken: boolean;
23
24
  deadline: bigint;
24
25
  v2RouterAddress?: string;
26
+ amountOutMin?: bigint;
25
27
  }): Promise<ethers.TransactionLike>;
@@ -7,8 +7,10 @@ import { FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_
7
7
  /**
8
8
  * ✅ 构建多笔买入和卖出交易
9
9
  */
10
- export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, useNativeToken = true, v2RouterAddress, }) {
10
+ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, useNativeToken = true, v2RouterAddress, buyAmountOutMins, sellAmountOutMins, }) {
11
11
  const deadline = getDeadline();
12
+ const buyMinAt = (i) => buyAmountOutMins?.[i] ?? 0n;
13
+ const sellMinAt = (i) => sellAmountOutMins?.[i] ?? 0n;
12
14
  if (routeParams.routeType === 'v2') {
13
15
  const { v2Path } = routeParams;
14
16
  const reversePath = [...v2Path].reverse();
@@ -18,17 +20,17 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
18
20
  // ✅ 根据是否使用原生代币选择不同的函数
19
21
  if (useNativeToken) {
20
22
  // BNB 池子:使用 swapExactETHForTokens / swapExactTokensForETH
21
- const buyUnsignedArray = await Promise.all(buyAmountsWei.map((amount) => {
23
+ const buyUnsignedArray = await Promise.all(buyAmountsWei.map((amount, i) => {
22
24
  const buyValue = amount + FLAT_FEE;
23
- return v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyValue });
25
+ return v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(buyMinAt(i), v2Path, buyer.address, deadline, { value: buyValue });
24
26
  }));
25
- const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount) => v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
27
+ const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount, i) => v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amount, sellMinAt(i), reversePath, seller.address, deadline)));
26
28
  return { buyUnsignedArray, sellUnsignedArray };
27
29
  }
28
30
  else {
29
31
  // ✅ ERC20 池子(如 USDT):使用 swapExactTokensForTokens
30
- const buyUnsignedArray = await Promise.all(buyAmountsWei.map((amount) => v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, v2Path, buyer.address, deadline)));
31
- const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount) => v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
32
+ const buyUnsignedArray = await Promise.all(buyAmountsWei.map((amount, i) => v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, buyMinAt(i), v2Path, buyer.address, deadline)));
33
+ const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount, i) => v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, sellMinAt(i), reversePath, seller.address, deadline)));
32
34
  return { buyUnsignedArray, sellUnsignedArray };
33
35
  }
34
36
  }
@@ -38,7 +40,7 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
38
40
  const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
39
41
  const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
40
42
  // 构建多笔买入交易(ERC20 模式 value = 0,因为通过代币授权支付)
41
- const buyUnsignedArray = await Promise.all(buyAmountsWei.map((amount) => {
43
+ const buyUnsignedArray = await Promise.all(buyAmountsWei.map((amount, i) => {
42
44
  const buyValue = useNativeToken ? amount + FLAT_FEE : 0n;
43
45
  const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [
44
46
  {
@@ -47,7 +49,7 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
47
49
  fee: v3Fee,
48
50
  recipient: buyer.address,
49
51
  amountIn: amount,
50
- amountOutMinimum: 0n,
52
+ amountOutMinimum: buyMinAt(i),
51
53
  sqrtPriceLimitX96: 0n,
52
54
  },
53
55
  ]);
@@ -56,7 +58,7 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
56
58
  // ✅ 根据是否使用原生代币构建不同的卖出交易
57
59
  if (useNativeToken) {
58
60
  // BNB 池子:卖出后 unwrap WBNB 为 BNB
59
- const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount) => {
61
+ const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount, i) => {
60
62
  const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [
61
63
  {
62
64
  tokenIn: v3TokenOut,
@@ -64,18 +66,18 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
64
66
  fee: v3Fee,
65
67
  recipient: PANCAKE_V3_ROUTER_ADDRESS,
66
68
  amountIn: amount,
67
- amountOutMinimum: 0n,
69
+ amountOutMinimum: sellMinAt(i),
68
70
  sqrtPriceLimitX96: 0n,
69
71
  },
70
72
  ]);
71
- const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
73
+ const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [sellMinAt(i), seller.address]);
72
74
  return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
73
75
  }));
74
76
  return { buyUnsignedArray, sellUnsignedArray };
75
77
  }
76
78
  else {
77
79
  // ✅ ERC20 池子:卖出后直接获得 ERC20
78
- const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount) => {
80
+ const sellUnsignedArray = await Promise.all(sellAmountsWei.map((amount, i) => {
79
81
  const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [
80
82
  {
81
83
  tokenIn: v3TokenOut,
@@ -83,7 +85,7 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
83
85
  fee: v3Fee,
84
86
  recipient: seller.address, // 直接发送到卖方地址
85
87
  amountIn: amount,
86
- amountOutMinimum: 0n,
88
+ amountOutMinimum: sellMinAt(i),
87
89
  sqrtPriceLimitX96: 0n,
88
90
  },
89
91
  ]);
@@ -95,15 +97,15 @@ export async function buildMultiRouteTransactions({ routeParams, buyAmountsWei,
95
97
  throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
96
98
  }
97
99
  // ✅ 构建单笔买入交易
98
- export async function buildSingleBuyTx({ routeParams, buyAmount, buyer, useNativeToken, deadline, v2RouterAddress, }) {
100
+ export async function buildSingleBuyTx({ routeParams, buyAmount, buyer, useNativeToken, deadline, v2RouterAddress, amountOutMin = 0n, }) {
99
101
  if (routeParams.routeType === 'v2') {
100
102
  const { v2Path } = routeParams;
101
103
  const v2Router = new Contract(v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
102
104
  if (useNativeToken) {
103
- return await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyAmount });
105
+ return await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(amountOutMin, v2Path, buyer.address, deadline, { value: buyAmount });
104
106
  }
105
107
  else {
106
- return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, v2Path, buyer.address, deadline);
108
+ return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, amountOutMin, v2Path, buyer.address, deadline);
107
109
  }
108
110
  }
109
111
  if (routeParams.routeType === 'v3-single') {
@@ -118,7 +120,7 @@ export async function buildSingleBuyTx({ routeParams, buyAmount, buyer, useNativ
118
120
  fee: v3Fee,
119
121
  recipient: buyer.address,
120
122
  amountIn: buyAmount,
121
- amountOutMinimum: 0n,
123
+ amountOutMinimum: amountOutMin,
122
124
  sqrtPriceLimitX96: 0n,
123
125
  },
124
126
  ]);
@@ -127,16 +129,16 @@ export async function buildSingleBuyTx({ routeParams, buyAmount, buyer, useNativ
127
129
  throw new Error('V3 多跳路由暂不支持');
128
130
  }
129
131
  // ✅ 构建单笔卖出交易
130
- export async function buildSingleSellTx({ routeParams, sellAmount, seller, useNativeToken, deadline, v2RouterAddress, }) {
132
+ export async function buildSingleSellTx({ routeParams, sellAmount, seller, useNativeToken, deadline, v2RouterAddress, amountOutMin = 0n, }) {
131
133
  if (routeParams.routeType === 'v2') {
132
134
  const { v2Path } = routeParams;
133
135
  const reversePath = [...v2Path].reverse();
134
136
  const v2Router = new Contract(v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
135
137
  if (useNativeToken) {
136
- return await v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
138
+ return await v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmount, amountOutMin, reversePath, seller.address, deadline);
137
139
  }
138
140
  else {
139
- return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
141
+ return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmount, amountOutMin, reversePath, seller.address, deadline);
140
142
  }
141
143
  }
142
144
  if (routeParams.routeType === 'v3-single') {
@@ -151,11 +153,11 @@ export async function buildSingleSellTx({ routeParams, sellAmount, seller, useNa
151
153
  fee: v3Fee,
152
154
  recipient: PANCAKE_V3_ROUTER_ADDRESS,
153
155
  amountIn: sellAmount,
154
- amountOutMinimum: 0n,
156
+ amountOutMinimum: amountOutMin,
155
157
  sqrtPriceLimitX96: 0n,
156
158
  },
157
159
  ]);
158
- const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
160
+ const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [amountOutMin, seller.address]);
159
161
  return await v3Router.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
160
162
  }
161
163
  else {
@@ -166,7 +168,7 @@ export async function buildSingleSellTx({ routeParams, sellAmount, seller, useNa
166
168
  fee: v3Fee,
167
169
  recipient: seller.address,
168
170
  amountIn: sellAmount,
169
- amountOutMinimum: 0n,
171
+ amountOutMinimum: amountOutMin,
170
172
  sqrtPriceLimitX96: 0n,
171
173
  },
172
174
  ]);
@@ -11,6 +11,7 @@ import { createPancakeContext, getGasPrice, quoteTokenOutput } from './bundle-bu
11
11
  import { validateSwapCounts } from './bundle-buy-first-plan.js';
12
12
  import { buildSingleBuyTx, buildSingleSellTx } from './bundle-buy-first-build.js';
13
13
  import { buildProfitTransaction } from './bundle-buy-first-profit.js';
14
+ import { splitQuotedMins } from './quote.js';
14
15
  /**
15
16
  * ✅ 多钱包捆绑换手
16
17
  * - 多个买方钱包执行买入(每个钱包1笔)
@@ -88,6 +89,9 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
88
89
  }
89
90
  // ✅ 将代币平均分配给卖方
90
91
  const sellAmountsWei = splitAmount(actualTokenOut, sellCount);
92
+ const slippageBps = config.slippageBps ?? 100;
93
+ const buyAmountOutMins = splitQuotedMins(quoteResult.quotedTokenOut, buyAmountsWei, slippageBps);
94
+ const sellAmountOutMins = splitQuotedMins(totalFundsWei, sellAmountsWei, slippageBps);
91
95
  // 买合计万三 + 卖合计万三。ERC-20 先把买入合计折成 BNB 再计提,避免 ANTFUN 微额报价为 0
92
96
  let profitAmount = 0n;
93
97
  if (useNativeToken) {
@@ -140,6 +144,7 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
140
144
  useNativeToken,
141
145
  deadline,
142
146
  v2RouterAddress: config.v2RouterAddress,
147
+ amountOutMin: buyAmountOutMins[i],
143
148
  });
144
149
  return buyer.signTransaction({
145
150
  ...unsigned,
@@ -160,6 +165,7 @@ export async function pancakeBundleBuyFirstMultiWallet(params) {
160
165
  useNativeToken,
161
166
  deadline,
162
167
  v2RouterAddress: config.v2RouterAddress,
168
+ amountOutMin: sellAmountOutMins[i],
163
169
  });
164
170
  return seller.signTransaction({
165
171
  ...unsigned,
@@ -16,6 +16,8 @@ export interface PancakeBuyFirstSignConfig {
16
16
  skipQuoteOnError?: boolean;
17
17
  userType?: UserType;
18
18
  bribeAmount?: number;
19
+ /** 滑点基点,默认 100 = 1%。 */
20
+ slippageBps?: number;
19
21
  v2RouterAddress?: string;
20
22
  }
21
23
  export type UserType = 'v0' | 'v1';
@@ -131,6 +133,8 @@ export type MultiSwapTxParams = {
131
133
  seller: Wallet;
132
134
  useNativeToken?: boolean;
133
135
  v2RouterAddress?: string;
136
+ buyAmountOutMins?: bigint[];
137
+ sellAmountOutMins?: bigint[];
134
138
  };
135
139
  export type MultiSwapUnsignedResult = {
136
140
  buyUnsignedArray: ethers.TransactionLike[];
@@ -6,6 +6,7 @@ import { getGasLimit } from '../../../core/gas/bundle-gas.js';
6
6
  import { NonceManager } from '../../../utils/bundle-helpers.js';
7
7
  import { BLOCKRAZOR_BUILDER_EOA, ZERO_ADDRESS, GAS_LIMITS, calculateBundleSwapRoundTripProfit } from '../../../core/constants/index.js';
8
8
  import { quoteRoundTripProfitToNative } from '../../../core/quote/quote-helpers.js';
9
+ import { splitQuotedMins } from './quote.js';
9
10
  import { buildMultiRouteTransactions, buildProfitTransaction, calculateBuyerFunds, createPancakeContext, getGasPrice, pancakeBundleBuyFirstMultiWallet, planMultiNonces, quoteTokenOutput, splitAmount, validateFinalBalances, validateSwapCounts, } from './bundle-buy-first-helpers.js';
10
11
  export async function pancakeBundleBuyFirstMerkle(params) {
11
12
  const { buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, buyCount: _buyCount, sellCount: _sellCount, taxRateBps = 0, // ✅ 税率(基点),用于计算实际可卖出代币数量
@@ -66,6 +67,9 @@ export async function pancakeBundleBuyFirstMerkle(params) {
66
67
  actualTokenOut = (quoteResult.quotedTokenOut * BigInt(10000 - taxRateBps)) / 10000n;
67
68
  }
68
69
  const sellAmountsWei = splitAmount(actualTokenOut, sellCount);
70
+ const slippageBps = config.slippageBps ?? 100;
71
+ const buyAmountOutMins = splitQuotedMins(quoteResult.quotedTokenOut, buyAmountsWei, slippageBps);
72
+ const sellAmountOutMins = splitQuotedMins(buyerFundsInfo.buyerFundsWei, sellAmountsWei, slippageBps);
69
73
  // ✅ 构建多笔买入和卖出交易
70
74
  const swapUnsignedArray = await buildMultiRouteTransactions({
71
75
  routeParams,
@@ -75,6 +79,8 @@ export async function pancakeBundleBuyFirstMerkle(params) {
75
79
  seller,
76
80
  useNativeToken,
77
81
  v2RouterAddress: config.v2RouterAddress,
82
+ buyAmountOutMins,
83
+ sellAmountOutMins,
78
84
  });
79
85
  const finalGasLimit = getGasLimit(config, 800000);
80
86
  const gasPrice = await getGasPrice(context.provider, config);
@@ -53,6 +53,8 @@ export type BuildSwapParams = {
53
53
  tokenAddress: string;
54
54
  useNativeToken?: boolean;
55
55
  v2RouterAddress?: string;
56
+ sellAmountOutMin?: bigint;
57
+ buyAmountOutMin?: bigint;
56
58
  };
57
59
  export type BuyerBudgetParams = {
58
60
  buyer: Wallet;
@@ -95,6 +97,8 @@ export interface PancakeSwapSignConfig {
95
97
  reserveGasBNB?: number;
96
98
  userType?: UserType;
97
99
  bribeAmount?: number;
100
+ /** 滑点基点,默认 100 = 1%。买卖 amountOutMin 都按报价下浮。 */
101
+ slippageBps?: number;
98
102
  v2RouterAddress?: string;
99
103
  }
100
104
  export interface PancakeSwapConfig extends CommonBundleConfig {
@@ -1,5 +1,14 @@
1
1
  import { ethers } from 'ethers';
2
- import { type QuoteBNBResult, type QuoteSellParams, type UserType } from './bundle-swap-types.js';
2
+ import { type QuoteBNBResult, type QuoteSellParams, type RouteParams, type UserType } from './bundle-swap-types.js';
3
+ /** 100 bps = 1%。上限 50%,避免写成 0 输出。 */
4
+ export declare function applyPancakeSlippage(quotedOut: bigint, slippageBps?: number): bigint;
5
+ export declare function splitQuotedMins(quotedTotal: bigint, amounts: bigint[], slippageBps?: number): bigint[];
6
+ export declare function quoteBuyOutput(params: {
7
+ routeParams: RouteParams;
8
+ buyAmountWei: bigint;
9
+ provider: ethers.JsonRpcProvider;
10
+ v2RouterAddress?: string;
11
+ }): Promise<bigint>;
3
12
  export declare function quoteSellOutput({ routeParams, sellAmountWei, provider, v2RouterAddress, }: QuoteSellParams): Promise<QuoteBNBResult>;
4
13
  /**
5
14
  * 根据 userType 计算利润率
@@ -1,6 +1,36 @@
1
1
  import { ADDRESSES, getProfitRateBps as getSharedProfitRateBps, calculateBundleSwapRoundTripProfit } from '../../../core/constants/index.js';
2
2
  import { quoteV2, quoteV3 } from '../../../core/quote/quote-helpers.js';
3
3
  import { WBNB_ADDRESS, } from './bundle-swap-types.js';
4
+ /** 100 bps = 1%。上限 50%,避免写成 0 输出。 */
5
+ export function applyPancakeSlippage(quotedOut, slippageBps = 100) {
6
+ const bps = Math.max(0, Math.min(5000, Math.floor(Number.isFinite(slippageBps) ? slippageBps : 100)));
7
+ return (quotedOut * BigInt(10000 - bps)) / 10000n;
8
+ }
9
+ export function splitQuotedMins(quotedTotal, amounts, slippageBps = 100) {
10
+ const total = amounts.reduce((sum, amt) => sum + amt, 0n);
11
+ if (total <= 0n)
12
+ return amounts.map(() => 0n);
13
+ return amounts.map((amt) => applyPancakeSlippage((quotedTotal * amt) / total, slippageBps));
14
+ }
15
+ export async function quoteBuyOutput(params) {
16
+ const { routeParams, buyAmountWei, provider, v2RouterAddress } = params;
17
+ if (buyAmountWei <= 0n)
18
+ return 0n;
19
+ if (routeParams.routeType === 'v2') {
20
+ const reversePath = [...routeParams.v2Path].reverse();
21
+ const tokenIn = reversePath[0];
22
+ const tokenOut = reversePath[reversePath.length - 1];
23
+ if (!tokenIn || !tokenOut)
24
+ return 0n;
25
+ const result = await quoteV2(provider, tokenIn, tokenOut, buyAmountWei, 'BSC', v2RouterAddress);
26
+ return result.amountOut;
27
+ }
28
+ if (routeParams.routeType === 'v3-single') {
29
+ const result = await quoteV3(provider, routeParams.v3TokenOut, routeParams.v3TokenIn, buyAmountWei, 'BSC', routeParams.v3Fee);
30
+ return result.amountOut;
31
+ }
32
+ return 0n;
33
+ }
4
34
  export async function quoteSellOutput({ routeParams, sellAmountWei, provider, v2RouterAddress, }) {
5
35
  if (routeParams.routeType === 'v2') {
6
36
  const { v2Path } = routeParams;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { applyPancakeSlippage, splitQuotedMins } from './quote.js';
3
+ describe('applyPancakeSlippage', () => {
4
+ it('drops 1% by default', () => {
5
+ expect(applyPancakeSlippage(1000000n)).toBe(990000n);
6
+ });
7
+ it('clamps out-of-range bps', () => {
8
+ expect(applyPancakeSlippage(1000000n, -10)).toBe(1000000n);
9
+ expect(applyPancakeSlippage(1000000n, 9000)).toBe(500000n);
10
+ });
11
+ });
12
+ describe('splitQuotedMins', () => {
13
+ it('splits quoted output then applies slippage', () => {
14
+ expect(splitQuotedMins(1000000n, [250000n, 750000n], 100)).toEqual([247500n, 742500n]);
15
+ });
16
+ });
@@ -6,7 +6,7 @@ import { getGasLimit } from '../../../core/gas/bundle-gas.js';
6
6
  import { ethers, Contract, Wallet } from 'ethers';
7
7
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
8
  import { NonceManager, getDeadline, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
9
- import { BRIBE_GAS_LIMIT, ERC20_BALANCE_OF_ABI, FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, ZERO_ADDRESS, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, } from './bundle-swap-helpers.js';
9
+ import { BRIBE_GAS_LIMIT, ERC20_BALANCE_OF_ABI, FLAT_FEE, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, ZERO_ADDRESS, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, quoteBuyOutput, applyPancakeSlippage, } from './bundle-swap-helpers.js';
10
10
  /**
11
11
  * PancakeSwap 批量换手(一卖多买)
12
12
  *
@@ -88,6 +88,15 @@ export async function pancakeBatchSwapMerkle(params) {
88
88
  }
89
89
  }
90
90
  }));
91
+ const slippageBps = config.slippageBps ?? 100;
92
+ const sellMinOut = applyPancakeSlippage(estimatedBNBOut, slippageBps);
93
+ const buyQuotedOuts = await Promise.all(buyAmountsWei.map((amt) => quoteBuyOutput({
94
+ routeParams,
95
+ buyAmountWei: amt,
96
+ provider: context.provider,
97
+ v2RouterAddress: config.v2RouterAddress,
98
+ })));
99
+ const buyMinOuts = buyQuotedOuts.map((quoted) => applyPancakeSlippage(quoted, slippageBps));
91
100
  const deadline = getDeadline();
92
101
  const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
93
102
  const batchV2Addr = config.v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS;
@@ -97,11 +106,11 @@ export async function pancakeBatchSwapMerkle(params) {
97
106
  const v2RouterSeller = new Contract(batchV2Addr, PANCAKE_V2_ROUTER_ABI, seller);
98
107
  if (useNativeToken) {
99
108
  // ✅ 原生代币模式(BNB):Token → WBNB → BNB
100
- sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline);
109
+ sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, sellMinOut, v2Path, seller.address, deadline);
101
110
  }
102
111
  else {
103
112
  // ✅ ERC20 模式(USDT):Token → USDT
104
- sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline);
113
+ sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, sellMinOut, v2Path, seller.address, deadline);
105
114
  }
106
115
  }
107
116
  else if (routeParams.routeType === 'v3-single') {
@@ -114,7 +123,7 @@ export async function pancakeBatchSwapMerkle(params) {
114
123
  fee: v3Fee,
115
124
  recipient: useNativeToken ? PANCAKE_V3_ROUTER_ADDRESS : seller.address,
116
125
  amountIn: sellAmountWei,
117
- amountOutMinimum: 0n,
126
+ amountOutMinimum: sellMinOut,
118
127
  sqrtPriceLimitX96: 0n,
119
128
  },
120
129
  ]);
@@ -140,11 +149,11 @@ export async function pancakeBatchSwapMerkle(params) {
140
149
  const v2RouterBuyer = new Contract(batchV2Addr, PANCAKE_V2_ROUTER_ABI, buyer);
141
150
  if (useNativeToken) {
142
151
  // ✅ 原生代币模式(BNB):BNB → WBNB → Token
143
- return await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, reversePath, buyer.address, deadline, { value: buyValue });
152
+ return await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(buyMinOuts[i] ?? 0n, reversePath, buyer.address, deadline, { value: buyValue });
144
153
  }
145
154
  else {
146
155
  // ✅ ERC20 模式(USDT):USDT → Token
147
- return await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline);
156
+ return await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, buyMinOuts[i] ?? 0n, reversePath, buyer.address, deadline);
148
157
  }
149
158
  }
150
159
  else if (routeParams.routeType === 'v3-single') {
@@ -157,7 +166,7 @@ export async function pancakeBatchSwapMerkle(params) {
157
166
  fee: v3Fee,
158
167
  recipient: buyer.address,
159
168
  amountIn: buyAmount,
160
- amountOutMinimum: 0n,
169
+ amountOutMinimum: buyMinOuts[i] ?? 0n,
161
170
  sqrtPriceLimitX96: 0n,
162
171
  },
163
172
  ]);
@@ -2,7 +2,7 @@ import { ethers } from 'ethers';
2
2
  import { type BalanceValidationParams, type BuildSwapParams, type BuyerBudget, type BuyerBudgetParams, type NoncePlan, type PancakeContext, type PancakeSwapConfig, type PancakeSwapSignConfig, type PlanNonceParams, type SwapUnsignedResult } from './bundle-swap-types.js';
3
3
  export declare function createPancakeContext(config: PancakeSwapSignConfig): PancakeContext;
4
4
  export declare function getGasPrice(provider: ethers.JsonRpcProvider, config: PancakeSwapSignConfig | PancakeSwapConfig): Promise<bigint>;
5
- export declare function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken, v2RouterAddress, }: BuildSwapParams): Promise<SwapUnsignedResult>;
5
+ export declare function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken, v2RouterAddress, sellAmountOutMin, buyAmountOutMin, }: BuildSwapParams): Promise<SwapUnsignedResult>;
6
6
  export declare function calculateBuyerBudget({ buyer, quotedBNBOut, reserveGasBNB, useNativeToken, quoteToken, quoteTokenDecimals, provider, }: BuyerBudgetParams): Promise<BuyerBudget>;
7
7
  /**
8
8
  * ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化)
@@ -23,7 +23,7 @@ export async function getGasPrice(provider, config) {
23
23
  }
24
24
  return gasPrice;
25
25
  }
26
- export async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken = true, v2RouterAddress, }) {
26
+ export async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken = true, v2RouterAddress, sellAmountOutMin = 0n, buyAmountOutMin = 0n, }) {
27
27
  void tokenAddress;
28
28
  const deadline = getDeadline();
29
29
  const buyValue = useNativeToken ? buyAmountBNB + FLAT_FEE : 0n;
@@ -36,19 +36,12 @@ export async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmo
36
36
  let sellUnsigned;
37
37
  let buyUnsigned;
38
38
  if (useNativeToken) {
39
- // ✅ 原生代币模式(BNB)
40
- // 卖出:Token → WBNB → BNB
41
- sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline);
42
- // 买入:BNB → WBNB → Token
43
- buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, reversePath, buyer.address, deadline, { value: buyValue });
39
+ sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, sellAmountOutMin, v2Path, seller.address, deadline);
40
+ buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmountOutMin, reversePath, buyer.address, deadline, { value: buyValue });
44
41
  }
45
42
  else {
46
- // ✅ ERC20 模式(如 USDT)
47
- // 卖出:Token → USDT
48
- sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline);
49
- // 买入:USDT → Token
50
- buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmountBNB, // USDT 数量
51
- 0n, reversePath, buyer.address, deadline);
43
+ sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, sellAmountOutMin, v2Path, seller.address, deadline);
44
+ buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmountBNB, buyAmountOutMin, reversePath, buyer.address, deadline);
52
45
  }
53
46
  return { sellUnsigned, buyUnsigned };
54
47
  }
@@ -57,21 +50,19 @@ export async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmo
57
50
  const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
58
51
  const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
59
52
  const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
60
- // 卖出:Token → WBNB,需要 unwrapWETH9
61
53
  const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [
62
54
  {
63
55
  tokenIn: v3TokenIn,
64
56
  tokenOut: v3TokenOut,
65
57
  fee: v3Fee,
66
- recipient: PANCAKE_V3_ROUTER_ADDRESS, // 先发给 Router
58
+ recipient: PANCAKE_V3_ROUTER_ADDRESS,
67
59
  amountIn: sellAmountWei,
68
- amountOutMinimum: 0n,
60
+ amountOutMinimum: sellAmountOutMin,
69
61
  sqrtPriceLimitX96: 0n,
70
62
  },
71
63
  ]);
72
- const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
64
+ const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [sellAmountOutMin, seller.address]);
73
65
  const sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
74
- // 买入:WBNB → Token
75
66
  const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [
76
67
  {
77
68
  tokenIn: v3TokenOut,
@@ -79,7 +70,7 @@ export async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmo
79
70
  fee: v3Fee,
80
71
  recipient: buyer.address,
81
72
  amountIn: buyAmountBNB,
82
- amountOutMinimum: 0n,
73
+ amountOutMinimum: buyAmountOutMin,
83
74
  sqrtPriceLimitX96: 0n,
84
75
  },
85
76
  ]);
@@ -7,7 +7,7 @@ import { ethers, Contract, Wallet } from 'ethers';
7
7
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
8
  import { NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
9
9
  import { ERC20_ABI } from '../../../abis/common.js';
10
- import { BRIBE_GAS_LIMIT, FLAT_FEE, NATIVE_TRANSFER_GAS_LIMIT, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, ZERO_ADDRESS, buildBNBHopChainForERC20, buildERC20HopChain, buildNativeHopChain, calculateProfitAmount, createPancakeContext, generateDisperseHopPaths, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, } from './bundle-swap-helpers.js';
10
+ import { BRIBE_GAS_LIMIT, FLAT_FEE, NATIVE_TRANSFER_GAS_LIMIT, PANCAKE_V2_ROUTER_ABI, PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, PANCAKE_V3_ROUTER_ADDRESS, WBNB_ADDRESS, ZERO_ADDRESS, buildBNBHopChainForERC20, buildERC20HopChain, buildNativeHopChain, calculateProfitAmount, createPancakeContext, generateDisperseHopPaths, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, quoteSellOutput, quoteBuyOutput, applyPancakeSlippage, } from './bundle-swap-helpers.js';
11
11
  /**
12
12
  * PancakeSwap 快捷批量换手(资金自动流转)
13
13
  *
@@ -183,6 +183,15 @@ export async function pancakeQuickBatchSwapMerkle(params) {
183
183
  if (sellerBalance < sellerRequired) {
184
184
  throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
185
185
  }
186
+ const slippageBps = config.slippageBps ?? 100;
187
+ const sellMinOut = applyPancakeSlippage(estimatedOutput, slippageBps);
188
+ const buyQuotedOuts = await Promise.all(transferAmountsWei.map((amt) => quoteBuyOutput({
189
+ routeParams,
190
+ buyAmountWei: amt,
191
+ provider: context.provider,
192
+ v2RouterAddress: config.v2RouterAddress,
193
+ })));
194
+ const buyMinOuts = buyQuotedOuts.map((quoted) => applyPancakeSlippage(quoted, slippageBps));
186
195
  // ==================== 规划 Nonce ====================
187
196
  // ✅ 如果前端传入了 startNonces,直接使用(性能优化)
188
197
  let sellerNonce = startNonces && startNonces.length > 0 ? startNonces[0] : await nonceManager.getNextNonce(seller);
@@ -209,11 +218,11 @@ export async function pancakeQuickBatchSwapMerkle(params) {
209
218
  const v2RouterSeller = new Contract(quickV2Addr, PANCAKE_V2_ROUTER_ABI, seller);
210
219
  if (useNativeToken) {
211
220
  // ✅ 原生代币模式(BNB):Token → WBNB → BNB
212
- sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline);
221
+ sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, sellMinOut, v2Path, seller.address, deadline);
213
222
  }
214
223
  else {
215
224
  // ✅ ERC20 模式(USDT):Token → USDT
216
- sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline);
225
+ sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountWei, sellMinOut, v2Path, seller.address, deadline);
217
226
  }
218
227
  }
219
228
  else if (routeParams.routeType === 'v3-single') {
@@ -226,7 +235,7 @@ export async function pancakeQuickBatchSwapMerkle(params) {
226
235
  fee: v3Fee,
227
236
  recipient: useNativeToken ? PANCAKE_V3_ROUTER_ADDRESS : seller.address,
228
237
  amountIn: sellAmountWei,
229
- amountOutMinimum: 0n,
238
+ amountOutMinimum: sellMinOut,
230
239
  sqrtPriceLimitX96: 0n,
231
240
  },
232
241
  ]);
@@ -346,10 +355,10 @@ export async function pancakeQuickBatchSwapMerkle(params) {
346
355
  const reversePath = [...v2Path].reverse();
347
356
  const v2RouterBuyer = new Contract(quickV2Addr, PANCAKE_V2_ROUTER_ABI, buyer);
348
357
  if (useNativeToken) {
349
- buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, reversePath, buyer.address, deadline, { value: buyValue });
358
+ buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(buyMinOuts[i] ?? 0n, reversePath, buyer.address, deadline, { value: buyValue });
350
359
  }
351
360
  else {
352
- buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline);
361
+ buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, buyMinOuts[i] ?? 0n, reversePath, buyer.address, deadline);
353
362
  }
354
363
  }
355
364
  else if (routeParams.routeType === 'v3-single') {
@@ -362,7 +371,7 @@ export async function pancakeQuickBatchSwapMerkle(params) {
362
371
  fee: v3Fee,
363
372
  recipient: buyer.address,
364
373
  amountIn: buyAmount,
365
- amountOutMinimum: 0n,
374
+ amountOutMinimum: buyMinOuts[i] ?? 0n,
366
375
  sqrtPriceLimitX96: 0n,
367
376
  },
368
377
  ]);
@@ -6,7 +6,7 @@ import { getGasLimit } from '../../../core/gas/bundle-gas.js';
6
6
  import { ethers, Wallet } from 'ethers';
7
7
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
8
  import { NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
9
- import { BRIBE_GAS_LIMIT, ZERO_ADDRESS, buildNoncePlanFromNonces, buildSwapTransactions, WBNB_ADDRESS, calculateBuyerBudget, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, planNonces, quoteSellOutput, validateFinalBalances, } from './bundle-swap-helpers.js';
9
+ import { BRIBE_GAS_LIMIT, ZERO_ADDRESS, buildNoncePlanFromNonces, buildSwapTransactions, WBNB_ADDRESS, calculateBuyerBudget, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, grossUpPancakeSellProceedsToBuySide, planNonces, quoteSellOutput, applyPancakeSlippage, quoteBuyOutput, validateFinalBalances, } from './bundle-swap-helpers.js';
10
10
  export async function pancakeBundleSwapMerkle(params) {
11
11
  const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
12
12
  } = params;
@@ -41,6 +41,13 @@ export async function pancakeBundleSwapMerkle(params) {
41
41
  quoteTokenDecimals,
42
42
  provider: context.provider,
43
43
  });
44
+ const quotedBuyOut = await quoteBuyOutput({
45
+ routeParams,
46
+ buyAmountWei: buyerBudget.buyAmountBNB,
47
+ provider: context.provider,
48
+ v2RouterAddress: config.v2RouterAddress,
49
+ });
50
+ const slippageBps = config.slippageBps ?? 100;
44
51
  const swapUnsigned = await buildSwapTransactions({
45
52
  routeParams,
46
53
  sellAmountWei,
@@ -50,6 +57,8 @@ export async function pancakeBundleSwapMerkle(params) {
50
57
  tokenAddress,
51
58
  useNativeToken,
52
59
  v2RouterAddress: config.v2RouterAddress,
60
+ sellAmountOutMin: applyPancakeSlippage(quoteResult.estimatedBNBOut, slippageBps),
61
+ buyAmountOutMin: applyPancakeSlippage(quotedBuyOut, slippageBps),
53
62
  });
54
63
  const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
55
64
  const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
@@ -146,12 +146,10 @@ export async function directBatchBuyForSubmit(params) {
146
146
  }
147
147
  const transactions = [];
148
148
  const totalFlow = amounts.reduce((sum, amount) => sum + amount, 0n);
149
- const isBatch = privateKeys.length > 1;
150
149
  for (let i = 0; i < privateKeys.length; i++) {
151
150
  const wallet = new Wallet(privateKeys[i], provider);
152
151
  const preview = await query.previewBuy(token, amounts[i]);
153
- const effectiveSlippage = isBatch ? Math.min(9500, slippageBps + i * 2000) : slippageBps;
154
- const minOut = applySlippage(preview, effectiveSlippage);
152
+ const minOut = applySlippage(preview, slippageBps);
155
153
  const calldata = encodeBuyCall(token, minOut);
156
154
  const nonce = await wallet.getNonce('pending');
157
155
  const signedTx = await wallet.signTransaction({
@@ -201,13 +199,11 @@ export async function directBatchSellForSubmit(params) {
201
199
  const wallets = privateKeys.map((pk) => new Wallet(pk, provider));
202
200
  const transactions = [];
203
201
  const estimatedOuts = [];
204
- const isBatch = privateKeys.length > 1;
205
202
  for (let i = 0; i < privateKeys.length; i++) {
206
203
  const wallet = wallets[i];
207
204
  const preview = await query.previewSell(token, tokenAmounts[i]);
208
205
  estimatedOuts.push(preview);
209
- const effectiveSlippage = isBatch ? Math.min(9500, slippageBps + i * 2000) : slippageBps;
210
- const minOut = applySlippage(preview, effectiveSlippage);
206
+ const minOut = applySlippage(preview, slippageBps);
211
207
  const calldata = encodeSellCall(token, tokenAmounts[i], minOut);
212
208
  const nonce = await wallet.getNonce('pending');
213
209
  const signedTx = await wallet.signTransaction({
@@ -96,5 +96,5 @@ export declare function buildProfitMetadata(extractProfit: boolean, totalBuyAmou
96
96
  * 比 Promise.all 更高效,N 个报价只需 1 次网络请求
97
97
  */
98
98
  export declare function quoteSellOutputsWithQuote(portal: ethers.Contract, tokenAddress: string, amountsWei: bigint[], outputToken: string): Promise<bigint[]>;
99
- export declare function resolveMinOutputs(_provided: (string | bigint)[] | undefined, walletCount: number, _quotedOutputs: bigint[]): any[];
99
+ export declare function resolveMinOutputs(provided: (string | bigint)[] | undefined, walletCount: number, quotedOutputs: bigint[]): bigint[];
100
100
  export {};
@@ -284,7 +284,9 @@ export async function quoteSellOutputsWithQuote(portal, tokenAddress, amountsWei
284
284
  }));
285
285
  }
286
286
  }
287
- export function resolveMinOutputs(_provided, walletCount, _quotedOutputs) {
288
- // ✅ 已移除滑点保护:minOutput 固定为 0
289
- return Array(walletCount).fill(0n);
287
+ export function resolveMinOutputs(provided, walletCount, quotedOutputs) {
288
+ if (provided && provided.length === walletCount) {
289
+ return provided.map((m) => (typeof m === 'string' ? ethers.parseEther(m) : m));
290
+ }
291
+ return quotedOutputs.map((q) => (q * 9900n) / 10000n);
290
292
  }
@@ -61,7 +61,7 @@ export async function resolveSingleSellOutputs(portal, tokenAddress, amountWei,
61
61
  });
62
62
  return {
63
63
  quotedOutput,
64
- minOut: 0n, // ✅ 已移除滑点保护:minOut 固定为 0
64
+ minOut: quotedOutput > 0n ? (quotedOutput * 9900n) / 10000n : 0n,
65
65
  };
66
66
  }
67
67
  catch {
@@ -16,19 +16,18 @@ export async function flapPrivateSellMerkle(params) {
16
16
  const amountWei = ethers.parseUnits(amount, 18);
17
17
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, wallet);
18
18
  // ✅ 并行获取 gasPrice、nonce、报价和构建未签名交易
19
- const [gasPrice, currentNonce, { quotedOutput }, unsigned] = await Promise.all([
19
+ const [gasPrice, currentNonce, { quotedOutput, minOut }] = await Promise.all([
20
20
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
21
- // ✅ 使用 pending nonce,避免 xLayer 等链上“已有 pending 交易”导致 nonce too low
22
21
  provider.getTransactionCount(wallet.address, 'pending'),
23
22
  resolveSingleSellOutputs(portal, tokenAddress, amountWei, minOutputAmount),
24
- portal.swapExactInput.populateTransaction({
25
- inputToken: tokenAddress,
26
- outputToken: ZERO_ADDRESS,
27
- inputAmount: amountWei,
28
- minOutputAmount: 0n, // ✅ 已移除滑点保护
29
- permitData: '0x',
30
- }),
31
23
  ]);
24
+ const unsigned = await portal.swapExactInput.populateTransaction({
25
+ inputToken: tokenAddress,
26
+ outputToken: ZERO_ADDRESS,
27
+ inputAmount: amountWei,
28
+ minOutputAmount: minOut,
29
+ permitData: '0x',
30
+ });
32
31
  const gasLimit = resolveGasLimit(config.gasLimitMultiplier);
33
32
  const txType = getTxType(config);
34
33
  // ✅ 获取贿赂金额
@@ -103,9 +102,8 @@ export async function flapBatchPrivateSellMerkle(params) {
103
102
  const portal = new ethers.Contract(portalAddr, PORTAL_ABI, provider);
104
103
  const portals = wallets.map((w) => new ethers.Contract(portalAddr, PORTAL_ABI, w));
105
104
  // ✅ 并行获取 gasPrice、所有 nonces、所有报价和构建未签名交易
106
- const [gasPrice, initialNonces, quotedOutputs, unsignedList] = await Promise.all([
105
+ const [gasPrice, initialNonces, quotedOutputs] = await Promise.all([
107
106
  getOptimizedGasPrice(provider, getGasPriceConfig(config)),
108
- // ✅ 使用 pending nonce,避免 xLayer 等链上“已有 pending 交易”导致 nonce too low
109
107
  Promise.all(wallets.map((w) => provider.getTransactionCount(w.address, 'pending'))),
110
108
  Promise.all(amountsWei.map((amount) => portal.quoteExactInput
111
109
  .staticCall({
@@ -114,15 +112,14 @@ export async function flapBatchPrivateSellMerkle(params) {
114
112
  inputAmount: amount,
115
113
  })
116
114
  .catch(() => 0n))),
117
- // ✅ 并行构建未签名交易(已移除滑点保护:minOuts 固定为 0)
118
- Promise.all(portals.map((p, i) => p.swapExactInput.populateTransaction({
119
- inputToken: tokenAddress,
120
- outputToken: ZERO_ADDRESS,
121
- inputAmount: amountsWei[i],
122
- minOutputAmount: 0n,
123
- permitData: '0x',
124
- }))),
125
115
  ]);
116
+ const unsignedList = await Promise.all(portals.map((p, i) => p.swapExactInput.populateTransaction({
117
+ inputToken: tokenAddress,
118
+ outputToken: ZERO_ADDRESS,
119
+ inputAmount: amountsWei[i],
120
+ minOutputAmount: (quotedOutputs[i] * 9900n) / 10000n,
121
+ permitData: '0x',
122
+ })));
126
123
  const extractProfit = shouldExtractProfit();
127
124
  const { totalProfit, maxRevenueIndex } = summarizeSellProfits(quotedOutputs, extractProfit);
128
125
  // ✅ 获取贿赂金额
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "4.0.14",
3
+ "version": "4.1.0",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",