four-flap-meme-sdk 1.4.11 → 1.4.13

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.
@@ -10,6 +10,7 @@
10
10
  import { ethers, Wallet, JsonRpcProvider, Contract } from 'ethers';
11
11
  import { NonceManager, getOptimizedGasPrice } from '../utils/bundle-helpers.js';
12
12
  import { PROFIT_CONFIG } from '../utils/constants.js';
13
+ import { getTokenToNativeQuote } from '../utils/quote-helpers.js';
13
14
  // ============================================================================
14
15
  // 常量配置
15
16
  // ============================================================================
@@ -44,56 +45,7 @@ const DEFAULT_GAS_LIMIT = 300000;
44
45
  const DEADLINE_MINUTES = 20;
45
46
  // ✅ BlockRazor Builder EOA 地址(用于贿赂,仅 BSC 链)
46
47
  const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
47
- // ✅ V2 Router ABI(用于 ERC20 → 原生代币报价)
48
- const QUOTE_ROUTER_ABI = [
49
- 'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
50
- ];
51
- // ✅ V3 QuoterV2 ABI(用于 V3 报价)
52
- // PancakeSwap V3 使用结构体参数版本
53
- const V3_QUOTER_ABI = [
54
- {
55
- "inputs": [{
56
- "components": [
57
- { "name": "tokenIn", "type": "address" },
58
- { "name": "tokenOut", "type": "address" },
59
- { "name": "amountIn", "type": "uint256" },
60
- { "name": "fee", "type": "uint24" },
61
- { "name": "sqrtPriceLimitX96", "type": "uint160" }
62
- ],
63
- "name": "params",
64
- "type": "tuple"
65
- }],
66
- "name": "quoteExactInputSingle",
67
- "outputs": [
68
- { "name": "amountOut", "type": "uint256" },
69
- { "name": "sqrtPriceX96After", "type": "uint160" },
70
- { "name": "initializedTicksCrossed", "type": "uint32" },
71
- { "name": "gasEstimate", "type": "uint256" }
72
- ],
73
- "stateMutability": "nonpayable",
74
- "type": "function"
75
- }
76
- ];
77
- // ✅ 各链的报价配置(V2 Router + V3 Quoter)
78
- const QUOTE_CONFIG = {
79
- BSC: {
80
- router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2 Router
81
- quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', // PancakeSwap V3 Quoter
82
- wrappedNative: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
83
- },
84
- MONAD: {
85
- router: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // PancakeSwap V2
86
- quoter: '', // TODO: 添加 Monad V3 Quoter
87
- wrappedNative: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // WMON
88
- },
89
- XLAYER: {
90
- router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // PotatoSwap V2
91
- quoter: '', // TODO: 添加 XLayer V3 Quoter
92
- wrappedNative: '0xe538905cf8410324e03a5a23c1c177a474d59b2b', // WOKB
93
- },
94
- };
95
- // ✅ V3 常用费率档位(用于报价尝试)
96
- const V3_FEE_TIERS = [100, 500, 2500, 10000]; // 0.01%, 0.05%, 0.25%, 1%
48
+ // ✅ V2/V3 报价相关常量和函数已移至 ../utils/quote-helpers.ts
97
49
  /**
98
50
  * 截断小数位数,避免超过代币精度导致 parseUnits 报错
99
51
  * 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
@@ -255,92 +207,6 @@ const DYORSWAP_SELECTORS = {
255
207
  */
256
208
  function encodeDYORSwapExactTokensForTokens(amountIn, amountOutMin, path, pools, to, // 可以是地址 (string) 或 address(2) 这样的特殊值 (bigint)
257
209
  flag, factory) {
258
- const abiCoder = ethers.AbiCoder.defaultAbiCoder();
259
- // 固定参数: amountIn, amountOutMin, offset_path, offset_pools, to, flag, factory
260
- // 共 7 个 slot = 7 * 32 = 224 bytes
261
- //
262
- // offset 是从参数区域开始计算的,不是从固定参数后面
263
- // path offset = 5 * 32 = 160 (跳过 amountIn, amountOutMin, offset_path, offset_pools, to)
264
- // 不对,成功交易显示 offset = 160 = 0xa0,而固定参数有 7 个
265
- //
266
- // 重新分析:offset 可能是从某个特定位置开始
267
- // 成功交易: path offset = 160, pools offset = 224
268
- // 差值 = 64 = 2 * 32 (path 有 2 个元素)
269
- //
270
- // 所以 offset 是从 (amountIn, amountOutMin, offset_path, offset_pools, to) 之后开始
271
- // 即 5 * 32 = 160 是 path 数据的开始位置
272
- // 计算 path 和 pools 数据
273
- const pathDataRaw = abiCoder.encode(['address[]'], [path]).slice(2);
274
- const poolsDataRaw = abiCoder.encode(['address[]'], [pools]).slice(2);
275
- // offset 从第 5 个参数后开始 (跳过 amountIn, amountOutMin, offset_path, offset_pools, to)
276
- // 但实际上,ABI 编码中 offset 是从整个编码数据的开始计算的
277
- // 7 个固定参数 * 32 = 224 bytes
278
- // 但成功交易显示 path offset = 160,这意味着它是从某个不同的基准点计算的
279
- // 让我们直接使用成功交易的模式:
280
- // path offset = 5 * 32 = 160 (第 5 个 slot 后开始,即 to 之后)
281
- // 但 to 在 slot 4,flag 在 slot 5,factory 在 slot 6
282
- // 所以 path 数据在 slot 7 开始
283
- //
284
- // 等等,offset 160 = 5 * 32,但 slot 5 是 flag
285
- // 这说明 offset 是相对于整个参数数据的开始(不包括 selector)
286
- //
287
- // 重新计算:
288
- // slot 0: amountIn
289
- // slot 1: amountOutMin
290
- // slot 2: path offset
291
- // slot 3: pools offset
292
- // slot 4: to
293
- // slot 5: flag
294
- // slot 6: factory
295
- // slot 7: path.length (offset 160 指向这里? 不对,160/32 = 5)
296
- //
297
- // 160 / 32 = 5,所以 offset 160 指向 slot 5
298
- // 但 slot 5 是 flag,不是 path.length
299
- //
300
- // 我觉得这个 ABI 可能有特殊的编码方式,让我直接复制成功交易的结构
301
- // 根据成功交易,参数顺序和 offset 计算方式:
302
- // [0] amountIn
303
- // [1] amountOutMin
304
- // [2] path offset = 0xa0 = 160
305
- // [3] pools offset = 0xe0 = 224
306
- // [4] to
307
- // [5] flag
308
- // [6] factory
309
- // [7] path.length = 2
310
- // [8] path[0]
311
- // [9] path[1]
312
- // [10] pools.length = 1
313
- // [11] pools[0]
314
- // 160 / 32 = 5,指向 slot 5,但 slot 5 是 flag
315
- // 这说明 offset 可能不是标准的 ABI 编码
316
- //
317
- // 让我尝试另一种理解:offset 是从 (to, flag, factory) 之后开始
318
- // 即 offset 0 = slot 7 的位置
319
- // 那么 path offset = 160 - 7*32 = 160 - 224 = -64? 不对
320
- //
321
- // 让我直接硬编码 offset 值:
322
- // path offset = 0xa0 = 160
323
- // pools offset = path offset + (1 + path.length) * 32
324
- // = 160 + (1 + 2) * 32 = 160 + 96 = 256? 但成功交易是 224
325
- //
326
- // 成功交易: pools offset = 224 = 0xe0
327
- // 224 - 160 = 64 = 2 * 32
328
- // 所以 pools offset = path offset + path.length * 32 (不包括 length slot?)
329
- //
330
- // 让我直接按成功交易的模式来:
331
- // path offset = 5 * 32 = 160 (固定)
332
- // pools offset = path offset + 32 + path.length * 32
333
- // = 160 + 32 + 2 * 32 = 160 + 96 = 256? 还是不对
334
- //
335
- // 实际 pools offset = 224 = 160 + 64 = 160 + 2 * 32
336
- // 所以 pools offset = path offset + path.length * 32 (不算 length slot)
337
- //
338
- // 但这不符合标准 ABI 编码...让我直接试试
339
- // 成功交易分析:
340
- // pathOffset = 160 (0xa0) = 5 * 32
341
- // poolsOffset = 224 (0xe0) = 7 * 32
342
- // 但实际上没有单独的 pools 数据!
343
- // poolsOffset 指向的是 path 数据结束后的位置(空)
344
210
  const pathOffset = 5n * 32n; // 160
345
211
  // poolsOffset = pathOffset + (1 + path.length) * 32 = 160 + 96 = 256?
346
212
  // 不对,成功交易是 224 = 160 + 64 = pathOffset + path.length * 32
@@ -582,177 +448,7 @@ async function getGasPrice(provider, config) {
582
448
  function isNativeToken(quoteToken) {
583
449
  return !quoteToken || quoteToken === ZERO_ADDRESS;
584
450
  }
585
- // ✅ 常用稳定币地址(用于多跳报价)
586
- const STABLE_COINS = {
587
- BSC: [
588
- '0x55d398326f99059fF775485246999027B3197955', // USDT
589
- '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
590
- '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
591
- ],
592
- MONAD: [],
593
- XLAYER: [
594
- '0x1E4a5963aBFD975d8c9021ce480b42188849D41d', // USDT
595
- ],
596
- };
597
- /**
598
- * ✅ 获取 ERC20 代币 → 原生代币的报价
599
- * 用于将 ERC20 利润转换为原生代币(和 core.ts 逻辑一致)
600
- *
601
- * @param provider - Provider 实例
602
- * @param tokenAddress - ERC20 代币地址
603
- * @param tokenAmount - 代币数量(wei)
604
- * @param chain - 链名称
605
- * @param version - 'v2' | 'v3',指定使用哪个版本的报价
606
- * @param fee - V3 费率档位(仅 V3 时使用)
607
- * @returns 等值的原生代币数量(wei),失败返回 0n
608
- */
609
- async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain, version = 'v2', fee) {
610
- if (tokenAmount <= 0n)
611
- return 0n;
612
- const chainUpper = chain.toUpperCase();
613
- const config = QUOTE_CONFIG[chainUpper];
614
- if (!config) {
615
- console.warn(`[getTokenToNativeQuote] 不支持的链: ${chain}`);
616
- return 0n;
617
- }
618
- const tokenLower = tokenAddress.toLowerCase();
619
- const wrappedNativeLower = config.wrappedNative.toLowerCase();
620
- const stableCoins = STABLE_COINS[chainUpper] || [];
621
- // 如果代币本身就是包装原生代币,直接返回
622
- if (tokenLower === wrappedNativeLower) {
623
- return tokenAmount;
624
- }
625
- // ==================== V2 报价 ====================
626
- if (version === 'v2') {
627
- const router = new Contract(config.router, QUOTE_ROUTER_ABI, provider);
628
- // V2 策略 1:直接路径 代币 → WBNB
629
- try {
630
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, config.wrappedNative]);
631
- const nativeAmount = amounts[1];
632
- if (nativeAmount > 0n) {
633
- console.log(`[getTokenToNativeQuote] V2 直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
634
- return nativeAmount;
635
- }
636
- }
637
- catch {
638
- // V2 直接路径失败,尝试多跳
639
- }
640
- // V2 策略 2:多跳路径 代币 → 稳定币 → WBNB
641
- for (const stableCoin of stableCoins) {
642
- // 跳过:代币本身就是稳定币
643
- if (tokenLower === stableCoin.toLowerCase()) {
644
- try {
645
- const amounts = await router.getAmountsOut(tokenAmount, [stableCoin, config.wrappedNative]);
646
- const nativeAmount = amounts[1];
647
- if (nativeAmount > 0n) {
648
- console.log(`[getTokenToNativeQuote] V2 稳定币直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
649
- return nativeAmount;
650
- }
651
- }
652
- catch {
653
- continue;
654
- }
655
- }
656
- // 多跳:代币 → 稳定币 → WBNB
657
- try {
658
- const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, stableCoin, config.wrappedNative]);
659
- const nativeAmount = amounts[2];
660
- if (nativeAmount > 0n) {
661
- console.log(`[getTokenToNativeQuote] V2 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(nativeAmount)} BNB`);
662
- return nativeAmount;
663
- }
664
- }
665
- catch {
666
- continue;
667
- }
668
- }
669
- console.warn(`[getTokenToNativeQuote] V2 所有报价路径均失败,跳过利润提取`);
670
- return 0n;
671
- }
672
- // ==================== V3 报价 ====================
673
- if (version === 'v3') {
674
- if (!config.quoter) {
675
- console.warn(`[getTokenToNativeQuote] 该链不支持 V3 Quoter: ${chain}`);
676
- return 0n;
677
- }
678
- const quoter = new Contract(config.quoter, V3_QUOTER_ABI, provider);
679
- // V3 策略 1:直接路径 代币 → WBNB
680
- // 如果指定了 fee,只用指定的 fee;否则尝试所有费率
681
- const feesToTry = fee ? [fee] : V3_FEE_TIERS;
682
- console.log(`[getTokenToNativeQuote] 开始 V3 报价: token=${tokenAddress.slice(0, 10)}..., amount=${tokenAmount}, feesToTry=${feesToTry}`);
683
- for (const tryFee of feesToTry) {
684
- try {
685
- // ✅ 使用结构体参数调用 QuoterV2
686
- const result = await quoter.quoteExactInputSingle.staticCall({
687
- tokenIn: tokenAddress,
688
- tokenOut: config.wrappedNative,
689
- amountIn: tokenAmount,
690
- fee: tryFee,
691
- sqrtPriceLimitX96: 0n
692
- });
693
- // QuoterV2 返回多个值,第一个是 amountOut
694
- const amountOut = result[0];
695
- if (amountOut > 0n) {
696
- console.log(`[getTokenToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
697
- return amountOut;
698
- }
699
- }
700
- catch (err) {
701
- console.log(`[getTokenToNativeQuote] V3 直接路径失败 (fee=${tryFee}): ${String(err).slice(0, 100)}`);
702
- continue;
703
- }
704
- }
705
- // V3 策略 2:多跳路径 代币 → USDT → WBNB
706
- for (const stableCoin of stableCoins) {
707
- if (tokenLower === stableCoin.toLowerCase())
708
- continue;
709
- for (const fee1 of feesToTry) {
710
- try {
711
- // 第一跳:代币 → 稳定币(使用结构体参数)
712
- const midResult = await quoter.quoteExactInputSingle.staticCall({
713
- tokenIn: tokenAddress,
714
- tokenOut: stableCoin,
715
- amountIn: tokenAmount,
716
- fee: fee1,
717
- sqrtPriceLimitX96: 0n
718
- });
719
- const midAmount = midResult[0];
720
- if (midAmount <= 0n)
721
- continue;
722
- console.log(`[getTokenToNativeQuote] V3 第一跳成功: 代币 → ${stableCoin.slice(0, 10)}... = ${midAmount}`);
723
- // 第二跳:稳定币 → WBNB(尝试多个费率)
724
- const stableFees = [100, 500, 2500]; // 0.01%, 0.05%, 0.25%
725
- for (const fee2 of stableFees) {
726
- try {
727
- const finalResult = await quoter.quoteExactInputSingle.staticCall({
728
- tokenIn: stableCoin,
729
- tokenOut: config.wrappedNative,
730
- amountIn: midAmount,
731
- fee: fee2,
732
- sqrtPriceLimitX96: 0n
733
- });
734
- const amountOut = finalResult[0];
735
- if (amountOut > 0n) {
736
- console.log(`[getTokenToNativeQuote] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
737
- return amountOut;
738
- }
739
- }
740
- catch {
741
- continue;
742
- }
743
- }
744
- }
745
- catch (err) {
746
- console.log(`[getTokenToNativeQuote] V3 第一跳失败 (fee=${fee1}): ${err}`);
747
- continue;
748
- }
749
- }
750
- }
751
- console.warn(`[getTokenToNativeQuote] V3 所有报价路径均失败,跳过利润提取`);
752
- return 0n;
753
- }
754
- return 0n;
755
- }
451
+ // ✅ getTokenToNativeQuote 函数已移至 ../utils/quote-helpers.ts
756
452
  /**
757
453
  * ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
758
454
  */
package/dist/index.d.ts CHANGED
@@ -43,6 +43,7 @@ export { fourBundleBuyFirstMerkle, type FourBuyFirstConfig, type FourBundleBuyFi
43
43
  export { flapBundleBuyFirstMerkle, type FlapBuyFirstSignConfig, type FlapBuyFirstConfig, type FlapBundleBuyFirstSignParams, type FlapBundleBuyFirstParams, type FlapBuyFirstResult } from './flap/portal-bundle-merkle/swap-buy-first.js';
44
44
  export { pancakeBundleBuyFirstMerkle, type PancakeBuyFirstSignConfig, type PancakeBuyFirstConfig, type PancakeBundleBuyFirstSignParams, type PancakeBundleBuyFirstParams, type PancakeBuyFirstResult } from './pancake/bundle-buy-first.js';
45
45
  export { PROFIT_CONFIG } from './utils/constants.js';
46
+ export { quoteV2, quoteV3, quote, getTokenToNativeQuote, getNativeToTokenQuote, getWrappedNativeAddress, getStableCoins, V3_FEE_TIERS, QUOTE_CONFIG, type QuoteParams, type QuoteResult, type SupportedChain, } from './utils/quote-helpers.js';
46
47
  export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 新增:顺序广播并等待确认(用于多跳)
47
48
  submitDirectToRpcParallel, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult } from './contracts/tm-bundle-merkle/submit.js';
48
49
  export { directV2BatchBuy, directV2BatchSell, directV3BatchBuy, directV3BatchSell, getRouterAddress, DIRECT_ROUTERS, type DirectV2BuyParams, type DirectV2SellParams, type DirectV3BuyParams, type DirectV3SellParams, type DirectRouterResult, type DirectRouterSignConfig, type DexKey, type RouterVersion, } from './dex/index.js';
package/dist/index.js CHANGED
@@ -63,6 +63,8 @@ export { flapBundleBuyFirstMerkle } from './flap/portal-bundle-merkle/swap-buy-f
63
63
  export { pancakeBundleBuyFirstMerkle } from './pancake/bundle-buy-first.js';
64
64
  // ✅ 硬编码利润配置(统一管理)
65
65
  export { PROFIT_CONFIG } from './utils/constants.js';
66
+ // ✅ V2/V3 报价工具(统一管理)
67
+ export { quoteV2, quoteV3, quote, getTokenToNativeQuote, getNativeToTokenQuote, getWrappedNativeAddress, getStableCoins, V3_FEE_TIERS, QUOTE_CONFIG, } from './utils/quote-helpers.js';
66
68
  // ✅ Monad 等不支持 Bundle 的链:逐笔 RPC 广播方法(服务器端使用)
67
69
  export { submitDirectToRpc, submitDirectToRpcSequential, // ✅ 新增:顺序广播并等待确认(用于多跳)
68
70
  submitDirectToRpcParallel } from './contracts/tm-bundle-merkle/submit.js';
@@ -129,6 +129,8 @@ export interface PancakeQuickBatchSwapParams {
129
129
  tokenAddress: string;
130
130
  routeParams: RouteParams;
131
131
  config: PancakeSwapSignConfig;
132
+ quoteToken?: string;
133
+ quoteTokenDecimals?: number;
132
134
  }
133
135
  /**
134
136
  * 快捷批量换手结果
@@ -139,21 +141,23 @@ export interface PancakeQuickBatchSwapResult {
139
141
  sellerAddress: string;
140
142
  buyerAddresses: string[];
141
143
  sellAmount: string;
142
- estimatedBNBOut: string;
144
+ estimatedOutput: string;
143
145
  transferAmounts: string[];
144
146
  profitAmount?: string;
147
+ useNativeToken: boolean;
145
148
  };
146
149
  }
147
150
  /**
148
151
  * PancakeSwap 快捷批量换手(资金自动流转)
149
152
  *
150
- * 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
153
+ * 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
151
154
  *
152
155
  * 特点:
153
- * - 子钱包不需要预先有 BNB 余额
156
+ * - 子钱包不需要预先有余额
154
157
  * - 资金来自主钱包卖出代币所得
155
158
  * - 提升资金利用率
159
+ * - 支持 BNB 和 ERC20(如 USDT)两种模式
156
160
  *
157
- * 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
161
+ * 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
158
162
  */
159
163
  export declare function pancakeQuickBatchSwapMerkle(params: PancakeQuickBatchSwapParams): Promise<PancakeQuickBatchSwapResult>;