four-flap-meme-sdk 4.0.9 → 4.0.11

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.
@@ -2,7 +2,7 @@
2
2
  * Four.meme 内盘批量换手(Merkle Bundle)— 一卖多买流程
3
3
  */
4
4
  import { ethers, Contract, Wallet } from 'ethers';
5
- import { countSellerNonceSlots, createFourSwapGasContext, fetchFourSwapSellQuoteAllowanceAndBalances, needsTmTokenApproval, planSellerNonces, signTmMaxApprovalTx, } from '../swap-sign-helpers.js';
5
+ import { countSellerNonceSlots, createFourSwapGasContext, fetchFourSwapSellQuoteAllowanceAndBalances, grossUpFourSellProceedsToBuySide, needsTmTokenApproval, planSellerNonces, signTmMaxApprovalTx, } from '../swap-sign-helpers.js';
6
6
  import { signBribeTransaction } from '../../../core/sign-context-helpers.js';
7
7
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
8
  import { NonceManager, getOptimizedGasPrice, getGasPriceConfig, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
@@ -58,9 +58,8 @@ export async function fourBatchSwapMerkle(params) {
58
58
  });
59
59
  // 检查是否需要授权
60
60
  const needApproval = needsTmTokenApproval(currentAllowance, decimals);
61
- // 利润配置
62
- const buySum = buyAmountsWei.reduce((a, b) => a + b, 0n);
63
- const profitAmount = totalBuyerFunds > 0n ? calculateBundleSwapRoundTripProfit(buySum, totalBuyerFunds) : 0n;
61
+ const profitNotional = await grossUpFourSellProceedsToBuySide(provider, tokenAddress, sellAmountWei, totalBuyerFunds);
62
+ const profitAmount = profitNotional > 0n ? calculateBundleSwapRoundTripProfit(profitNotional, profitNotional) : 0n;
64
63
  // ✅ 获取贿赂金额
65
64
  const bribeAmount = getBribeAmount(config);
66
65
  const needBribeTx = bribeAmount > 0n;
@@ -2,7 +2,7 @@
2
2
  * Four.meme 内盘捆绑换手(Merkle Bundle)— 单卖单买流程
3
3
  */
4
4
  import { ethers, Contract, Wallet } from 'ethers';
5
- import { countSellerNonceSlots, createFourSwapGasContext, fetchFourSwapSellQuoteAndAllowance, needsTmTokenApproval, planSellerNonces, signTmMaxApprovalTx, } from '../swap-sign-helpers.js';
5
+ import { countSellerNonceSlots, createFourSwapGasContext, fetchFourSwapSellQuoteAndAllowance, grossUpFourSellProceedsToBuySide, needsTmTokenApproval, planSellerNonces, signTmMaxApprovalTx, } from '../swap-sign-helpers.js';
6
6
  import { signBribeTransaction } from '../../../core/sign-context-helpers.js';
7
7
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
8
8
  import { NonceManager, getOptimizedGasPrice, getGasPriceConfig, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
@@ -40,10 +40,11 @@ export async function fourBundleSwapMerkle(params) {
40
40
  }
41
41
  // 检查是否需要授权
42
42
  const needApproval = needsTmTokenApproval(currentAllowance, decimals);
43
- // 利润配置
43
+ // 利润配置:按卖出代币的买侧计价收万六,避免曲线折价后只剩万五点多
44
44
  const extractProfit = true;
45
- const profitAmount = extractProfit && sellerWillGetBNB > 0n
46
- ? calculateBundleSwapRoundTripProfit(buyerFunds, sellerWillGetBNB)
45
+ const profitNotional = await grossUpFourSellProceedsToBuySide(provider, tokenAddress, sellAmountWei, sellerWillGetBNB);
46
+ const profitAmount = extractProfit && profitNotional > 0n
47
+ ? calculateBundleSwapRoundTripProfit(profitNotional, profitNotional)
47
48
  : 0n;
48
49
  // ✅ 获取贿赂金额
49
50
  const bribeAmount = getBribeAmount(config);
@@ -2,7 +2,7 @@
2
2
  * Four.meme 内盘快捷批量换手(Merkle Bundle)— 资金利用率模式流程
3
3
  */
4
4
  import { ethers, Contract, Wallet } from 'ethers';
5
- import { createFourSwapGasContext, quoteFourSwapSellFunds, } from '../swap-sign-helpers.js';
5
+ import { createFourSwapGasContext, grossUpFourSellProceedsToBuySide, quoteFourSwapSellFunds, } from '../swap-sign-helpers.js';
6
6
  import { generateDisperseHopPaths, buildNativeHopChain, NATIVE_TRANSFER_GAS_LIMIT } from '../swap-hop-helpers.js';
7
7
  import { signBribeTransaction } from '../../../core/sign-context-helpers.js';
8
8
  import { calculateSellAmount } from '../../../utils/swap-helpers.js';
@@ -51,8 +51,8 @@ export async function fourQuickBatchSwapMerkle(params) {
51
51
  ]);
52
52
  const { amount: sellAmountWei, decimals } = sellAmountResult;
53
53
  const estimatedBNBOut = await quoteFourSwapSellFunds(provider, tokenAddress, sellAmountWei);
54
- // 计算利润(根据 userType 动态调整)
55
- const profitAmount = estimatedBNBOut > 0n ? calculateBundleSwapRoundTripProfit(estimatedBNBOut, estimatedBNBOut) : 0n;
54
+ const profitNotional = await grossUpFourSellProceedsToBuySide(provider, tokenAddress, sellAmountWei, estimatedBNBOut);
55
+ const profitAmount = profitNotional > 0n ? calculateBundleSwapRoundTripProfit(profitNotional, profitNotional) : 0n;
56
56
  const distributableAmount = estimatedBNBOut - profitAmount;
57
57
  // ✅ 计算每个买方分到的金额
58
58
  let transferAmountsWei;
@@ -8,6 +8,8 @@ export declare function countSellerNonceSlots(needBribeTx: boolean, needApproval
8
8
  export declare function planSellerNonces(sellerNonces: number[], needBribeTx: boolean, needApproval: boolean, extractProfit: boolean): SellerNoncePlan;
9
9
  export declare function signTmMaxApprovalTx(seller: Wallet, tokenAddress: string, approvalNonce: number, gasPrice: bigint, chainIdNum: number, txType: 0 | 2): Promise<string>;
10
10
  export declare function quoteFourSwapSellFunds(provider: JsonRpcProvider, tokenAddress: string, sellAmountWei: bigint): Promise<bigint>;
11
+ /** 换手按卖出所得收万六会像万五点多:用所得能买回的数量把计价额抬到买侧。 */
12
+ export declare function grossUpFourSellProceedsToBuySide(provider: JsonRpcProvider, tokenAddress: string, sellAmountWei: bigint, sellProceedsWei: bigint): Promise<bigint>;
11
13
  export declare function fetchFourSwapSellQuoteAndAllowance(provider: JsonRpcProvider, tokenAddress: string, sellerAddress: string, sellAmountWei: bigint): Promise<{
12
14
  sellFunds: bigint;
13
15
  allowance: bigint;
@@ -54,6 +54,22 @@ export async function quoteFourSwapSellFunds(provider, tokenAddress, sellAmountW
54
54
  const sellQuote = await helper3.trySell(tokenAddress, sellAmountWei);
55
55
  return sellQuote.funds;
56
56
  }
57
+ /** 换手按卖出所得收万六会像万五点多:用所得能买回的数量把计价额抬到买侧。 */
58
+ export async function grossUpFourSellProceedsToBuySide(provider, tokenAddress, sellAmountWei, sellProceedsWei) {
59
+ if (sellProceedsWei <= 0n || sellAmountWei <= 0n)
60
+ return sellProceedsWei;
61
+ try {
62
+ const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
63
+ const buyQuote = await helper3.tryBuy(tokenAddress, 0n, sellProceedsWei);
64
+ const tokensBought = buyQuote.estimatedAmount;
65
+ if (tokensBought <= 0n || tokensBought >= sellAmountWei)
66
+ return sellProceedsWei;
67
+ return (sellProceedsWei * sellAmountWei) / tokensBought;
68
+ }
69
+ catch {
70
+ return sellProceedsWei;
71
+ }
72
+ }
57
73
  export async function fetchFourSwapSellQuoteAndAllowance(provider, tokenAddress, sellerAddress, sellAmountWei) {
58
74
  const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
59
75
  const erc20 = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
@@ -6,6 +6,17 @@ export declare function quoteSellOutput({ routeParams, sellAmountWei, provider,
6
6
  */
7
7
  export declare function getProfitRateBps(_userType?: UserType): number;
8
8
  export declare function calculateProfitAmount(estimatedBNBOut: bigint, _userType?: UserType): bigint;
9
+ /** 换手按卖出所得收万六会像万五点多:用所得能买回的数量把计价额抬到买侧。 */
10
+ export declare function grossUpPancakeSellProceedsToBuySide(params: {
11
+ provider: ethers.JsonRpcProvider;
12
+ tokenAddress: string;
13
+ sellAmountWei: bigint;
14
+ sellProceedsWei: bigint;
15
+ quoteTokenIn: string;
16
+ version: 'v2' | 'v3';
17
+ fee?: number;
18
+ v2RouterAddress?: string;
19
+ }): Promise<bigint>;
9
20
  /**
10
21
  * ✅ 获取 ERC20 代币 → 原生代币(BNB)的报价
11
22
  * 用于将 ERC20 利润转换为 BNB
@@ -50,6 +50,24 @@ export function calculateProfitAmount(estimatedBNBOut, _userType) {
50
50
  }
51
51
  return calculateBundleSwapRoundTripProfit(estimatedBNBOut, estimatedBNBOut);
52
52
  }
53
+ /** 换手按卖出所得收万六会像万五点多:用所得能买回的数量把计价额抬到买侧。 */
54
+ export async function grossUpPancakeSellProceedsToBuySide(params) {
55
+ const { sellAmountWei, sellProceedsWei } = params;
56
+ if (sellProceedsWei <= 0n || sellAmountWei <= 0n)
57
+ return sellProceedsWei;
58
+ try {
59
+ const quoted = params.version === 'v3'
60
+ ? await quoteV3(params.provider, params.quoteTokenIn, params.tokenAddress, sellProceedsWei, 'BSC', params.fee)
61
+ : await quoteV2(params.provider, params.quoteTokenIn, params.tokenAddress, sellProceedsWei, 'BSC', params.v2RouterAddress);
62
+ const tokensBought = quoted.amountOut;
63
+ if (tokensBought <= 0n || tokensBought >= sellAmountWei)
64
+ return sellProceedsWei;
65
+ return (sellProceedsWei * sellAmountWei) / tokensBought;
66
+ }
67
+ catch {
68
+ return sellProceedsWei;
69
+ }
70
+ }
53
71
  /**
54
72
  * ✅ 获取 ERC20 代币 → 原生代币(BNB)的报价
55
73
  * 用于将 ERC20 利润转换为 BNB
@@ -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, ZERO_ADDRESS, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, 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, } from './bundle-swap-helpers.js';
10
10
  /**
11
11
  * PancakeSwap 批量换手(一卖多买)
12
12
  *
@@ -203,18 +203,25 @@ export async function pancakeBatchSwapMerkle(params) {
203
203
  type: txType,
204
204
  });
205
205
  }
206
- // 修复:利润计算应基于 BNB 数量,不是 ERC20 数量
206
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
207
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
208
+ const profitNotional = await grossUpPancakeSellProceedsToBuySide({
209
+ provider: context.provider,
210
+ tokenAddress,
211
+ sellAmountWei,
212
+ sellProceedsWei: estimatedBNBOut,
213
+ quoteTokenIn: useNativeToken ? WBNB_ADDRESS : quoteToken,
214
+ version,
215
+ fee,
216
+ v2RouterAddress: config.v2RouterAddress,
217
+ });
207
218
  let profitAmount;
208
219
  if (useNativeToken) {
209
- profitAmount = calculateProfitAmount(estimatedBNBOut, config.userType);
210
- // 利润计算完成
220
+ profitAmount = calculateProfitAmount(profitNotional, config.userType);
211
221
  }
212
222
  else {
213
- const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
214
- const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
215
- const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedBNBOut, version, fee);
223
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, profitNotional, version, fee);
216
224
  profitAmount = calculateProfitAmount(estimatedBNBValue, config.userType);
217
- // ERC20→BNB 报价完成
218
225
  }
219
226
  // 计算利润 nonce
220
227
  const profitNonce = profitAmount > 0n
@@ -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, ZERO_ADDRESS, buildBNBHopChainForERC20, buildERC20HopChain, buildNativeHopChain, calculateProfitAmount, createPancakeContext, generateDisperseHopPaths, getERC20ToNativeQuote, getGasPrice, 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, } from './bundle-swap-helpers.js';
11
11
  /**
12
12
  * PancakeSwap 快捷批量换手(资金自动流转)
13
13
  *
@@ -120,16 +120,24 @@ export async function pancakeQuickBatchSwapMerkle(params) {
120
120
  v2RouterAddress: config.v2RouterAddress,
121
121
  });
122
122
  const estimatedOutput = quoteResult.estimatedBNBOut;
123
- // 计算利润(万分之六)
123
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
124
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
125
+ const profitNotional = await grossUpPancakeSellProceedsToBuySide({
126
+ provider: context.provider,
127
+ tokenAddress,
128
+ sellAmountWei,
129
+ sellProceedsWei: estimatedOutput,
130
+ quoteTokenIn: useNativeToken ? WBNB_ADDRESS : quoteToken,
131
+ version,
132
+ fee,
133
+ v2RouterAddress: config.v2RouterAddress,
134
+ });
124
135
  let profitAmount;
125
136
  if (useNativeToken) {
126
- profitAmount = calculateProfitAmount(estimatedOutput, config.userType);
137
+ profitAmount = calculateProfitAmount(profitNotional, config.userType);
127
138
  }
128
139
  else {
129
- // ERC20 模式:需要将 ERC20 价值转换为 BNB
130
- const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
131
- const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
132
- const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedOutput, version, fee);
140
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, profitNotional, version, fee);
133
141
  profitAmount = calculateProfitAmount(estimatedBNBValue, config.userType);
134
142
  }
135
143
  const distributableAmount = estimatedOutput - (useNativeToken ? profitAmount : 0n);
@@ -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, calculateBuyerBudget, calculateProfitAmount, createPancakeContext, getERC20ToNativeQuote, getGasPrice, 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, 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;
@@ -51,23 +51,25 @@ export async function pancakeBundleSwapMerkle(params) {
51
51
  useNativeToken,
52
52
  v2RouterAddress: config.v2RouterAddress,
53
53
  });
54
- // 修复:利润计算应基于 BNB 数量,不是 ERC20 数量
55
- // 如果输出是原生代币(BNB),直接使用报价结果
56
- // 如果输出是 ERC20(如 USDT),需要先转换为 BNB 等值
54
+ const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
55
+ const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
56
+ const profitNotional = await grossUpPancakeSellProceedsToBuySide({
57
+ provider: context.provider,
58
+ tokenAddress,
59
+ sellAmountWei,
60
+ sellProceedsWei: quoteResult.estimatedBNBOut,
61
+ quoteTokenIn: useNativeToken ? WBNB_ADDRESS : quoteToken,
62
+ version,
63
+ fee,
64
+ v2RouterAddress: config.v2RouterAddress,
65
+ });
57
66
  let profitAmount;
58
67
  if (useNativeToken) {
59
- // 输出是 BNB,直接计算利润(根据 userType 动态调整)
60
- profitAmount = calculateProfitAmount(quoteResult.estimatedBNBOut, config.userType);
61
- // 利润计算完成
68
+ profitAmount = calculateProfitAmount(profitNotional, config.userType);
62
69
  }
63
70
  else {
64
- // 输出是 ERC20,需要先获取 ERC20 BNB 的报价
65
- const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
66
- const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
67
- const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, quoteResult.estimatedBNBOut, // 这实际上是 ERC20 数量
68
- version, fee);
71
+ const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, profitNotional, version, fee);
69
72
  profitAmount = calculateProfitAmount(estimatedBNBValue, config.userType);
70
- // ERC20→BNB 报价完成
71
73
  }
72
74
  // ✅ 获取贿赂金额
73
75
  const bribeAmount = config.bribeAmount && config.bribeAmount > 0 ? ethers.parseEther(String(config.bribeAmount)) : 0n;
@@ -59,12 +59,18 @@ export async function quoteV2(provider, tokenIn, tokenOut, amountIn, chain, rout
59
59
  }
60
60
  });
61
61
  const results = await Promise.all(quotePromises);
62
- // 找到第一个成功的结果(优先直接路径)
62
+ // 取报价最高的路径:薄直连池(如 ANTFUN/WBNB)会先成功但少报,利润看起来不像万六
63
+ let best;
63
64
  for (const result of results) {
64
- if (result.success) {
65
- return { amountOut: result.amountOut, path: result.path };
65
+ if (!result.success)
66
+ continue;
67
+ if (!best || result.amountOut > best.amountOut) {
68
+ best = { amountOut: result.amountOut, path: result.path };
66
69
  }
67
70
  }
71
+ if (best) {
72
+ return { amountOut: best.amountOut, path: best.path };
73
+ }
68
74
  return { amountOut: 0n };
69
75
  }
70
76
  // ============================================================================
@@ -260,11 +266,8 @@ export async function quoteRoundTripProfitToNative(provider, quoteToken, buyQuot
260
266
  };
261
267
  const buyNative = await quoteLeg(buyQuoteWei);
262
268
  const sellNative = sellQuoteWei === buyQuoteWei ? buyNative : await quoteLeg(sellQuoteWei);
263
- const profit = calculateBundleSwapRoundTripProfit(buyNative, sellNative);
264
- if (profit <= 0n && (buyQuoteWei > 0n || sellQuoteWei > 0n)) {
265
- throw new Error('计价币利润折成原生币报价为 0,请检查计价币流动性后重试');
266
- }
267
- return profit;
269
+ const notional = buyNative > sellNative ? buyNative : sellNative;
270
+ return calculateBundleSwapRoundTripProfit(notional, notional);
268
271
  }
269
272
  export async function quoteRoundTripProfitToNativeByChainId(provider, quoteToken, buyQuoteWei, sellQuoteWei, chainId) {
270
273
  const chain = getQuoteChainKey(chainId);
@@ -10,7 +10,7 @@ import { quoteRoundTripProfitToNativeByChainId } from '../../../core/quote/quote
10
10
  import { getGasPriceConfig, getTxType, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI, } from './config.js';
11
11
  import { buildTransactionRequest } from '../../../core/tx/build-request.js';
12
12
  import { BRIBE_GAS_LIMIT, ERC20_BALANCE_OF_ABI, } from './swap-helpers.js';
13
- import { buildApprovalTransaction, buildProfitTransaction, calculateProfitAmount, createChainContext, quoteSellOutput, } from './swap-planning.js';
13
+ import { buildApprovalTransaction, buildProfitTransaction, calculateProfitAmount, createChainContext, grossUpSellProceedsToBuySide, quoteSellOutput, } from './swap-planning.js';
14
14
  /**
15
15
  * Flap 内盘批量换手(一卖多买)
16
16
  *
@@ -100,13 +100,21 @@ export async function flapBatchSwapMerkle(params) {
100
100
  }
101
101
  }
102
102
  }));
103
- // ✅ 计算利润。ERC-20 先把卖出合计折成原生币再计提万六,避免 ANTFUN 微额报价为 0
103
+ // ✅ 计算利润。换手按卖出代币的买侧计价收万六,避免曲线折价后只剩万五点多
104
+ const profitNotional = await grossUpSellProceedsToBuySide({
105
+ provider: chainContext.provider,
106
+ portalAddress: chainContext.portalAddress,
107
+ quoteToken: outputToken,
108
+ tokenAddress,
109
+ sellAmountWei,
110
+ sellProceedsWei: quote.quotedNative,
111
+ });
104
112
  let nativeProfitAmount = 0n;
105
113
  if (useNativeToken) {
106
- nativeProfitAmount = calculateProfitAmount(quote.quotedNative, config.userType);
114
+ nativeProfitAmount = calculateProfitAmount(profitNotional, config.userType);
107
115
  }
108
- else if (quote.quotedNative > 0n && quoteToken) {
109
- nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, quoteToken, quote.quotedNative, quote.quotedNative, chainContext.chainId);
116
+ else if (profitNotional > 0n && quoteToken) {
117
+ nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, quoteToken, profitNotional, profitNotional, chainContext.chainId);
110
118
  }
111
119
  // ✅ 并行构建所有买入交易
112
120
  const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
@@ -11,7 +11,7 @@ import { BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI, getBribeAmount, getGasPriceConfig,
11
11
  import { buildTransactionRequest } from '../../../core/tx/build-request.js';
12
12
  import { splitAmountByWeight as splitAmount } from '../../../core/amount/split-by-weight.js';
13
13
  import { createChainContext } from './swap-buy-first-chain.js';
14
- import { quoteBuyerOutput, estimateSellFunds } from './swap-buy-first-quote.js';
14
+ import { quoteBuyerOutput } from './swap-buy-first-quote.js';
15
15
  import { validateSwapCounts } from './swap-buy-first-plan.js';
16
16
  import { buildProfitTransaction } from './swap-buy-first-profit.js';
17
17
  /**
@@ -88,12 +88,10 @@ export async function flapBundleBuyFirstMultiWallet(params) {
88
88
  const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
89
89
  // ✅ 将代币平均分配给卖方
90
90
  const sellAmountsWei = splitAmount(quoteResult.sellAmountWei, sellCount);
91
- // ✅ 第二批并行:估算利润 + ERC20 转原生代币报价(如需要)
92
- const portal = new Contract(chainContext.portalAddress, PORTAL_ABI, chainContext.provider);
93
- const estimatedSellFunds = await estimateSellFunds(portal, tokenAddress, quoteResult.sellAmountWei, outputToken);
91
+ // ✅ 第二批:ERC20 转原生代币报价(如需要)
94
92
  let nativeProfitAmount = 0n;
95
93
  if (useNativeToken) {
96
- nativeProfitAmount = calculateBundleSwapRoundTripProfit(totalFundsWei, estimatedSellFunds > 0n ? estimatedSellFunds : totalFundsWei);
94
+ nativeProfitAmount = calculateBundleSwapRoundTripProfit(totalFundsWei, totalFundsWei);
97
95
  }
98
96
  else if (totalFundsWei > 0n) {
99
97
  nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, inputToken, totalFundsWei, totalFundsWei, chainContext.chainId);
@@ -9,7 +9,7 @@ import { ZERO_ADDRESS, GAS_LIMITS, calculateBundleSwapRoundTripProfit } from '..
9
9
  import { getGasPriceConfig, getTxType, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI, } from './config.js';
10
10
  import { buildTransactionRequest } from '../../../core/tx/build-request.js';
11
11
  import { splitAmountByWeight as splitAmount } from '../../../core/amount/split-by-weight.js';
12
- import { validateSwapCounts, calculateBuyerFunds, quoteBuyerOutput, ensureSellerBalance, estimateSellFunds, planMultiNonces, buildProfitTransaction, createChainContext, flapBundleBuyFirstMultiWallet, } from './swap-buy-first-helpers.js';
12
+ import { validateSwapCounts, calculateBuyerFunds, quoteBuyerOutput, ensureSellerBalance, planMultiNonces, buildProfitTransaction, createChainContext, flapBundleBuyFirstMultiWallet, } from './swap-buy-first-helpers.js';
13
13
  export async function flapBundleBuyFirstMerkle(params) {
14
14
  const { chain, buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount, } = params;
15
15
  // ✅ 判断是否为多钱包模式
@@ -86,7 +86,7 @@ export async function flapBundleBuyFirstMerkle(params) {
86
86
  // ✅ 拆分买入和卖出金额
87
87
  const buyAmountsWei = splitAmount(buyerFundsWei, buyCount);
88
88
  const sellAmountsWei = splitAmount(sellAmountWei, sellCount);
89
- // ✅ 第三批并行 - 构建多笔买入和卖出交易、estimatedSellFunds
89
+ // ✅ 第三批并行 - 构建多笔买入和卖出交易
90
90
  const buyUnsignedPromises = buyAmountsWei.map((amount) => portalBuyer.swapExactInput.populateTransaction({
91
91
  inputToken,
92
92
  outputToken: tokenAddress,
@@ -101,10 +101,9 @@ export async function flapBundleBuyFirstMerkle(params) {
101
101
  minOutputAmount: 0,
102
102
  permitData: '0x',
103
103
  }));
104
- const [buyUnsignedArray, sellUnsignedArray, estimatedSellFunds] = await Promise.all([
104
+ const [buyUnsignedArray, sellUnsignedArray] = await Promise.all([
105
105
  Promise.all(buyUnsignedPromises),
106
106
  Promise.all(sellUnsignedPromises),
107
- estimateSellFunds(portalSeller, tokenAddress, sellAmountWei, outputToken),
108
107
  ]);
109
108
  // ✅ 获取贿赂金额
110
109
  const bribeAmount = getBribeAmount(config);
@@ -123,7 +122,7 @@ export async function flapBundleBuyFirstMerkle(params) {
123
122
  // 买合计万三 + 卖合计万三。ERC-20 先把买入合计折成原生币再计提,避免 ANTFUN 微额报价为 0
124
123
  let nativeProfitAmount = 0n;
125
124
  if (useNativeToken) {
126
- nativeProfitAmount = calculateBundleSwapRoundTripProfit(buyerFundsWei, estimatedSellFunds > 0n ? estimatedSellFunds : buyerFundsWei);
125
+ nativeProfitAmount = calculateBundleSwapRoundTripProfit(buyerFundsWei, buyerFundsWei);
127
126
  }
128
127
  else if (buyerFundsWei > 0n) {
129
128
  nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, inputToken, buyerFundsWei, buyerFundsWei, chainContext.chainId);
@@ -22,6 +22,15 @@ type QuoteSellParams = {
22
22
  outputToken?: string;
23
23
  };
24
24
  export declare function quoteSellOutput({ portalAddress, tokenAddress, sellAmountWei, provider, skipQuoteOnError, outputToken, }: QuoteSellParams): Promise<QuoteResult>;
25
+ /** 换手按卖出所得收万六会像万五点多:卖出折价后小于买同等代币的花费。按卖出代币的买侧计价抬到买侧。 */
26
+ export declare function grossUpSellProceedsToBuySide(params: {
27
+ provider: ethers.JsonRpcProvider;
28
+ portalAddress: string;
29
+ quoteToken: string;
30
+ tokenAddress: string;
31
+ sellAmountWei: bigint;
32
+ sellProceedsWei: bigint;
33
+ }): Promise<bigint>;
25
34
  type BuyerNeedParams = {
26
35
  buyer: Wallet;
27
36
  quotedNative: bigint;
@@ -65,6 +65,26 @@ export async function quoteSellOutput({ portalAddress, tokenAddress, sellAmountW
65
65
  throw new Error(`卖出报价失败: ${getErrorMessageFromUnknown(err)}`, { cause: err });
66
66
  }
67
67
  }
68
+ /** 换手按卖出所得收万六会像万五点多:卖出折价后小于买同等代币的花费。按卖出代币的买侧计价抬到买侧。 */
69
+ export async function grossUpSellProceedsToBuySide(params) {
70
+ const { sellAmountWei, sellProceedsWei } = params;
71
+ if (sellProceedsWei <= 0n || sellAmountWei <= 0n)
72
+ return sellProceedsWei;
73
+ try {
74
+ const portal = new Contract(params.portalAddress, PORTAL_ABI, params.provider);
75
+ const tokensBought = (await portal.quoteExactInput.staticCall({
76
+ inputToken: params.quoteToken,
77
+ outputToken: params.tokenAddress,
78
+ inputAmount: sellProceedsWei,
79
+ }));
80
+ if (tokensBought <= 0n || tokensBought >= sellAmountWei)
81
+ return sellProceedsWei;
82
+ return (sellProceedsWei * sellAmountWei) / tokensBought;
83
+ }
84
+ catch {
85
+ return sellProceedsWei;
86
+ }
87
+ }
68
88
  export async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider, }) {
69
89
  const reserveGas = ethers.parseEther((reserveGasEth || 0.0005).toString());
70
90
  let buyerBalance;
@@ -12,7 +12,7 @@ import { getGasPriceConfig, getTxType, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, P
12
12
  import { buildTransactionRequest } from '../../../core/tx/build-request.js';
13
13
  import { GAS_LIMITS } from '../../../core/constants/index.js';
14
14
  import { BRIBE_GAS_LIMIT, NATIVE_TRANSFER_GAS_LIMIT, buildBNBHopChainForERC20, buildERC20HopChain, buildNativeHopChain, generateDisperseHopPaths, } from './swap-helpers.js';
15
- import { buildProfitTransaction, calculateProfitAmount, createChainContext, quoteSellOutput, } from './swap-planning.js';
15
+ import { buildProfitTransaction, calculateProfitAmount, createChainContext, grossUpSellProceedsToBuySide, quoteSellOutput, } from './swap-planning.js';
16
16
  /**
17
17
  * Flap 内盘快捷批量换手(资金利用率模式)
18
18
  *
@@ -89,11 +89,19 @@ export async function flapQuickBatchSwapMerkle(params) {
89
89
  outputToken,
90
90
  });
91
91
  const estimatedOutput = quote.quotedNative;
92
- // 计算利润(根据 userType 动态调整)
93
- const tokenProfitAmount = calculateProfitAmount(estimatedOutput, config.userType);
92
+ const profitNotional = await grossUpSellProceedsToBuySide({
93
+ provider: chainContext.provider,
94
+ portalAddress: chainContext.portalAddress,
95
+ quoteToken: outputToken,
96
+ tokenAddress,
97
+ sellAmountWei,
98
+ sellProceedsWei: estimatedOutput,
99
+ });
100
+ // ✅ 计算利润。换手按卖出代币的买侧计价收万六;原生币从卖出所得预留,ERC-20 利润 hop 另付
101
+ const tokenProfitAmount = calculateProfitAmount(profitNotional, config.userType);
94
102
  let nativeProfitAmount = tokenProfitAmount;
95
- if (!useNativeToken && estimatedOutput > 0n && quoteToken) {
96
- nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, quoteToken, estimatedOutput, estimatedOutput, chainContext.chainId);
103
+ if (!useNativeToken && profitNotional > 0n && quoteToken) {
104
+ nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, quoteToken, profitNotional, profitNotional, chainContext.chainId);
97
105
  }
98
106
  const distributableAmount = useNativeToken ? estimatedOutput - tokenProfitAmount : estimatedOutput; // ERC20 模式利润从原生代币扣
99
107
  // ✅ 计算每个买方分到的金额
@@ -10,7 +10,7 @@ import { quoteRoundTripProfitToNativeByChainId } from '../../../core/quote/quote
10
10
  import { getGasPriceConfig, getTxType, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI, } from './config.js';
11
11
  import { buildTransactionRequest } from '../../../core/tx/build-request.js';
12
12
  import { BRIBE_GAS_LIMIT, } from './swap-helpers.js';
13
- import { buildApprovalTransaction, buildNoncePlan, buildProfitTransaction, calculateBuyerNeed, calculateProfitAmount, createChainContext, planNonces, quoteSellOutput, validateBalances, } from './swap-planning.js';
13
+ import { buildApprovalTransaction, buildNoncePlan, buildProfitTransaction, calculateBuyerNeed, calculateProfitAmount, createChainContext, grossUpSellProceedsToBuySide, planNonces, quoteSellOutput, validateBalances, } from './swap-planning.js';
14
14
  export async function flapBundleSwapMerkle(params) {
15
15
  const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config, quoteToken, quoteTokenDecimals, startNonces, // ✅ 可选:前端预获取的 nonces
16
16
  } = params;
@@ -63,13 +63,21 @@ export async function flapBundleSwapMerkle(params) {
63
63
  quoteTokenDecimals,
64
64
  provider: chainContext.provider,
65
65
  });
66
- // 计算利润。ERC-20 先把卖出合计折成原生币再计提万六,避免 ANTFUN 微额报价为 0
66
+ const profitNotional = await grossUpSellProceedsToBuySide({
67
+ provider: chainContext.provider,
68
+ portalAddress: chainContext.portalAddress,
69
+ quoteToken: outputToken,
70
+ tokenAddress,
71
+ sellAmountWei,
72
+ sellProceedsWei: quote.quotedNative,
73
+ });
74
+ // ✅ 计算利润。换手按卖出代币的买侧计价收万六,避免曲线折价后只剩万五点多
67
75
  let nativeProfitAmount = 0n;
68
76
  if (useNativeToken) {
69
- nativeProfitAmount = calculateProfitAmount(quote.quotedNative, config.userType);
77
+ nativeProfitAmount = calculateProfitAmount(profitNotional, config.userType);
70
78
  }
71
- else if (quote.quotedNative > 0n && quoteToken) {
72
- nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, quoteToken, quote.quotedNative, quote.quotedNative, chainContext.chainId);
79
+ else if (profitNotional > 0n && quoteToken) {
80
+ nativeProfitAmount = await quoteRoundTripProfitToNativeByChainId(chainContext.provider, quoteToken, profitNotional, profitNotional, chainContext.chainId);
73
81
  }
74
82
  // ✅ 获取贿赂金额
75
83
  const bribeAmount = getBribeAmount(config);
@@ -8,6 +8,7 @@ import { Contract, formatEther } from 'ethers';
8
8
  import { ADDRESSES, ZERO_ADDRESS } from '../../core/constants/index.js';
9
9
  import { Helper3 } from '../four/contracts/helper3.js';
10
10
  import { FlapPortal } from '../flap/portal.js';
11
+ import { getTaxTokenInfo, isTaxToken } from '../four/tax-token.js';
11
12
  import { DEFAULT_V3_FEE_TIERS } from './lp-inspect-helpers.js';
12
13
  import { getChainConfig } from './registry.js';
13
14
  import { determinePlatform, formatBalance, getProvider, getTokenDecimals, normalizeLiquidity, queryV2Pairs, queryV3Pools, } from './inspect-helpers.js';
@@ -15,6 +16,12 @@ import { determinePlatform, formatBalance, getProvider, getTokenDecimals, normal
15
16
  // 常量(从公共模块导入)
16
17
  // ============================================================================
17
18
  const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
19
+ /** BSC V8 买卖税;status=0 说明不是 Flap 币,slot 里常见残留 100 bps(1%)不能当当前税。 */
20
+ function flapV8TaxBps(st) {
21
+ if (!Number(st.status))
22
+ return 0;
23
+ return Math.max(Number(st.buyTaxRate ?? 0n), Number(st.sellTaxRate ?? 0n));
24
+ }
18
25
  // ============================================================================
19
26
  // 主查询函数
20
27
  // ============================================================================
@@ -64,24 +71,25 @@ export async function inspectTokenLP(token, opts) {
64
71
  if (opts.flapChain) {
65
72
  try {
66
73
  const flap = new FlapPortal({ chain: opts.flapChain, rpcUrl: opts.rpcUrl });
67
- // ✅ 优先尝试 getTokenV7(包含 taxRate),失败则 fallback 到 getTokenV5
68
74
  let st = null;
69
75
  let taxRate;
70
- if (opts.flapChain === 'BSC') {
76
+ const useBscV8 = opts.flapChain === 'BSC';
77
+ if (useBscV8) {
71
78
  try {
72
79
  const stV8 = await flap.getTokenV8Safe(token);
73
80
  st = stV8;
74
- taxRate = Math.max(Number(stV8.buyTaxRate ?? 0n), Number(stV8.sellTaxRate ?? 0n));
81
+ taxRate = flapV8TaxBps(stV8);
75
82
  }
76
83
  catch {
77
- // V8 不可用时走 V7
84
+ // V8 不可用时只借 V7/V5 读盘面,不用 V7 残留 taxRate(常见 100 bps)
78
85
  }
79
86
  }
80
87
  if (!st) {
81
88
  try {
82
89
  const stV7 = await flap.getTokenV7(token);
83
90
  st = stV7;
84
- taxRate = Number(stV7.taxRate ?? 0n);
91
+ if (!useBscV8)
92
+ taxRate = Number(stV7.taxRate ?? 0n);
85
93
  }
86
94
  catch {
87
95
  st = await flap.getTokenV5(token);
@@ -190,6 +198,17 @@ export async function inspectTokenLP(token, opts) {
190
198
  const offerTokens = formatBalance(info.offers, shouldFormat);
191
199
  const lastPrice = formatBalance(info.lastPrice, shouldFormat);
192
200
  result.four = { helper: ADDRESSES.BSC.TokenManagerHelper3, reserveNative, offerTokens, lastPrice };
201
+ try {
202
+ if (await isTaxToken(token, opts.rpcUrl)) {
203
+ const fee = Number((await getTaxTokenInfo(token, opts.rpcUrl)).config.feeRate);
204
+ const taxRate = fee > 0 && fee <= 10 ? fee * 100 : fee;
205
+ if (taxRate > 0)
206
+ result.taxRate = taxRate;
207
+ }
208
+ }
209
+ catch {
210
+ // 非 TaxToken 或读费率失败
211
+ }
193
212
  return result;
194
213
  }
195
214
  }
@@ -331,14 +350,8 @@ export async function inspectTokenLP(token, opts) {
331
350
  const flap = new FlapPortal({ chain: opts.flapChain, rpcUrl: opts.rpcUrl });
332
351
  let taxRate = 0;
333
352
  if (opts.flapChain === 'BSC') {
334
- try {
335
- const stV8 = await flap.getTokenV8Safe(token);
336
- taxRate = Math.max(Number(stV8.buyTaxRate ?? 0n), Number(stV8.sellTaxRate ?? 0n));
337
- }
338
- catch {
339
- const stV7 = await flap.getTokenV7(token);
340
- taxRate = Number(stV7.taxRate ?? 0n);
341
- }
353
+ const stV8 = await flap.getTokenV8Safe(token);
354
+ taxRate = flapV8TaxBps(stV8);
342
355
  }
343
356
  else {
344
357
  const stV7 = await flap.getTokenV7(token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "4.0.9",
3
+ "version": "4.0.11",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",