four-flap-meme-sdk 4.4.2 → 4.5.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.
Files changed (68) hide show
  1. package/README.md +16 -22
  2. package/dist/bundle/four-meme/approve-tokenmanager.js +2 -2
  3. package/dist/bundle/four-meme/core/create-token-quote.js +10 -1
  4. package/dist/bundle/four-meme/private/buy-quote.js +10 -1
  5. package/dist/bundle/four-meme/swap-sign-helpers.js +6 -1
  6. package/dist/bundle/four-meme/types/buy-first.d.ts +2 -1
  7. package/dist/bundle/four-meme/types/config.d.ts +2 -1
  8. package/dist/bundle/four-meme/types/swap-types.d.ts +2 -1
  9. package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +16 -8
  10. package/dist/bundle/four-meme/utils-sweep/main.js +15 -1
  11. package/dist/bundle/four-meme/utils-sweep/native-no-hop.js +17 -9
  12. package/dist/bundle/index.d.ts +1 -1
  13. package/dist/bundle/index.js +1 -1
  14. package/dist/bundle/internal.d.ts +2 -0
  15. package/dist/bundle/internal.js +7 -3
  16. package/dist/bundle/submit/index.d.ts +1 -3
  17. package/dist/bundle/submit/index.js +1 -3
  18. package/dist/chains/bsc/index.d.ts +1 -1
  19. package/dist/chains/bsc/index.js +1 -1
  20. package/dist/chains/bsc/pancake/bundle-buy-first-types.d.ts +2 -1
  21. package/dist/chains/bsc/pancake/bundle-swap-types.d.ts +2 -1
  22. package/dist/chains/bsc/platforms/four/index.d.ts +1 -1
  23. package/dist/chains/bsc/platforms/four/index.js +1 -1
  24. package/dist/chains/eni/batch-router/bundle-approve.js +2 -1
  25. package/dist/chains/eni/batch-router/transfer-disperse.js +2 -1
  26. package/dist/chains/eni/platforms/dswap/liquidity.js +4 -4
  27. package/dist/chains/robinhood/sign.js +15 -1
  28. package/dist/core/clients/blockrazor.js +8 -6
  29. package/dist/core/clients/blockrazor.test.d.ts +1 -0
  30. package/dist/core/clients/blockrazor.test.js +23 -0
  31. package/dist/core/clients/index.d.ts +0 -1
  32. package/dist/core/clients/index.js +0 -1
  33. package/dist/core/clients/merkle.js +12 -7
  34. package/dist/core/clients/merkle.test.d.ts +1 -0
  35. package/dist/core/clients/merkle.test.js +8 -0
  36. package/dist/core/gas/bundle-gas.d.ts +8 -0
  37. package/dist/core/gas/bundle-gas.js +13 -1
  38. package/dist/core/nonce/nonce-manager.d.ts +2 -0
  39. package/dist/core/nonce/nonce-manager.js +21 -1
  40. package/dist/core/tx/wallet-sign-patch.js +61 -4
  41. package/dist/core/tx/wallet-sign-patch.test.d.ts +1 -0
  42. package/dist/core/tx/wallet-sign-patch.test.js +21 -0
  43. package/dist/domains/dex/direct-router-v2-buy.js +16 -2
  44. package/dist/domains/dex/direct-router-v2-sell.js +3 -2
  45. package/dist/domains/flap/portal-bundle/helpers.js +1 -1
  46. package/dist/domains/flap/portal-bundle-merkle/private/buy.js +3 -3
  47. package/dist/domains/flap/portal-bundle-merkle/private/helpers.d.ts +1 -1
  48. package/dist/domains/flap/portal-bundle-merkle/private/helpers.js +4 -8
  49. package/dist/domains/flap/portal-bundle-merkle/private/sell.js +3 -3
  50. package/dist/domains/flap/portal-bundle-merkle/swap-buy-first-types.d.ts +2 -1
  51. package/dist/domains/flap/portal-bundle-merkle/swap-helpers.d.ts +2 -1
  52. package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +10 -4
  53. package/dist/domains/flap/portal-bundle-merkle/swap-planning.test.d.ts +1 -0
  54. package/dist/domains/flap/portal-bundle-merkle/swap-planning.test.js +16 -0
  55. package/dist/domains/flap/portal-bundle-merkle/types-config.d.ts +2 -1
  56. package/dist/domains/four/contracts/tm-bundle-create.js +13 -1
  57. package/dist/domains/four/contracts/tm-bundle-helpers.js +1 -1
  58. package/dist/domains/holders/internal/constants.d.ts +1 -1
  59. package/dist/domains/holders/internal/constants.js +1 -1
  60. package/dist/domains/merkle/index.d.ts +1 -1
  61. package/dist/domains/merkle/index.js +1 -1
  62. package/dist/exports/root-bundle-and-tooling.d.ts +1 -1
  63. package/dist/exports/root-bundle-and-tooling.js +1 -1
  64. package/dist/exports/root-foundations.d.ts +0 -1
  65. package/dist/exports/root-foundations.js +0 -1
  66. package/dist/utils/swap-helpers.d.ts +1 -15
  67. package/dist/utils/swap-helpers.js +0 -80
  68. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **four.meme 和 Flap Protocol 统一 SDK - 生产环境 MEV 保护方案**
4
4
 
5
- 提供完整的代币发行和交易功能,所有操作均通过 **Merkle.io Bundle** 执行,确保 MEV 保护、原子性和私有性。
5
+ 提供完整的代币发行和交易功能,签名后通过 BlockRazor / 48.club / Emit 提交,确保 MEV 保护和原子性。
6
6
 
7
7
  ## 核心优势
8
8
 
@@ -20,9 +20,9 @@ npm i four-flap-meme-sdk ethers
20
20
 
21
21
  **要求**:Node.js >= 18, ESM, ethers v6
22
22
 
23
- ## 获取 Merkle API Key
23
+ ## 提交
24
24
 
25
- 从 [Merkle.io](https://merkle.io) 获取免费 API Key,用于 Bundle 服务。
25
+ SDK 只负责构建与签名;Merkle 网络客户端和 `submitBundleToMerkle` 已移除,请使用 BlockRazor、48.club 或 Emit 提交。
26
26
 
27
27
  ## 浏览器环境 CORS 支持
28
28
 
@@ -66,7 +66,6 @@ const result = await createTokenWithBundleBuyMerkle({
66
66
  label: 'Meme'
67
67
  },
68
68
  config: {
69
- apiKey: 'YOUR_MERKLE_API_KEY',
70
69
  rpcUrl: 'https://bsc-dataseed.binance.org'
71
70
  }
72
71
  });
@@ -84,7 +83,7 @@ await batchBuyWithBundleMerkle({
84
83
  privateKeys: ['0x...买家1', '0x...买家2', '0x...买家3'],
85
84
  buyAmounts: ['0.3', '0.5', '0.2'],
86
85
  tokenAddress: '0x...代币地址',
87
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
86
+ config: {}
88
87
  });
89
88
  ```
90
89
 
@@ -98,7 +97,7 @@ await batchSellWithBundleMerkle({
98
97
  privateKeys: ['0x...卖家1', '0x...卖家2'],
99
98
  sellAmounts: ['1000', '2000'],
100
99
  tokenAddress: '0x...代币地址',
101
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
100
+ config: {}
102
101
  });
103
102
  ```
104
103
 
@@ -110,7 +109,7 @@ import { approveFourBatchMerkle } from 'four-flap-meme-sdk';
110
109
  await approveFourBatchMerkle({
111
110
  privateKeys: ['0x...地址1', '0x...地址2'],
112
111
  tokenAddress: '0x...代币地址',
113
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
112
+ config: {}
114
113
  });
115
114
  ```
116
115
 
@@ -131,7 +130,7 @@ await fourPancakeProxyBatchBuyMerkle({
131
130
  ['0x...WBNB', '0x...TOKEN']
132
131
  ],
133
132
  routeType: 'v2',
134
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
133
+ config: {}
135
134
  });
136
135
 
137
136
  // V2 批量卖出
@@ -143,7 +142,7 @@ await fourPancakeProxyBatchSellMerkle({
143
142
  ['0x...TOKEN', '0x...WBNB']
144
143
  ],
145
144
  routeType: 'v2',
146
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
145
+ config: {}
147
146
  });
148
147
  ```
149
148
 
@@ -158,7 +157,7 @@ await fourPancakeProxyBatchBuyMerkle({
158
157
  tokenOut: '0x...TOKEN',
159
158
  fee: 2500, // 0.25%
160
159
  routeType: 'v3-single',
161
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
160
+ config: {}
162
161
  });
163
162
 
164
163
  // V3 多跳买入
@@ -168,7 +167,7 @@ await fourPancakeProxyBatchBuyMerkle({
168
167
  lpAddresses: ['0x...WBNB_USDT_LP', '0x...USDT_TOKEN_LP'],
169
168
  exactTokenIn: '0x...WBNB',
170
169
  routeType: 'v3-multi',
171
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
170
+ config: {}
172
171
  });
173
172
  ```
174
173
 
@@ -196,7 +195,6 @@ const result = await flapCreateTokenWithBundleBuyMerkle({
196
195
  meta: 'QmXxx...' // IPFS CID
197
196
  },
198
197
  config: {
199
- apiKey: 'YOUR_MERKLE_API_KEY',
200
198
  rpcUrl: 'https://bsc-dataseed.binance.org'
201
199
  },
202
200
  dexThresh: 1,
@@ -222,7 +220,7 @@ await flapBatchBuyWithBundleMerkle({
222
220
  privateKeys: ['0x...买家1', '0x...买家2'],
223
221
  buyAmounts: ['0.5', '1.0'],
224
222
  tokenAddress: '0x...代币地址',
225
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
223
+ config: {}
226
224
  });
227
225
 
228
226
  // 批量卖出
@@ -231,7 +229,7 @@ await flapBatchSellWithBundleMerkle({
231
229
  privateKeys: ['0x...卖家1', '0x...卖家2'],
232
230
  sellAmounts: ['1000', '2000'],
233
231
  tokenAddress: '0x...代币地址',
234
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
232
+ config: {}
235
233
  });
236
234
  ```
237
235
 
@@ -253,7 +251,7 @@ await flapPancakeProxyBatchBuyMerkle({
253
251
  ['0x...WBNB', '0x...TOKEN']
254
252
  ],
255
253
  routeType: 'v2',
256
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
254
+ config: {}
257
255
  });
258
256
 
259
257
  // V3 单跳卖出
@@ -265,7 +263,7 @@ await flapPancakeProxyBatchSellMerkle({
265
263
  tokenOut: '0x...WBNB',
266
264
  fee: 2500,
267
265
  routeType: 'v3-single',
268
- config: { apiKey: 'YOUR_MERKLE_API_KEY' }
266
+ config: {}
269
267
  });
270
268
  ```
271
269
 
@@ -275,7 +273,6 @@ await flapPancakeProxyBatchSellMerkle({
275
273
 
276
274
  ```typescript
277
275
  type BundleMerkleConfig = {
278
- apiKey: string; // Merkle API Key(必填)
279
276
  rpcUrl?: string; // RPC 节点(可选)
280
277
  bundleBlockOffset?: number; // Bundle 有效期,默认 200 区块
281
278
  gasLimitMultiplier?: number; // Gas limit 倍数,默认 1.2
@@ -331,11 +328,8 @@ type BundleMerkleConfig = {
331
328
 
332
329
  ## 常见问题
333
330
 
334
- **Q: 为什么只推荐 Merkle Bundle 方法?**
335
- A: 生产环境必须使用 MEV 保护,防止抢跑和三明治攻击。Merkle Bundle 是目前最可靠的 MEV 保护方案。
336
-
337
- **Q: Merkle API Key 如何获取?**
338
- A: 访问 [Merkle.io](https://merkle.io) 注册并获取免费 API Key。
331
+ **Q: 现在如何提交 Bundle?**
332
+ A: 使用 BlockRazor、48.club 或 Emit service;Merkle 网络客户端已移除。
339
333
 
340
334
  **Q: Bundle 交易失败怎么办?**
341
335
  A: 检查配置参数(gasLimit、blockOffset、slippage),确保所有地址有足够余额,适当增大超时时间。
@@ -50,8 +50,8 @@ function resolveApprovalGasLimit(config) {
50
50
  if (config.gasLimit)
51
51
  return BigInt(config.gasLimit);
52
52
  if (config.gasLimitMultiplier)
53
- return BigInt(Math.ceil(60000 * config.gasLimitMultiplier));
54
- return 60000n;
53
+ return BigInt(Math.ceil(150000 * config.gasLimitMultiplier));
54
+ return 150000n;
55
55
  }
56
56
  /**
57
57
  * 批量授权代币给 TokenManager(用于 Private Buy/Sell)
@@ -63,11 +63,20 @@ export async function signQuoteApproveIfNeeded(params) {
63
63
  if (allowance >= amount)
64
64
  return undefined;
65
65
  const unsigned = await token.approve.populateTransaction(params.spender, amount);
66
+ let gasLimit = 150000n;
67
+ try {
68
+ const estimated = await provider.estimateGas({ ...unsigned, from: params.wallet.address });
69
+ if (estimated > 0n)
70
+ gasLimit = estimated;
71
+ }
72
+ catch {
73
+ /* 估算失败用 15 万,避免代理报价币 8 万不够 */
74
+ }
66
75
  return params.wallet.signTransaction({
67
76
  ...unsigned,
68
77
  from: params.wallet.address,
69
78
  nonce: await params.getNonce(),
70
- gasLimit: 80000n,
79
+ gasLimit,
71
80
  gasPrice: params.gasPrice,
72
81
  chainId: params.chainId,
73
82
  type: params.txType,
@@ -56,11 +56,20 @@ export async function signQuoteApproveTxs(params) {
56
56
  continue;
57
57
  const token = new Contract(quoteAddress, ERC20_ABI, wallets[i]);
58
58
  const unsigned = await token.approve.populateTransaction(spender, amounts[i]);
59
+ let gasLimit = 150000n;
60
+ try {
61
+ const estimated = await provider.estimateGas({ ...unsigned, from: wallets[i].address });
62
+ if (estimated > 0n)
63
+ gasLimit = estimated;
64
+ }
65
+ catch {
66
+ /* 估算失败用 15 万 */
67
+ }
59
68
  signed.push(await wallets[i].signTransaction({
60
69
  ...unsigned,
61
70
  from: wallets[i].address,
62
71
  nonce: await nonceManager.getNextNonce(wallets[i]),
63
- gasLimit: 80000n,
72
+ gasLimit,
64
73
  gasPrice,
65
74
  chainId,
66
75
  type: txType,
@@ -1,5 +1,6 @@
1
1
  import { ethers, Contract } from 'ethers';
2
2
  import { getGasLimit } from '../../utils/bundle-helpers.js';
3
+ import { estimateApproveGasLimit } from '../../core/gas/bundle-gas.js';
3
4
  import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
4
5
  import { MULTICALL3_ABI } from '../../abis/common.js';
5
6
  import { batchGetBalances } from '../internal.js';
@@ -85,12 +86,16 @@ export function planSellerNonces(sellerNonces, needBribeTx, needApproval, extrac
85
86
  }
86
87
  export async function signTmMaxApprovalTx(seller, tokenAddress, approvalNonce, gasPrice, chainIdNum, txType) {
87
88
  const approveData = APPROVE_INTERFACE.encodeFunctionData('approve', [TM_ADDRESS, ethers.MaxUint256]);
89
+ const linked = seller.provider;
90
+ const gasLimit = linked
91
+ ? await estimateApproveGasLimit(linked, seller.address, tokenAddress, approveData)
92
+ : 150000n;
88
93
  return seller.signTransaction({
89
94
  to: tokenAddress,
90
95
  data: approveData,
91
96
  value: 0n,
92
97
  nonce: approvalNonce,
93
- gasLimit: 80000n,
98
+ gasLimit,
94
99
  gasPrice,
95
100
  chainId: chainIdNum,
96
101
  type: txType,
@@ -6,7 +6,8 @@ export type FourBuyFirstSignConfig = FourSignConfig & {
6
6
  userType?: UserType;
7
7
  };
8
8
  export type FourBuyFirstConfig = CommonBundleConfig & {
9
- apiKey: string;
9
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容,不再读取 */
10
+ apiKey?: string;
10
11
  customRpcUrl?: string;
11
12
  bundleBlockOffset?: number;
12
13
  reserveGasBNB?: number;
@@ -22,7 +22,8 @@ export type FourSignConfig = {
22
22
  bribeAmount?: number;
23
23
  };
24
24
  export type FourBundleMerkleConfig = {
25
- apiKey: string;
25
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容,不再读取 */
26
+ apiKey?: string;
26
27
  customRpcUrl?: string;
27
28
  fourApiUrl?: string;
28
29
  bundleBlockOffset?: number;
@@ -16,7 +16,8 @@ export type FourSwapSignConfig = {
16
16
  };
17
17
  /** Swap Merkle 配置(保留兼容) */
18
18
  export type FourSwapConfig = CommonBundleConfig & {
19
- apiKey: string;
19
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容,不再读取 */
20
+ apiKey?: string;
20
21
  customRpcUrl?: string;
21
22
  bundleBlockOffset?: number;
22
23
  reserveGasBNB?: number;
@@ -113,14 +113,23 @@ export async function sweepErc20NoHop(ctx) {
113
113
  }
114
114
  }
115
115
  // ✅ 第二步:生成归集交易(ERC20 全额归集,费用以原生币单独支付)
116
+ // 支付者可能既要归集又要发利润,必须先按“主交易 + 利润”预留连续 nonce;
117
+ // 且支付者不能再从批量 nonce 里取一次,否则 target 同时在 sources 时会拿到 m+1 做主交易、m 做利润。
116
118
  let payerProfitNonce;
119
+ let payerMainNonce;
120
+ const payerAddressErc20 = extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20
121
+ ? profitPayerWalletErc20.address.toLowerCase()
122
+ : undefined;
117
123
  if (extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20) {
118
- const nonces = await nonceManager.getNextNonceBatch(profitPayerWalletErc20, useTargetAsPayerErc20 ? 1 : 2);
119
- payerProfitNonce = useTargetAsPayerErc20 ? nonces[0] : nonces[1];
124
+ const payerIsSourceWithAmount = targetIndexInSources >= 0 && useTargetAsPayerErc20 && sweepAmounts[targetIndexInSources] > 0n;
125
+ const payerNeedsMain = !useTargetAsPayerErc20 || payerIsSourceWithAmount;
126
+ const nonces = await nonceManager.getNextNonceBatch(profitPayerWalletErc20, payerNeedsMain ? 2 : 1);
127
+ if (payerNeedsMain)
128
+ payerMainNonce = nonces[0];
129
+ payerProfitNonce = payerNeedsMain ? nonces[1] : nonces[0];
120
130
  }
121
- // ✅ 优化:批量获取所有钱包的 nonce(JSON-RPC 批量请求)
122
- // 过滤出需要归集的钱包(如果用 targetWallet 支付利润,则不排除 maxSweepIndex)
123
- const walletsToSweepErc20 = wallets.filter((_, i) => sweepAmounts[i] > 0n && (useTargetAsPayerErc20 || i !== maxSweepIndex));
131
+ // 支付者已单独预留 nonce,不能再进入批量查询。
132
+ const walletsToSweepErc20 = wallets.filter((w, i) => sweepAmounts[i] > 0n && (!payerAddressErc20 || w.address.toLowerCase() !== payerAddressErc20));
124
133
  const noncesErc20 = walletsToSweepErc20.length > 0 ? await nonceManager.getNextNoncesForWallets(walletsToSweepErc20) : [];
125
134
  let nonceIdxErc20 = 0;
126
135
  const txPromises = wallets.map(async (w, i) => {
@@ -129,10 +138,9 @@ export async function sweepErc20NoHop(ctx) {
129
138
  return null;
130
139
  // ✅ ERC20 归集:全部归集,不扣除代币(利润从 BNB 支付)
131
140
  const actualToSend = toSend;
132
- // ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce
133
141
  let nonce;
134
- if (!useTargetAsPayerErc20 && i === maxSweepIndex && payerProfitNonce !== undefined) {
135
- nonce = payerProfitNonce - 1; // 使用预留的第一个 nonce
142
+ if (payerMainNonce !== undefined && w.address.toLowerCase() === payerAddressErc20) {
143
+ nonce = payerMainNonce;
136
144
  }
137
145
  else {
138
146
  nonce = noncesErc20[nonceIdxErc20++];
@@ -16,7 +16,7 @@ import { sweepNativeNoHop } from './native-no-hop.js';
16
16
  import { sweepErc20NoHop } from './erc20-no-hop.js';
17
17
  import { sweepWithHops } from './with-hops.js';
18
18
  export async function sweepWithBundleMerkle(params) {
19
- const { sourcePrivateKeys, target, targetPrivateKey, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, } = params;
19
+ const { sourcePrivateKeys, target, targetPrivateKey, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce, } = params;
20
20
  // 快速返回空结果
21
21
  if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) {
22
22
  return {
@@ -45,6 +45,17 @@ export async function sweepWithBundleMerkle(params) {
45
45
  const hopCountInput = resolveHopCountInput(actualKeys.length, hopCount, sources);
46
46
  const preparedHops = providedHops ?? _generateHopWallets(actualKeys.length, hopCountInput);
47
47
  const hasHops = preparedHops !== null;
48
+ if (providedHops) {
49
+ // 中转交易当前按 nonce 0/1 组装;复用已发过交易的 hop key 会直接冲突。
50
+ for (const group of providedHops) {
51
+ for (const key of group) {
52
+ const address = new Wallet(key).address;
53
+ if (await provider.getTransactionCount(address, 'latest') > 0) {
54
+ throw new Error('hopPrivateKeys 中包含已使用的地址;请换用未发过交易的中转私钥');
55
+ }
56
+ }
57
+ }
58
+ }
48
59
  const maxHopCount = hasHops ? Math.max(...preparedHops.map((h) => h.length)) : 0;
49
60
  // 先按配置算上限,再向节点估算。估算成功就用估算值,归集才不会把没用完的 gas 退回钱包。
50
61
  let finalGasLimit = _calculateGasLimit(config, isNative, hasHops, maxHopCount);
@@ -74,6 +85,9 @@ export async function sweepWithBundleMerkle(params) {
74
85
  const bribeSignedTx = [];
75
86
  // ✅ 共享 NonceManager:统一协调 贿赂/利润提取 等 targetWallet 交易的 nonce,避免同一钱包 nonce 冲突
76
87
  const nonceManager = new NonceManager(provider);
88
+ if (startNonce !== undefined && actualKeys[0]) {
89
+ nonceManager.seedNextNonce(new Wallet(actualKeys[0]).address, startNonce);
90
+ }
77
91
  // ✅ 贿赂交易放在最前面(由 targetWallet 签名)
78
92
  if (needBribeTx && targetWallet) {
79
93
  const [bribeGasPrice, bribeNonce] = await Promise.all([
@@ -15,6 +15,7 @@ export async function sweepNativeNoHop(ctx) {
15
15
  let profitHopWallets;
16
16
  const wallets = actualKeys.map((pk) => new Wallet(pk, provider));
17
17
  const addresses = wallets.map((w) => w.address);
18
+ const targetIndexInSources = addresses.findIndex((addr) => addr.toLowerCase() === target.toLowerCase());
18
19
  // ✅ 复用主流程共享的 NonceManager(贿赂交易已用它预留 targetWallet 的 nonce,避免利润提取与之冲突)
19
20
  const nonceManager = ctx.nonceManager ?? new NonceManager(provider);
20
21
  // ✅ 优化:并行获取 gasPrice 和余额
@@ -91,13 +92,20 @@ export async function sweepNativeNoHop(ctx) {
91
92
  const profitPayerWallet = useTargetAsPayer ? targetWallet : maxSweepIndex >= 0 ? wallets[maxSweepIndex] : null;
92
93
  // ✅ 先为支付者预留 nonce,确保 nonce 连续
93
94
  let payerProfitNonce;
95
+ let payerMainNonce;
96
+ const payerAddress = extractProfit && totalProfit > 0n && profitPayerWallet
97
+ ? profitPayerWallet.address.toLowerCase()
98
+ : undefined;
94
99
  if (extractProfit && totalProfit > 0n && profitPayerWallet) {
95
- const nonces = await nonceManager.getNextNonceBatch(profitPayerWallet, useTargetAsPayer ? 1 : 2);
96
- payerProfitNonce = useTargetAsPayer ? nonces[0] : nonces[1]; // targetWallet 只需要 1 个 nonce
100
+ const payerIsSourceWithAmount = targetIndexInSources >= 0 && useTargetAsPayer && sweepAmounts[targetIndexInSources] > 0n;
101
+ const payerNeedsMain = !useTargetAsPayer || payerIsSourceWithAmount;
102
+ const nonces = await nonceManager.getNextNonceBatch(profitPayerWallet, payerNeedsMain ? 2 : 1);
103
+ if (payerNeedsMain)
104
+ payerMainNonce = nonces[0];
105
+ payerProfitNonce = payerNeedsMain ? nonces[1] : nonces[0];
97
106
  }
98
- // ✅ 优化:批量获取所有钱包的 nonce(JSON-RPC 批量请求)
99
- // 过滤出需要归集的钱包(如果用 targetWallet 支付利润,则不排除 maxSweepIndex)
100
- const walletsToSweep = wallets.filter((_, i) => sweepAmounts[i] > 0n && (useTargetAsPayer || i !== maxSweepIndex));
107
+ // 支付者已单独预留 nonce,不能再进入批量查询。
108
+ const walletsToSweep = wallets.filter((w, i) => sweepAmounts[i] > 0n && (!payerAddress || w.address.toLowerCase() !== payerAddress));
101
109
  const nonces = walletsToSweep.length > 0 ? await nonceManager.getNextNoncesForWallets(walletsToSweep) : [];
102
110
  let nonceIdx = 0;
103
111
  const txPromises = wallets.map(async (w, i) => {
@@ -108,17 +116,17 @@ export async function sweepNativeNoHop(ctx) {
108
116
  if (extractProfit && !useTargetAsPayer) {
109
117
  // ✅ 如果由源钱包支付利润,支付者扣除所有利润总和;其他钱包不扣利润,归集干净
110
118
  if (i === maxSweepIndex && totalProfit > 0n) {
111
- actualToSend = toSend - totalProfit; // 支付者扣除所有利润总和
119
+ actualToSend = toSend > totalProfit ? toSend - totalProfit : 0n; // 支付者扣除利润,不足则不发
112
120
  }
113
121
  else {
114
122
  actualToSend = toSend; // 其他钱包不扣利润,归集全部
115
123
  }
116
124
  }
117
125
  // ✅ 如果由 targetWallet 支付利润,所有源钱包都归集全部金额
118
- // ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce
126
+ // 支付者 actualToSend=0 时仍发 0 值交易,消费预留 nonce,避免利润 nonce 出现空洞。
119
127
  let nonce;
120
- if (!useTargetAsPayer && i === maxSweepIndex && payerProfitNonce !== undefined) {
121
- nonce = payerProfitNonce - 1; // 使用预留的第一个 nonce
128
+ if (payerMainNonce !== undefined && w.address.toLowerCase() === payerAddress) {
129
+ nonce = payerMainNonce;
122
130
  }
123
131
  else {
124
132
  nonce = nonces[nonceIdx++];
@@ -5,4 +5,4 @@ export { getErc20Decimals, getErc20DecimalsMerkle, generateHopWallets, normalize
5
5
  export { resolveChainName, createSignProvider, createSignWallet, resolveSignContext, normalizeItemsOrRecipientsAmounts, resolveProvidedHops, resolveHopCountInput, signBribeTransaction, type SignConfigInput, type HopItem, } from './sign-context-helpers.js';
6
6
  export { BUNDLE_ERRORS, ERRORS, getBundleErrorMessage, getErrorMessage } from './errors.js';
7
7
  export { getTxType, getGasPriceMultiplier, getBundleOptions, getGasPriceConfig, shouldExtractProfit, getProfitRateBps, getProfitRecipient, calculateProfit, calculateBatchProfit, getBribeAmount, type BundleGasConfigInput, type BundleMerkleConfigInput, } from './config-helpers.js';
8
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult, } from './submit.js';
8
+ export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult, } from './submit.js';
@@ -5,4 +5,4 @@ export { getErc20Decimals, getErc20DecimalsMerkle, generateHopWallets, normalize
5
5
  export { resolveChainName, createSignProvider, createSignWallet, resolveSignContext, normalizeItemsOrRecipientsAmounts, resolveProvidedHops, resolveHopCountInput, signBribeTransaction, } from './sign-context-helpers.js';
6
6
  export { BUNDLE_ERRORS, ERRORS, getBundleErrorMessage, getErrorMessage } from './errors.js';
7
7
  export { getTxType, getGasPriceMultiplier, getBundleOptions, getGasPriceConfig, shouldExtractProfit, getProfitRateBps, getProfitRecipient, calculateProfit, calculateBatchProfit, getBribeAmount, } from './config-helpers.js';
8
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, } from './submit.js';
8
+ export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, } from './submit.js';
@@ -22,6 +22,8 @@ export declare function estimateSweepLimits(params: {
22
22
  tokenAddress?: string;
23
23
  nativeFallback: bigint;
24
24
  tokenFallback: bigint;
25
+ /** 估算值安全垫;节点估值通常是刚好够,批量转账/合约收款方需要余量 */
26
+ bufferPercent?: number;
25
27
  }): Promise<{
26
28
  nativeGasLimit: bigint;
27
29
  tokenGasLimit: bigint;
@@ -131,6 +131,10 @@ export async function batchGetTokenAndNativeBalances(provider, addresses, tokenA
131
131
  }
132
132
  /** 归集/分发:按节点估算这笔转账的 gas。失败返回 null,调用方再用固定上限。 */
133
133
  export async function estimateSweepLimits(params) {
134
+ const withBuffer = (gas) => {
135
+ const pct = params.bufferPercent ?? 20;
136
+ return gas + (gas * BigInt(Math.max(0, Math.floor(pct)))) / 100n;
137
+ };
134
138
  try {
135
139
  if (!params.tokenAddress) {
136
140
  const gas = await params.provider.estimateGas({
@@ -139,7 +143,7 @@ export async function estimateSweepLimits(params) {
139
143
  value: 0n,
140
144
  });
141
145
  if (gas > 0n)
142
- return { nativeGasLimit: gas, tokenGasLimit: gas, estimated: true };
146
+ return { nativeGasLimit: withBuffer(gas), tokenGasLimit: withBuffer(gas), estimated: true };
143
147
  }
144
148
  else {
145
149
  const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
@@ -155,8 +159,8 @@ export async function estimateSweepLimits(params) {
155
159
  ]);
156
160
  if (tokenGas > 0n) {
157
161
  return {
158
- nativeGasLimit: nativeGas > 0n ? nativeGas : params.nativeFallback,
159
- tokenGasLimit: tokenGas,
162
+ nativeGasLimit: nativeGas > 0n ? withBuffer(nativeGas) : params.nativeFallback,
163
+ tokenGasLimit: withBuffer(tokenGas),
160
164
  estimated: true,
161
165
  };
162
166
  }
@@ -2,13 +2,11 @@
2
2
  * Bundle 提交 barrel
3
3
  *
4
4
  * 原超长文件按提交后端拆分为:
5
- * - helpers.ts:Merkle / BlockRazor 共用缓存与批量提交辅助
6
- * - merkle.ts:Merkle 提交后端
5
+ * - helpers.ts:BlockRazor 共用批量提交辅助
7
6
  * - blockrazor.ts:BlockRazor 提交后端
8
7
  * - direct.ts:逐笔 RPC 广播后端
9
8
  *
10
9
  * 对外 export 符号与路径保持不变。
11
10
  */
12
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, type MerkleSubmitConfig, type SubmitBundleResult, } from './merkle.js';
13
11
  export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, } from './blockrazor.js';
14
12
  export { submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, type DirectSubmitConfig, type DirectTxResult, type DirectSubmitResult, } from './direct.js';
@@ -2,13 +2,11 @@
2
2
  * Bundle 提交 barrel
3
3
  *
4
4
  * 原超长文件按提交后端拆分为:
5
- * - helpers.ts:Merkle / BlockRazor 共用缓存与批量提交辅助
6
- * - merkle.ts:Merkle 提交后端
5
+ * - helpers.ts:BlockRazor 共用批量提交辅助
7
6
  * - blockrazor.ts:BlockRazor 提交后端
8
7
  * - direct.ts:逐笔 RPC 广播后端
9
8
  *
10
9
  * 对外 export 符号与路径保持不变。
11
10
  */
12
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, } from './merkle.js';
13
11
  export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, } from './blockrazor.js';
14
12
  export { submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, } from './direct.js';
@@ -5,6 +5,6 @@
5
5
  */
6
6
  export * from './constants.js';
7
7
  export * as platforms from './platforms/index.js';
8
- export { disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle, fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle, fourBundleBuyFirstMerkle, createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourBatchPrivateBuyMerkle, submitBundleToMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, approveFourTokenManagerBatch, } from './platforms/four/index.js';
8
+ export { disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle, fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle, fourBundleBuyFirstMerkle, createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourBatchPrivateBuyMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, approveFourTokenManagerBatch, } from './platforms/four/index.js';
9
9
  export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle, pancakeCrossSwapMerkle, pancakeBundleBuyFirstMerkle, } from './platforms/pancake/index.js';
10
10
  export { IroFactoryQuery, createProject as iroCreateProject, encodeCreateProjectCall as iroEncodeCreateProjectCall, parseCreateProjectEvent as iroParseCreateProjectEvent, IroTokenQuery, subscribe as iroSubscribe, pledge as iroPledge, batchSubscribe as iroBatchSubscribe, batchSubscribeForSubmit as iroBatchSubscribeForSubmit, IroPoolQuery, poolRemovePledge as iroPoolRemovePledge, poolExtract as iroPoolExtract, poolTransferAwards as iroPoolTransferAwards, batchExtract as iroBatchExtract, IRO_FACTORY_ADDRESS, IRO_FACTORY_ABI, IRO_TOKEN_ABI, IRO_POOL_ABI, CREATE_PROJECT_GAS_LIMIT as BSC_CREATE_PROJECT_GAS_LIMIT, IRO_PLATFORM_URL, IRO_TESTNET_PLATFORM_URL, setIroApiBase, setIroTestApiBase, iroApiCreateProject, iroApiUploadImage, } from './platforms/iro/index.js';
@@ -20,7 +20,7 @@ fourBundleBuyFirstMerkle,
20
20
  // 发币 / 批量卖 / 私有买入(消费者白名单别名)
21
21
  createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourBatchPrivateBuyMerkle,
22
22
  // 提交
23
- submitBundleToMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart,
23
+ submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart,
24
24
  // 授权
25
25
  approveFourTokenManagerBatch, } from './platforms/four/index.js';
26
26
  // ============================================================================
@@ -48,7 +48,8 @@ export interface V3MultiRouteParams {
48
48
  }
49
49
  export type RouteParams = V2RouteParams | V3SingleRouteParams | V3MultiRouteParams;
50
50
  export interface PancakeBuyFirstConfig extends CommonBundleConfig {
51
- apiKey: string;
51
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容,不再读取 */
52
+ apiKey?: string;
52
53
  customRpcUrl?: string;
53
54
  bundleBlockOffset?: number;
54
55
  reserveGasBNB?: number;
@@ -103,7 +103,8 @@ export interface PancakeSwapSignConfig {
103
103
  v2RouterAddress?: string;
104
104
  }
105
105
  export interface PancakeSwapConfig extends CommonBundleConfig {
106
- apiKey: string;
106
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容,不再读取 */
107
+ apiKey?: string;
107
108
  customRpcUrl?: string;
108
109
  bundleBlockOffset?: number;
109
110
  reserveGasBNB?: number;
@@ -19,6 +19,6 @@ export { sweepWithBundleMerkle } from '../../../../bundle/four-meme/utils-sweep.
19
19
  export { pairwiseTransferWithBundleMerkle } from '../../../../bundle/four-meme/utils-pairwise.js';
20
20
  export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, } from '../../../../bundle/four-meme/pancake-proxy.js';
21
21
  export { approveFourTokenManagerBatch } from '../../../../bundle/four-meme/approve-tokenmanager.js';
22
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult, } from '../../../../bundle/submit.js';
22
+ export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, type DirectSubmitConfig, type DirectSubmitResult, type DirectTxResult, } from '../../../../bundle/submit.js';
23
23
  export { fourBundleBuyFirstMerkle } from '../../../../bundle/four-meme/swap-buy-first.js';
24
24
  export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle, } from '../../../../bundle/four-meme/swap.js';
@@ -24,7 +24,7 @@ export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approv
24
24
  // TokenManager 授权方法
25
25
  export { approveFourTokenManagerBatch } from '../../../../bundle/four-meme/approve-tokenmanager.js';
26
26
  // Bundle 提交方法(服务器端使用)
27
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, } from '../../../../bundle/submit.js';
27
+ export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, submitDirectToRpc, submitDirectToRpcSequential, submitDirectToRpcSequentialNoWait, submitDirectToRpcParallel, submitDirectToRpcSmart, } from '../../../../bundle/submit.js';
28
28
  // 先买后卖换手方法
29
29
  export { fourBundleBuyFirstMerkle } from '../../../../bundle/four-meme/swap-buy-first.js';
30
30
  // 换手方法
@@ -9,6 +9,7 @@ import { Wallet, Interface } from 'ethers';
9
9
  import { ENI_CHAIN_ID, ENI_RPC_URL } from '../constants.js';
10
10
  import { createEniProvider } from '../../../utils/provider-factory.js';
11
11
  import { ERC20_ABI } from '../../../abis/common.js';
12
+ import { estimateApproveGasLimit } from '../../../core/gas/bundle-gas.js';
12
13
  const MAX_UINT256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
13
14
  const erc20Iface = new Interface(ERC20_ABI);
14
15
  /**
@@ -33,7 +34,7 @@ export async function batchApprove(params) {
33
34
  type: 2,
34
35
  maxFeePerGas,
35
36
  maxPriorityFeePerGas,
36
- gasLimit: 60000n,
37
+ gasLimit: await estimateApproveGasLimit(provider, wallet.address, token, calldata),
37
38
  });
38
39
  return { signedTx, from: wallet.address, nonce };
39
40
  }));
@@ -14,6 +14,7 @@ import { ERC20_ABI } from '../../../abis/common.js';
14
14
  import { getProfitRecipient, calculateTransferFee } from '../../../core/constants/profit.js';
15
15
  import { GAS_LIMITS } from '../../../core/constants/gas.js';
16
16
  import { erc20Iface, DEFAULT_GAS_PER_SWEEP, GAS_NATIVE_TRANSFER } from './transfer-shared.js';
17
+ import { estimateApproveGasLimit } from '../../../core/gas/bundle-gas.js';
17
18
  // ============================================================================
18
19
  // 批量分发 EGAS (通过 BatchRouter 合约, 1 笔 tx)
19
20
  // ============================================================================
@@ -118,7 +119,7 @@ export async function eniDisperseForSubmit(params) {
118
119
  type: 2,
119
120
  maxFeePerGas,
120
121
  maxPriorityFeePerGas,
121
- gasLimit: 60000n,
122
+ gasLimit: await estimateApproveGasLimit(provider, mainWallet.address, tokenAddress, erc20Iface.encodeFunctionData('approve', [routerAddr, ethers.MaxUint256])),
122
123
  });
123
124
  signedTxs.push(approveTx);
124
125
  }
@@ -173,7 +173,7 @@ export async function addLiquidityETHForSubmit(params) {
173
173
  type: 2,
174
174
  maxFeePerGas,
175
175
  maxPriorityFeePerGas,
176
- gasLimit: 60000n,
176
+ gasLimit: 150000n,
177
177
  });
178
178
  signedTxs.push(approveTx);
179
179
  }
@@ -280,7 +280,7 @@ export async function removeLiquidityETHForSubmit(params) {
280
280
  type: 2,
281
281
  maxFeePerGas,
282
282
  maxPriorityFeePerGas,
283
- gasLimit: 60000n,
283
+ gasLimit: 150000n,
284
284
  });
285
285
  signedTxs.push(approveTx);
286
286
  }
@@ -364,7 +364,7 @@ export async function addLiquidityForSubmit(params) {
364
364
  type: 2,
365
365
  maxFeePerGas,
366
366
  maxPriorityFeePerGas,
367
- gasLimit: 60000n,
367
+ gasLimit: 150000n,
368
368
  });
369
369
  signedTxs.push(approveTx);
370
370
  }
@@ -439,7 +439,7 @@ export async function removeLiquidityForSubmit(params) {
439
439
  type: 2,
440
440
  maxFeePerGas,
441
441
  maxPriorityFeePerGas,
442
- gasLimit: 60000n,
442
+ gasLimit: 150000n,
443
443
  });
444
444
  signedTxs.push(approveTx);
445
445
  }