four-flap-meme-sdk 4.4.3 → 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 (52) hide show
  1. package/README.md +16 -22
  2. package/dist/bundle/four-meme/types/buy-first.d.ts +2 -1
  3. package/dist/bundle/four-meme/types/config.d.ts +2 -1
  4. package/dist/bundle/four-meme/types/swap-types.d.ts +2 -1
  5. package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +16 -8
  6. package/dist/bundle/four-meme/utils-sweep/main.js +15 -1
  7. package/dist/bundle/four-meme/utils-sweep/native-no-hop.js +17 -9
  8. package/dist/bundle/index.d.ts +1 -1
  9. package/dist/bundle/index.js +1 -1
  10. package/dist/bundle/internal.d.ts +2 -0
  11. package/dist/bundle/internal.js +7 -3
  12. package/dist/bundle/submit/index.d.ts +1 -3
  13. package/dist/bundle/submit/index.js +1 -3
  14. package/dist/chains/bsc/index.d.ts +1 -1
  15. package/dist/chains/bsc/index.js +1 -1
  16. package/dist/chains/bsc/pancake/bundle-buy-first-types.d.ts +2 -1
  17. package/dist/chains/bsc/pancake/bundle-swap-types.d.ts +2 -1
  18. package/dist/chains/bsc/platforms/four/index.d.ts +1 -1
  19. package/dist/chains/bsc/platforms/four/index.js +1 -1
  20. package/dist/core/clients/blockrazor.js +8 -6
  21. package/dist/core/clients/blockrazor.test.d.ts +1 -0
  22. package/dist/core/clients/blockrazor.test.js +23 -0
  23. package/dist/core/clients/index.d.ts +0 -1
  24. package/dist/core/clients/index.js +0 -1
  25. package/dist/core/clients/merkle.js +12 -7
  26. package/dist/core/clients/merkle.test.d.ts +1 -0
  27. package/dist/core/clients/merkle.test.js +8 -0
  28. package/dist/core/nonce/nonce-manager.d.ts +2 -0
  29. package/dist/core/nonce/nonce-manager.js +21 -1
  30. package/dist/core/tx/wallet-sign-patch.js +22 -11
  31. package/dist/core/tx/wallet-sign-patch.test.d.ts +1 -0
  32. package/dist/core/tx/wallet-sign-patch.test.js +21 -0
  33. package/dist/domains/dex/direct-router-v2-buy.js +13 -0
  34. package/dist/domains/flap/portal-bundle-merkle/private/buy.js +3 -3
  35. package/dist/domains/flap/portal-bundle-merkle/private/helpers.d.ts +1 -1
  36. package/dist/domains/flap/portal-bundle-merkle/private/helpers.js +4 -8
  37. package/dist/domains/flap/portal-bundle-merkle/private/sell.js +3 -3
  38. package/dist/domains/flap/portal-bundle-merkle/swap-buy-first-types.d.ts +2 -1
  39. package/dist/domains/flap/portal-bundle-merkle/swap-helpers.d.ts +2 -1
  40. package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +5 -2
  41. package/dist/domains/flap/portal-bundle-merkle/swap-planning.test.d.ts +1 -0
  42. package/dist/domains/flap/portal-bundle-merkle/swap-planning.test.js +16 -0
  43. package/dist/domains/flap/portal-bundle-merkle/types-config.d.ts +2 -1
  44. package/dist/domains/merkle/index.d.ts +1 -1
  45. package/dist/domains/merkle/index.js +1 -1
  46. package/dist/exports/root-bundle-and-tooling.d.ts +1 -1
  47. package/dist/exports/root-bundle-and-tooling.js +1 -1
  48. package/dist/exports/root-foundations.d.ts +0 -1
  49. package/dist/exports/root-foundations.js +0 -1
  50. package/dist/utils/swap-helpers.d.ts +1 -15
  51. package/dist/utils/swap-helpers.js +0 -80
  52. 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),确保所有地址有足够余额,适当增大超时时间。
@@ -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
  // 换手方法
@@ -146,16 +146,18 @@ export class BlockRazorClient {
146
146
  }
147
147
  // 验证 Gas Price(最低 0.05 Gwei)
148
148
  for (const rawTx of params.txs) {
149
+ let tx;
149
150
  try {
150
- const tx = Transaction.from(rawTx);
151
- const gasPrice = tx.gasPrice || 0n;
152
- const minGasPrice = ethers.parseUnits(String(MIN_GAS_PRICE_GWEI), 'gwei');
153
- if (gasPrice < minGasPrice) {
154
- throw new Error(`Transaction gas price (${ethers.formatUnits(gasPrice, 'gwei')} gwei) is below minimum (${MIN_GAS_PRICE_GWEI} gwei)`);
155
- }
151
+ tx = Transaction.from(rawTx);
156
152
  }
157
153
  catch {
158
154
  // 解析失败,跳过检查
155
+ continue;
156
+ }
157
+ const gasPrice = tx.gasPrice && tx.gasPrice > 0n ? tx.gasPrice : tx.maxFeePerGas ?? 0n;
158
+ const minGasPrice = ethers.parseUnits(String(MIN_GAS_PRICE_GWEI), 'gwei');
159
+ if (gasPrice < minGasPrice) {
160
+ throw new Error(`Transaction gas price (${ethers.formatUnits(gasPrice, 'gwei')} gwei) is below minimum (${MIN_GAS_PRICE_GWEI} gwei)`);
159
161
  }
160
162
  }
161
163
  // 构建请求参数
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { Wallet } from 'ethers';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { BlockRazorClient } from './blockrazor.js';
4
+ describe('BlockRazor gas price validation', () => {
5
+ it('低于 0.05 Gwei 的签名交易被拒绝', async () => {
6
+ const client = new BlockRazorClient({
7
+ apiKey: 'test',
8
+ chainId: 56,
9
+ customRpcUrl: 'http://127.0.0.1:1',
10
+ });
11
+ const wallet = Wallet.createRandom();
12
+ const raw = await wallet.signTransaction({
13
+ to: Wallet.createRandom().address,
14
+ value: 0n,
15
+ nonce: 0,
16
+ gasLimit: 21000n,
17
+ gasPrice: 1n,
18
+ chainId: 56,
19
+ type: 0,
20
+ });
21
+ await expect(client.sendBundleRaw({ txs: [raw] })).rejects.toThrow(/below minimum/);
22
+ });
23
+ });
@@ -3,6 +3,5 @@
3
3
  */
4
4
  export { FourClient, buildLoginMessage, buildCreateTokenReq, type FourConfig, type GenerateNonceReq, type LoginReq, type CreateTokenReq, type CreateTokenResp, type FourTokenTaxInfo, type FourRaisedToken, type FourTokenLabel, } from './four.js';
5
5
  export { Club48Client, sendBatchPrivateTransactions, sendBackrunBundle, type BundleParams, type BundleStatus, type Club48Config, } from './club48.js';
6
- export { MerkleClient, createMerkleClient, type MerkleConfig, type BundleParams as MerkleBundleParams, type SendBundleOptions, type BundleResult, type TransactionResult, } from './merkle.js';
7
6
  export { BlockRazorClient, createBlockRazorClient, BLOCKRAZOR_BUILDER_EOA, type BlockRazorConfig, type BlockRazorBundleParams, type SendBundleOptions as BlockRazorSendBundleOptions, type BundleResult as BlockRazorBundleResult, type TransactionResult as BlockRazorTransactionResult, type IncentiveTransactionParams, } from './blockrazor.js';
8
7
  export { EmitServiceClient, submitToEmitServiceBlockRazor, type EmitServiceClientConfig, type EmitServiceSubmitRequest, type EmitServiceSubmitResponse, type EmitServiceSubmitResponseData, type EmitServiceRouteConfigResponse, } from './emitservice.js';
@@ -3,6 +3,5 @@
3
3
  */
4
4
  export { FourClient, buildLoginMessage, buildCreateTokenReq, } from './four.js';
5
5
  export { Club48Client, sendBatchPrivateTransactions, sendBackrunBundle, } from './club48.js';
6
- export { MerkleClient, createMerkleClient, } from './merkle.js';
7
6
  export { BlockRazorClient, createBlockRazorClient, BLOCKRAZOR_BUILDER_EOA, } from './blockrazor.js';
8
7
  export { EmitServiceClient, submitToEmitServiceBlockRazor, } from './emitservice.js';
@@ -38,12 +38,15 @@ export class MerkleClient {
38
38
  constructor(config) {
39
39
  this.chainId = config.chainId;
40
40
  this.chainName = config.chainId === CHAINS.BSC.chainId ? 'bsc' : 'ethereum';
41
- // ✅ 修复:移除弃用的 mempool.merkle.io RPC,使用公共 RPC 作为默认值
42
- // 优先使用用户自定义 RPC,否则使用官方公共 RPC
43
- const defaultRpc = config.chainId === CHAINS.BSC.chainId
44
- ? CHAINS.BSC.rpcUrls.backup // bsc-dataseed1
45
- : CHAINS.ETHEREUM.rpcUrls.default;
46
- const rpcUrl = config.customRpcUrl || defaultRpc;
41
+ // Blink/Merkle 的 bundle RPC 是带 API key 的节点 endpoint;公共 BSC 节点
42
+ // 不支持 eth_sendBundle。customRpcUrl 可显式覆盖(需自带 key)。
43
+ const bundleBase = config.chainId === CHAINS.BSC.chainId
44
+ ? CHAINS.BSC.bundleRpcUrl
45
+ : CHAINS.ETHEREUM.bundleRpcUrl;
46
+ const rpcUrl = config.customRpcUrl || (bundleBase ? `${bundleBase}/v1/${config.apiKey}` : '');
47
+ if (!rpcUrl) {
48
+ throw new Error('Merkle bundle RPC endpoint is not configured');
49
+ }
47
50
  this.provider = createProvider(rpcUrl, this.chainId, this.chainName);
48
51
  }
49
52
  /**
@@ -366,10 +369,12 @@ export class MerkleClient {
366
369
  */
367
370
  getProviderInfo() {
368
371
  const rpcConnection = this.provider._getConnection?.();
372
+ const rawUrl = rpcConnection?.url ?? 'unknown';
369
373
  return {
370
374
  chainId: this.chainId,
371
375
  chainName: this.chainName,
372
- rpcUrl: rpcConnection?.url ?? 'unknown',
376
+ // API key 在路径中,调试输出只保留脱敏后的 endpoint。
377
+ rpcUrl: rawUrl.replace(/\/v1\/[^/?#]+/, '/v1/***'),
373
378
  };
374
379
  }
375
380
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { MerkleClient } from './merkle.js';
3
+ describe('MerkleClient endpoint', () => {
4
+ it('默认使用带 apiKey 的 Blink BSC bundle RPC', () => {
5
+ const client = new MerkleClient({ apiKey: 'test-key', chainId: 56 });
6
+ expect(client.getProviderInfo().rpcUrl).toContain('https://bsc.blinklabs.xyz/v1/***');
7
+ });
8
+ });
@@ -14,6 +14,8 @@ export declare class NonceManager {
14
14
  private readOnchainNonce;
15
15
  getNextNonce(wallet: Wallet | string): Promise<number>;
16
16
  getNextNonceBatch(wallet: Wallet | string, count: number): Promise<number[]>;
17
+ /** 预填某个地址的下一个 nonce(用于调用方显式 startNonce 的并行场景)。 */
18
+ seedNextNonce(wallet: Wallet | string, nextNonce: number): void;
17
19
  clearTemp(): void;
18
20
  getTempCachedNonce(address: string): number | undefined;
19
21
  getNextNoncesForWallets(wallets: (Wallet | string)[]): Promise<number[]>;
@@ -157,6 +157,14 @@ export class NonceManager {
157
157
  return Array.from({ length: count }, (_, i) => startNonce + i);
158
158
  });
159
159
  }
160
+ /** 预填某个地址的下一个 nonce(用于调用方显式 startNonce 的并行场景)。 */
161
+ seedNextNonce(wallet, nextNonce) {
162
+ if (!Number.isInteger(nextNonce) || nextNonce < 0)
163
+ return;
164
+ const address = typeof wallet === 'string' ? wallet : wallet.address;
165
+ const key = address.toLowerCase();
166
+ this.tempNonceCache.set(key, nextNonce);
167
+ }
160
168
  clearTemp() {
161
169
  this.tempNonceCache.clear();
162
170
  }
@@ -210,10 +218,22 @@ export class NonceManager {
210
218
  const rawResults = await asBatchSendProvider(this.provider)._send(batchRequests);
211
219
  const byId = new Map();
212
220
  for (const row of rawResults) {
221
+ if (row.error || row.result == null) {
222
+ throw new Error(`batch eth_getTransactionCount failed: ${row.error?.message || 'missing result'}`);
223
+ }
213
224
  const id = Number(row.id);
225
+ if (!Number.isInteger(id))
226
+ throw new Error('batch eth_getTransactionCount returned invalid id');
214
227
  byId.set(id, parseRpcQuantity(row.result));
215
228
  }
216
- queryResults = uniqueQueries.map((_, idx) => Math.max(byId.get(idx * 2 + 1) || 0, byId.get(idx * 2 + 2) || 0));
229
+ queryResults = uniqueQueries.map((_, idx) => {
230
+ const latest = byId.get(idx * 2 + 1);
231
+ const pending = byId.get(idx * 2 + 2);
232
+ if (latest === undefined || pending === undefined) {
233
+ throw new Error('batch eth_getTransactionCount returned incomplete result');
234
+ }
235
+ return Math.max(latest, pending);
236
+ });
217
237
  }
218
238
  catch {
219
239
  queryResults = await Promise.all(uniqueQueries.map(({ address }) => this.readOnchainNonce(address)));
@@ -46,18 +46,29 @@ Wallet.prototype.signTransaction = async function (tx) {
46
46
  /* 节点没给出 gas 价格时保留交易上已有的出价 */
47
47
  }
48
48
  }
49
- try {
50
- const estimated = await provider.estimateGas({
51
- from: signable.from ?? this.address,
52
- to: signable.to,
53
- data: signable.data,
54
- value: signable.value,
55
- });
56
- if (estimated > 0n)
57
- signable = { ...signable, gasLimit: estimated };
49
+ let hasGasLimit = false;
50
+ if (signable.gasLimit != null) {
51
+ try {
52
+ hasGasLimit = BigInt(signable.gasLimit) > 0n;
53
+ }
54
+ catch {
55
+ hasGasLimit = false;
56
+ }
58
57
  }
59
- catch {
60
- /* 节点没给出 gas 上限时保留原来的上限,避免发不出去 */
58
+ if (!hasGasLimit) {
59
+ try {
60
+ const estimated = await provider.estimateGas({
61
+ from: signable.from ?? this.address,
62
+ to: signable.to,
63
+ data: signable.data,
64
+ value: signable.value,
65
+ });
66
+ if (estimated > 0n)
67
+ signable = { ...signable, gasLimit: estimated };
68
+ }
69
+ catch {
70
+ /* 节点没给出 gas 上限时保留原来的上限,避免发不出去 */
71
+ }
61
72
  }
62
73
  }
63
74
  return _origWalletSign.call(this, signable);
@@ -0,0 +1 @@
1
+ import './wallet-sign-patch.js';
@@ -0,0 +1,21 @@
1
+ import { Transaction, Wallet } from 'ethers';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import './wallet-sign-patch.js';
4
+ describe('wallet sign patch gasLimit', () => {
5
+ it('保留调用方显式 gasLimit,不被 estimateGas 覆盖', async () => {
6
+ const estimateGas = vi.fn(async () => 21000n);
7
+ const provider = { estimateGas };
8
+ const wallet = new Wallet(Wallet.createRandom().privateKey, provider);
9
+ const raw = await wallet.signTransaction({
10
+ to: Wallet.createRandom().address,
11
+ value: 0n,
12
+ nonce: 0,
13
+ gasLimit: 500000n,
14
+ gasPrice: 1000000000n,
15
+ chainId: 56,
16
+ type: 0,
17
+ });
18
+ expect(Transaction.from(raw).gasLimit).toBe(500000n);
19
+ expect(estimateGas).not.toHaveBeenCalled();
20
+ });
21
+ });
@@ -71,6 +71,19 @@ export async function directV2BatchBuy(params) {
71
71
  activeNonces = activeIndices.map((i) => nonces[i]);
72
72
  activeBuyAllowances = activeIndices.map((i) => buyAllowances[i]);
73
73
  }
74
+ // 0 金额钱包不参与 swap;否则 amountIn=0 会让整包 revert,拖死正常钱包。
75
+ const positiveIndices = activeFlowAmounts
76
+ .map((amount, index) => (amount > 0n ? index : -1))
77
+ .filter((index) => index >= 0);
78
+ if (positiveIndices.length === 0) {
79
+ throw new Error('所有买方金额为 0,无法执行');
80
+ }
81
+ if (positiveIndices.length !== activeWallets.length) {
82
+ activeWallets = positiveIndices.map((i) => activeWallets[i]);
83
+ activeFlowAmounts = positiveIndices.map((i) => activeFlowAmounts[i]);
84
+ activeNonces = positiveIndices.map((i) => activeNonces[i]);
85
+ activeBuyAllowances = positiveIndices.map((i) => activeBuyAllowances[i]);
86
+ }
74
87
  const activeTotalFlowWei = activeFlowAmounts.reduce((sum, amt) => sum + amt, 0n);
75
88
  const activeBaseProfitWei = calculateProfitAmount(activeTotalFlowWei);
76
89
  let activeProfitWei = activeBaseProfitWei > 0n ? activeBaseProfitWei : 0n;
@@ -4,13 +4,13 @@ import { FLAP_PORTAL_ADDRESSES } from '../../constants.js';
4
4
  import { ZERO_ADDRESS } from '../../../../core/constants/index.js';
5
5
  import { GAS_LIMITS } from '../../../../core/constants/index.js';
6
6
  import { PORTAL_ABI, getTxType, getGasPriceConfig, shouldExtractProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, } from '../config.js';
7
- import { createMerkleContext, splitBuyAmount, parseMinOutput, resolveGasLimit, resolveBatchMinOutputs, findMaxIndex, signBatchTransactions, } from './helpers.js';
7
+ import { createPrivateContext, splitBuyAmount, parseMinOutput, resolveGasLimit, resolveBatchMinOutputs, findMaxIndex, signBatchTransactions, } from './helpers.js';
8
8
  /**
9
9
  * 私有购买(单笔)(Merkle 版本)
10
10
  */
11
11
  export async function flapPrivateBuyMerkle(params) {
12
12
  const { chain, privateKey, tokenAddress, amountIn, minOutputAmount, config } = params;
13
- const { chainId, provider } = createMerkleContext(chain, config);
13
+ const { chainId, provider } = createPrivateContext(chain, config);
14
14
  const wallet = new Wallet(privateKey, provider);
15
15
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
16
16
  const originalAmountWei = ethers.parseEther(amountIn);
@@ -97,7 +97,7 @@ export async function flapBatchPrivateBuyMerkle(params) {
97
97
  if (privateKeys.length !== amountsIn.length) {
98
98
  throw new Error('privateKeys and amountsIn length mismatch');
99
99
  }
100
- const { chainId, provider } = createMerkleContext(chain, config);
100
+ const { chainId, provider } = createPrivateContext(chain, config);
101
101
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
102
102
  const wallets = privateKeys.map((k) => new Wallet(k, provider));
103
103
  const originalAmountsWei = amountsIn.map((a) => ethers.parseEther(a));
@@ -2,7 +2,7 @@ import { ethers, Wallet } from 'ethers';
2
2
  import type { FlapPrivateBuyMerkleParams } from '../types.js';
3
3
  type FlapChain = FlapPrivateBuyMerkleParams['chain'];
4
4
  type FlapConfig = FlapPrivateBuyMerkleParams['config'];
5
- export declare function createMerkleContext(chain: FlapChain, config: FlapConfig): {
5
+ export declare function createPrivateContext(chain: FlapChain, config: FlapConfig): {
6
6
  chainId: number;
7
7
  provider: ethers.JsonRpcProvider;
8
8
  };
@@ -1,10 +1,9 @@
1
1
  import { ethers } from 'ethers';
2
- import { MerkleClient } from '../../../../core/clients/merkle.js';
3
2
  import { createProvider } from '../../../../utils/provider-factory.js';
4
3
  import { ZERO_ADDRESS, CHAINS, applySlippageBps } from '../../../../core/constants/index.js';
5
4
  import { CHAIN_ID_MAP, getTxType, calculateProfit, } from '../config.js';
6
5
  const DEFAULT_GAS_LIMIT = 800000;
7
- export function createMerkleContext(chain, config) {
6
+ export function createPrivateContext(chain, config) {
8
7
  const chainId = CHAIN_ID_MAP[chain];
9
8
  // ✅ 检查是否为 Merkle 支持的链(BSC, ETH)
10
9
  // Monad 不支持 Merkle 捆绑服务,使用普通 RPC
@@ -19,14 +18,11 @@ export function createMerkleContext(chain, config) {
19
18
  provider: createProvider(config.customRpcUrl, chainId, chain.toLowerCase()),
20
19
  };
21
20
  }
22
- const merkle = new MerkleClient({
23
- apiKey: config.apiKey,
24
- chainId: chainId, // 类型断言为 Merkle 支持的 chainId
25
- customRpcUrl: config.customRpcUrl,
26
- });
21
+ const rpcUrl = config.customRpcUrl
22
+ || (chainId === CHAINS.BSC.chainId ? CHAINS.BSC.rpcUrls.default : CHAINS.ETHEREUM.rpcUrls.default);
27
23
  return {
28
24
  chainId,
29
- provider: merkle.getProvider(),
25
+ provider: createProvider(rpcUrl, chainId, chain.toLowerCase()),
30
26
  };
31
27
  }
32
28
  export function splitBuyAmount(amount, extractProfit) {
@@ -4,13 +4,13 @@ import { FLAP_PORTAL_ADDRESSES } from '../../constants.js';
4
4
  import { ZERO_ADDRESS, applySlippageBps } from '../../../../core/constants/index.js';
5
5
  import { GAS_LIMITS } from '../../../../core/constants/index.js';
6
6
  import { PORTAL_ABI, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, } from '../config.js';
7
- import { createMerkleContext, resolveGasLimit, resolveSingleSellOutputs, signBatchTransactions, summarizeSellProfits, } from './helpers.js';
7
+ import { createPrivateContext, resolveGasLimit, resolveSingleSellOutputs, signBatchTransactions, summarizeSellProfits, } from './helpers.js';
8
8
  /**
9
9
  * 私有卖出(单笔)(Merkle 版本)
10
10
  */
11
11
  export async function flapPrivateSellMerkle(params) {
12
12
  const { chain, privateKey, tokenAddress, amount, minOutputAmount, config } = params;
13
- const { chainId, provider } = createMerkleContext(chain, config);
13
+ const { chainId, provider } = createPrivateContext(chain, config);
14
14
  const wallet = new Wallet(privateKey, provider);
15
15
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
16
16
  const amountWei = ethers.parseUnits(amount, 18);
@@ -95,7 +95,7 @@ export async function flapBatchPrivateSellMerkle(params) {
95
95
  if (privateKeys.length !== amounts.length) {
96
96
  throw new Error('privateKeys and amounts length mismatch');
97
97
  }
98
- const { chainId, provider } = createMerkleContext(chain, config);
98
+ const { chainId, provider } = createPrivateContext(chain, config);
99
99
  const portalAddr = FLAP_PORTAL_ADDRESSES[chain];
100
100
  const wallets = privateKeys.map((k) => new Wallet(k, provider));
101
101
  const amountsWei = amounts.map((a) => ethers.parseUnits(a, 18));
@@ -13,7 +13,8 @@ export interface FlapBuyFirstSignConfig extends FlapSignConfig {
13
13
  userType?: UserType;
14
14
  }
15
15
  export interface FlapBuyFirstConfig extends CommonBundleConfig {
16
- apiKey: string;
16
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容 */
17
+ apiKey?: string;
17
18
  customRpcUrl?: string;
18
19
  bundleBlockOffset?: number;
19
20
  reserveGasETH?: number;
@@ -49,7 +49,8 @@ export declare const MONAD_WMON: "0x3bd359c1119da7da1d913d1c4d2b7c461115433a";
49
49
  export declare function getTokenToNativeQuote(provider: ethers.JsonRpcProvider, tokenAddress: string, tokenAmount: bigint, chainId: number): Promise<bigint>;
50
50
  export type FlapChain = 'bsc' | 'xlayer' | 'base';
51
51
  export interface FlapSwapConfig extends CommonBundleConfig {
52
- apiKey: string;
52
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容 */
53
+ apiKey?: string;
53
54
  customRpcUrl?: string;
54
55
  bundleBlockOffset?: number;
55
56
  reserveGasETH?: number;
@@ -100,6 +100,9 @@ export async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, n
100
100
  buyerBalance = await erc20.balanceOf(buyer.address);
101
101
  }
102
102
  const estimatedBuyerNeed = quotedNative;
103
+ if (estimatedBuyerNeed <= 0n) {
104
+ throw new Error('卖出报价为 0,拒绝用买方全部余额买入;请稍后重试或检查池子流动性');
105
+ }
103
106
  const buyerNeedTotal = useNativeToken ? estimatedBuyerNeed + reserveGas : estimatedBuyerNeed;
104
107
  if (buyerBalance < buyerNeedTotal) {
105
108
  if (useNativeToken) {
@@ -113,7 +116,7 @@ export async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, n
113
116
  ` - 实际: ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
114
117
  }
115
118
  }
116
- const maxBuyerValue = estimatedBuyerNeed > 0n ? estimatedBuyerNeed : useNativeToken ? buyerBalance - reserveGas : buyerBalance;
119
+ const maxBuyerValue = estimatedBuyerNeed;
117
120
  return { reserveGas, buyerBalance, buyerNeedTotal, maxBuyerValue };
118
121
  }
119
122
  export async function validateBalances({ buyerAddress, portalGasCost, provider, chainContext, seller, useNativeToken, }) {
@@ -168,7 +171,7 @@ export async function planNonces({ seller, buyer, approvalExists, extractProfit,
168
171
  return { sellerNonce: nonces[0], buyerNonce: nonces[1] };
169
172
  }
170
173
  export async function buildProfitTransaction({ provider, seller, profitAmount, profitNonce, gasPrice, chainId, txType, }) {
171
- if (!profitNonce || profitAmount === 0n) {
174
+ if (profitNonce === undefined || profitAmount === 0n) {
172
175
  return null;
173
176
  }
174
177
  const profitHopResult = await buildProfitHopTransactions({
@@ -0,0 +1,16 @@
1
+ import { ethers, Wallet } from 'ethers';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { calculateBuyerNeed } from './swap-planning.js';
4
+ describe('calculateBuyerNeed quote guard', () => {
5
+ it('报价为 0 时拒绝用买方全部余额买入', async () => {
6
+ const provider = {
7
+ getBalance: vi.fn(async () => ethers.parseEther('1')),
8
+ };
9
+ const buyer = new Wallet(Wallet.createRandom().privateKey, provider);
10
+ await expect(calculateBuyerNeed({
11
+ buyer,
12
+ quotedNative: 0n,
13
+ nativeToken: 'BNB',
14
+ })).rejects.toThrow(/卖出报价为 0/);
15
+ });
16
+ });
@@ -62,7 +62,8 @@ export type FlapSignConfig = {
62
62
  extractProfitOnLaunch?: boolean;
63
63
  };
64
64
  export type FlapBundleMerkleConfig = {
65
- apiKey: string;
65
+ /** @deprecated Merkle 提交已移除;字段仅为旧调用兼容,不再读取 */
66
+ apiKey?: string;
66
67
  customRpcUrl?: string;
67
68
  bundleBlockOffset?: number;
68
69
  minBlockOffset?: number;
@@ -8,5 +8,5 @@ export { sweepWithBundleMerkle } from '../../bundle/four-meme/utils-sweep.js';
8
8
  export { pairwiseTransferWithBundleMerkle } from '../../bundle/four-meme/utils-pairwise.js';
9
9
  export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle } from '../../bundle/four-meme/swap.js';
10
10
  export { fourBundleBuyFirstMerkle } from '../../bundle/four-meme/swap-buy-first.js';
11
- export { submitBundleToMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type DirectSubmitConfig, } from '../../bundle/submit.js';
11
+ export { submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, type BlockRazorSubmitConfig, type DirectSubmitConfig, } from '../../bundle/submit.js';
12
12
  export type { DisperseSignParams, DisperseMerkleResult, SweepSignParams, SweepMerkleResult, FourBundleSwapSignParams, FourSwapResult, FourBatchSwapSignParams, FourBatchSwapResult, FourQuickBatchSwapSignParams, FourQuickBatchSwapResult, PairwiseTransferSignParams, PairwiseTransferMerkleResult, } from '../../bundle/four-meme/types/index.js';
@@ -8,4 +8,4 @@ export { sweepWithBundleMerkle } from '../../bundle/four-meme/utils-sweep.js';
8
8
  export { pairwiseTransferWithBundleMerkle } from '../../bundle/four-meme/utils-pairwise.js';
9
9
  export { fourBundleSwapMerkle, fourBatchSwapMerkle, fourQuickBatchSwapMerkle } from '../../bundle/four-meme/swap.js';
10
10
  export { fourBundleBuyFirstMerkle } from '../../bundle/four-meme/swap-buy-first.js';
11
- export { submitBundleToMerkle, submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, } from '../../bundle/submit.js';
11
+ export { submitBundleToBlockRazor, submitDirectToRpc, submitDirectToRpcSmart, } from '../../bundle/submit.js';
@@ -20,7 +20,7 @@ export { sweepWithBundleMerkle } from '../bundle/four-meme/utils-sweep.js';
20
20
  export { pairwiseTransferWithBundleMerkle } from '../bundle/four-meme/utils-pairwise.js';
21
21
  export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, } from '../bundle/four-meme/pancake-proxy.js';
22
22
  export { approveFourTokenManagerBatch } from '../bundle/four-meme/approve-tokenmanager.js';
23
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, } from '../bundle/submit.js';
23
+ export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, } from '../bundle/submit.js';
24
24
  export type { FourBundleMerkleConfig, FourSignConfig, FourCreateWithBundleBuySignParams, FourBatchBuySignParams, FourBatchSellSignParams, FourPrivateBuySignParams, FourPrivateSellSignParams, FourBatchPrivateBuySignParams, FourBatchPrivateSellSignParams, FourPancakeProxyBatchBuySignParams, FourPancakeProxyBatchSellSignParams, DisperseSignParams as FourDisperseSignParams, SweepSignParams as FourSweepSignParams, FourCreateWithBundleBuyMerkleParams, FourCreateWithBundleBuyMerkleResult, FourBatchBuyMerkleParams, FourBatchBuyMerkleResult, FourBatchSellMerkleParams, FourBatchSellMerkleResult, FourPrivateBuyMerkleParams, FourPrivateSellMerkleParams, FourBatchPrivateBuyMerkleParams, FourBatchPrivateSellMerkleParams, FourBatchPrivateMerkleResult, FourPrivateTransactionResult, DisperseMerkleParams, DisperseMerkleResult, SweepMerkleParams, SweepMerkleResult, FourPancakeProxyBatchBuyParams, FourPancakeProxyBatchBuyResult, FourPancakeProxyBatchSellParams, FourPancakeProxyBatchSellResult, FourPancakeProxyApprovalParams, FourPancakeProxyApprovalBatchParams, FourPancakeProxyApprovalBatchResult, ApproveFourTokenManagerBatchParams, ApproveFourTokenManagerBatchResult, } from '../bundle/four-meme/types/index.js';
25
25
  export type { OpenFourCreateSignParams } from '../bundle/four-meme/core.js';
26
26
  export type { OpenFourBatchBuySignParams } from '../bundle/four-meme/private.js';
@@ -20,7 +20,7 @@ export { sweepWithBundleMerkle } from '../bundle/four-meme/utils-sweep.js';
20
20
  export { pairwiseTransferWithBundleMerkle } from '../bundle/four-meme/utils-pairwise.js';
21
21
  export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, } from '../bundle/four-meme/pancake-proxy.js';
22
22
  export { approveFourTokenManagerBatch } from '../bundle/four-meme/approve-tokenmanager.js';
23
- export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, } from '../bundle/submit.js';
23
+ export { submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, } from '../bundle/submit.js';
24
24
  // ✅ Four.meme TaxToken 工具(税币识别/查询/领取奖励)
25
25
  export { isTaxToken as fourIsTaxToken, isAntiSniperFeeMode as fourIsAntiSniperFeeMode, getCreatorType as fourGetCreatorType, getTaxTokenInfo as fourGetTaxTokenInfo, getClaimableFee as fourGetClaimableFee, getClaimedFee as fourGetClaimedFee, getUserRewardInfo as fourGetUserRewardInfo, buildClaimFeeTx as fourBuildClaimFeeTx, buildBatchClaimFeeTxs as fourBuildBatchClaimFeeTxs, validateFourTokenTaxInfo, TAX_TOKEN_ABI, TransferMode as FourTransferMode, } from '../domains/four/index.js';
26
26
  export { PinataClient } from '../domains/flap/pinata.js';
@@ -31,7 +31,6 @@ export { parseFlapError, getFlapErrorMessage, getFlapErrorMessageEn, type FlapEr
31
31
  export { getFlapMetaByAddress, getFlapMetasByAddresses } from '../domains/flap/meta.js';
32
32
  export { createTokenFlow, type CreateTokenFlowInput, type CreateTokenFlowOutput } from '../domains/flows/create.js';
33
33
  export { Club48Client, sendBatchPrivateTransactions, sendBackrunBundle, type BundleParams, type BundleStatus, type Club48Config, } from '../core/clients/club48.js';
34
- export { MerkleClient, createMerkleClient, type MerkleConfig, type BundleParams as MerkleBundleParams, type SendBundleOptions, type BundleResult, type TransactionResult, } from '../core/clients/merkle.js';
35
34
  export { BlockRazorClient, createBlockRazorClient, BLOCKRAZOR_BUILDER_EOA, type BlockRazorConfig, type BlockRazorBundleParams, type SendBundleOptions as BlockRazorSendBundleOptions, type BundleResult as BlockRazorBundleResult, type TransactionResult as BlockRazorTransactionResult, type IncentiveTransactionParams, } from '../core/clients/blockrazor.js';
36
35
  export { EmitServiceClient, submitToEmitServiceBlockRazor, type EmitServiceClientConfig, type EmitServiceSubmitRequest, type EmitServiceSubmitResponse, type EmitServiceSubmitResponseData, type EmitServiceRouteConfigResponse, } from '../core/clients/emitservice.js';
37
36
  export { NonceManager, getOptimizedGasPrice, estimateGasWithSafety, estimateGasBatch, buildTransaction, signTransactionsBatch, signTransactionsBatchMultiWallet, validateSignedTransactions, getDeadline, encodeV3Path, decodeV3Path, getGasLimit, getGasPriceConfig, getTxType, getChainId, type GasPriceConfig, type GasEstimateConfig, type BuildTransactionOptions, type CommonBundleConfig, } from '../utils/bundle-helpers.js';
@@ -66,7 +66,6 @@ export { parseFlapError, getFlapErrorMessage, getFlapErrorMessageEn, } from '../
66
66
  export { getFlapMetaByAddress, getFlapMetasByAddresses } from '../domains/flap/meta.js';
67
67
  export { createTokenFlow } from '../domains/flows/create.js';
68
68
  export { Club48Client, sendBatchPrivateTransactions, sendBackrunBundle, } from '../core/clients/club48.js';
69
- export { MerkleClient, createMerkleClient, } from '../core/clients/merkle.js';
70
69
  export { BlockRazorClient, createBlockRazorClient, BLOCKRAZOR_BUILDER_EOA, } from '../core/clients/blockrazor.js';
71
70
  export { EmitServiceClient, submitToEmitServiceBlockRazor, } from '../core/clients/emitservice.js';
72
71
  export { NonceManager, getOptimizedGasPrice, estimateGasWithSafety, estimateGasBatch, buildTransaction, signTransactionsBatch, signTransactionsBatchMultiWallet, validateSignedTransactions, getDeadline, encodeV3Path, decodeV3Path, getGasLimit, getGasPriceConfig, getTxType, getChainId, } from '../utils/bundle-helpers.js';
@@ -1,21 +1,7 @@
1
1
  /**
2
2
  * 换手交易通用工具函数
3
3
  */
4
- import { Wallet, JsonRpcProvider } from 'ethers';
5
- import type { MerkleClient } from '../core/clients/merkle.js';
6
- /**
7
- * 检查并执行代币授权(如果需要)
8
- * ✅ 与 pancake-proxy.ts 的授权逻辑一致
9
- */
10
- export declare function ensureTokenApproval(provider: JsonRpcProvider, merkle: MerkleClient, wallet: Wallet, tokenAddress: string, spenderAddress: string, config: {
11
- gasLimit?: number;
12
- gasLimitMultiplier?: number;
13
- minGasPriceGwei?: number;
14
- maxGasPriceGwei?: number;
15
- txType?: 0 | 2;
16
- bundleBlockOffset?: number;
17
- waitTimeoutMs?: number;
18
- }): Promise<void>;
4
+ import { JsonRpcProvider } from 'ethers';
19
5
  /**
20
6
  * 获取代币余额
21
7
  */
@@ -2,88 +2,8 @@
2
2
  * 换手交易通用工具函数
3
3
  */
4
4
  import { ethers, Contract, Interface } from 'ethers';
5
- import { batchCheckAllowances } from '../core/erc20/index.js';
6
5
  import { ERC20_ABI, MULTICALL3_ABI } from '../abis/common.js';
7
6
  import { getMulticall3Address } from '../core/constants/index.js';
8
- import { NonceManager, getOptimizedGasPrice } from './bundle-helpers.js';
9
- const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
10
- /**
11
- * 获取 Gas Limit(授权专用)
12
- * 优先使用 config.gasLimit,否则使用默认值 * multiplier
13
- *
14
- * ✅ ethers v6 最佳实践:直接使用 bigint 类型
15
- */
16
- function getApprovalGasLimit(config, defaultGas = 80000) {
17
- // 优先使用前端传入的 gasLimit
18
- if (config.gasLimit !== undefined) {
19
- // 如果已经是 bigint,直接返回;否则转换
20
- return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
21
- }
22
- // 使用默认值 * multiplier
23
- const multiplier = config.gasLimitMultiplier ?? 1.0;
24
- const calculatedGas = Math.ceil(defaultGas * multiplier);
25
- // JavaScript 原生 BigInt 转换(ethers v6 兼容)
26
- return BigInt(calculatedGas);
27
- }
28
- /**
29
- * 检查并执行代币授权(如果需要)
30
- * ✅ 与 pancake-proxy.ts 的授权逻辑一致
31
- */
32
- export async function ensureTokenApproval(provider, merkle, wallet, tokenAddress, spenderAddress, config) {
33
- // 使用Multicall3批量检查(虽然只有一个地址,但保持接口一致)
34
- const allowances = await batchCheckAllowances(provider, tokenAddress, [wallet.address], spenderAddress);
35
- const currentAllowance = allowances[0];
36
- if (currentAllowance >= APPROVAL_THRESHOLD) {
37
- return;
38
- }
39
- // ✅ 使用 NonceManager(与 pancake-proxy.ts 一致)
40
- const nonceManager = new NonceManager(provider);
41
- // 构建授权交易
42
- const tokenContract = new Contract(tokenAddress, ERC20_ABI, wallet);
43
- const approveUnsigned = await tokenContract.approve.populateTransaction(spenderAddress, ethers.MaxUint256);
44
- // ✅ 使用 getApprovalGasLimit 函数处理 Gas Limit(与 pancake-proxy.ts 一致)
45
- const approveGasLimit = getApprovalGasLimit(config, 80000);
46
- // ✅ 使用 getOptimizedGasPrice 处理 Gas Price(与 pancake-proxy.ts 一致)
47
- const gasPriceConfig = {};
48
- if (config.minGasPriceGwei !== undefined) {
49
- gasPriceConfig.baseGasPrice = ethers.parseUnits(String(config.minGasPriceGwei), 'gwei');
50
- gasPriceConfig.multiplierPercent = 0;
51
- }
52
- if (config.maxGasPriceGwei !== undefined) {
53
- gasPriceConfig.maxGasPrice = ethers.parseUnits(String(config.maxGasPriceGwei), 'gwei');
54
- }
55
- const gasPrice = await getOptimizedGasPrice(provider, gasPriceConfig);
56
- // ✅ 使用 NonceManager 获取 nonce
57
- const nonce = await nonceManager.getNextNonce(wallet);
58
- // ✅ 获取 chainId(不硬编码)
59
- const network = await provider.getNetwork();
60
- const chainId = Number(network.chainId);
61
- // 签名
62
- const signedTx = await wallet.signTransaction({
63
- ...approveUnsigned,
64
- from: wallet.address,
65
- nonce,
66
- gasLimit: approveGasLimit,
67
- gasPrice,
68
- chainId,
69
- type: config.txType ?? 0,
70
- });
71
- // 清理临时 nonce 缓存
72
- nonceManager.clearTemp();
73
- // ✅ 提交 Bundle(使用 blockOffset)
74
- const blockOffset = config.bundleBlockOffset ?? 5;
75
- const bundleResult = await merkle.sendBundle({
76
- transactions: [signedTx],
77
- blockOffset,
78
- minTimestamp: 0,
79
- maxTimestamp: 0,
80
- });
81
- // 等待确认
82
- const results = await merkle.waitForBundleConfirmation(bundleResult.txHashes, 1, config.waitTimeoutMs ?? 120000);
83
- if (!results[0]?.success) {
84
- throw new Error(`授权失败: ${results[0]?.error || '未知错误'}`);
85
- }
86
- }
87
7
  async function readDecimalsAndBalance(provider, tokenAddress, walletAddress) {
88
8
  try {
89
9
  const chainId = Number((await provider.getNetwork()).chainId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "4.4.3",
3
+ "version": "4.5.0",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",