four-flap-meme-sdk 1.4.52 → 1.4.53

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.
@@ -93,14 +93,19 @@ export type HoldersMakerResult = {
93
93
  /**
94
94
  * 刷持有人(一个 bundle 完成分发+买入)
95
95
  *
96
- * 流程(同一个 bundle):
96
+ * 原生代币模式流程(同一个 bundle):
97
97
  * 1. 贿赂交易
98
- * 2. 分发原生代币(dev → 新钱包1, dev → 新钱包2, ...)
99
- * 3. 买入交易(新钱包1 买入, 新钱包2 买入, ...)
98
+ * 2. 分发原生代币(dev → 新钱包)
99
+ * 3. 买入交易(新钱包 nonce=0)
100
100
  * 4. 利润多跳
101
101
  *
102
- * ⚠️ 仅支持原生代币(BNB/MON)模式
103
- * ERC20 模式需要授权,无法在同一个 bundle 中完成
102
+ * ERC20 模式流程(同一个 bundle):
103
+ * 1. 贿赂交易
104
+ * 2. 分发原生代币(gas 费)
105
+ * 3. 分发 ERC20 代币(购买资金)
106
+ * 4. 授权交易(新钱包 nonce=0)
107
+ * 5. 买入交易(新钱包 nonce=1)
108
+ * 6. 利润多跳
104
109
  *
105
110
  * @returns 包含所有签名交易的结果,由前端提交
106
111
  */
@@ -111,10 +116,12 @@ export declare function holdersMaker(params: HoldersMakerParams): Promise<Holder
111
116
  export declare function estimateHoldersMakerCost(params: {
112
117
  holdersCount: number;
113
118
  buyAmountPerHolder: string;
119
+ baseToken?: BaseTokenType;
114
120
  gasLimit?: number;
115
121
  gasPriceGwei?: number;
116
122
  }): {
117
123
  totalNativeCost: string;
124
+ totalERC20Cost?: string;
118
125
  gasEstimate: string;
119
126
  batchCount: number;
120
127
  };
@@ -10,7 +10,7 @@ import { generateWallets } from './wallet.js';
10
10
  import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, getDeadline } from './bundle-helpers.js';
11
11
  import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, PROFIT_CONFIG } from './constants.js';
12
12
  import { FLAP_PORTAL_ADDRESSES } from '../flap/constants.js';
13
- import { V2_ROUTER_ABI, V3_ROUTER02_ABI } from '../abis/common.js';
13
+ import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_ABI } from '../abis/common.js';
14
14
  // Four 内盘 ABI
15
15
  const FOUR_TM2_ABI = [
16
16
  'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable'
@@ -19,13 +19,19 @@ const FOUR_TM2_ABI = [
19
19
  const PANCAKE_V2_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV2Router;
20
20
  const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
21
21
  const WBNB_ADDRESS = ADDRESSES.BSC.WBNB;
22
+ // ERC20 稳定币地址
23
+ const USDT_ADDRESS = ADDRESSES.BSC.USDT;
24
+ const USDC_ADDRESS = ADDRESSES.BSC.USDC;
25
+ // ERC20 转账和授权 Gas Limit
26
+ const ERC20_TRANSFER_GAS_LIMIT = 65000n;
27
+ const ERC20_APPROVE_GAS_LIMIT = 50000n;
22
28
  // ============================================================================
23
29
  // 常量
24
30
  // ============================================================================
25
31
  const DEFAULT_GAS_LIMIT = 800000;
26
32
  const DEFAULT_GAS_PRICE_GWEI = 3;
27
33
  const GAS_BUFFER_MULTIPLIER = 2; // 2倍 Gas 费安全系数
28
- // Bundle 限制计算:
34
+ // Bundle 限制计算(原生代币模式):
29
35
  // - 贿赂 1 笔
30
36
  // - 利润多跳 PROFIT_HOP_COUNT + 1 笔(payer → hop1 → hop2 → recipient)
31
37
  // - 分发 N 笔
@@ -33,7 +39,18 @@ const GAS_BUFFER_MULTIPLIER = 2; // 2倍 Gas 费安全系数
33
39
  // 1 + (PROFIT_HOP_COUNT + 1) + 2N ≤ 50
34
40
  // 2N ≤ 50 - 2 - PROFIT_HOP_COUNT
35
41
  // N ≤ (48 - PROFIT_HOP_COUNT) / 2
36
- const DEFAULT_MAX_WALLETS_PER_BATCH = Math.floor((48 - PROFIT_HOP_COUNT) / 2);
42
+ const DEFAULT_MAX_WALLETS_PER_BATCH_NATIVE = Math.floor((48 - PROFIT_HOP_COUNT) / 2);
43
+ // Bundle 限制计算(ERC20 模式):
44
+ // - 贿赂 1 笔
45
+ // - 利润多跳 PROFIT_HOP_COUNT + 1 笔
46
+ // - 分发 BNB N 笔
47
+ // - 分发 ERC20 N 笔
48
+ // - 授权 N 笔
49
+ // - 买入 N 笔
50
+ // 1 + (PROFIT_HOP_COUNT + 1) + 4N ≤ 50
51
+ // 4N ≤ 50 - 2 - PROFIT_HOP_COUNT
52
+ // N ≤ (48 - PROFIT_HOP_COUNT) / 4
53
+ const DEFAULT_MAX_WALLETS_PER_BATCH_ERC20 = Math.floor((48 - PROFIT_HOP_COUNT) / 4);
37
54
  // ============================================================================
38
55
  // 辅助函数
39
56
  // ============================================================================
@@ -47,6 +64,81 @@ function chunkArray(arr, size) {
47
64
  }
48
65
  return chunks;
49
66
  }
67
+ /**
68
+ * 获取 ERC20 稳定币地址
69
+ */
70
+ function getBaseTokenAddress(baseToken, chain, customAddress) {
71
+ if (customAddress)
72
+ return customAddress;
73
+ if (baseToken === 'usdt')
74
+ return USDT_ADDRESS;
75
+ if (baseToken === 'usdc')
76
+ return USDC_ADDRESS;
77
+ throw new Error(`未知的基础代币类型: ${baseToken}`);
78
+ }
79
+ /**
80
+ * 获取 Router 地址(用于授权)
81
+ */
82
+ function getRouterAddress(tradeType) {
83
+ switch (tradeType) {
84
+ case 'v2':
85
+ return PANCAKE_V2_ROUTER_ADDRESS;
86
+ case 'v3':
87
+ return PANCAKE_V3_ROUTER_ADDRESS;
88
+ default:
89
+ throw new Error(`ERC20 模式不支持交易类型: ${tradeType},仅支持 v2/v3`);
90
+ }
91
+ }
92
+ /**
93
+ * 构建 ERC20 转账交易
94
+ */
95
+ async function buildERC20TransferTx(wallet, tokenAddress, to, amount, nonce, gasPrice, chainId, txType) {
96
+ const iface = new ethers.Interface(ERC20_ABI);
97
+ const data = iface.encodeFunctionData('transfer', [to, amount]);
98
+ const tx = {
99
+ to: tokenAddress,
100
+ data,
101
+ value: 0n,
102
+ nonce,
103
+ gasLimit: ERC20_TRANSFER_GAS_LIMIT,
104
+ chainId
105
+ };
106
+ if (txType === 2) {
107
+ tx.maxFeePerGas = gasPrice;
108
+ tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
109
+ tx.type = 2;
110
+ }
111
+ else {
112
+ tx.gasPrice = gasPrice;
113
+ tx.type = 0;
114
+ }
115
+ return await wallet.signTransaction(tx);
116
+ }
117
+ /**
118
+ * 构建 ERC20 授权交易
119
+ */
120
+ async function buildERC20ApproveTx(wallet, tokenAddress, spender, amount, nonce, gasPrice, chainId, txType) {
121
+ const iface = new ethers.Interface(ERC20_ABI);
122
+ const data = iface.encodeFunctionData('approve', [spender, amount]);
123
+ const tx = {
124
+ to: tokenAddress,
125
+ data,
126
+ value: 0n,
127
+ nonce,
128
+ gasLimit: ERC20_APPROVE_GAS_LIMIT,
129
+ chainId
130
+ };
131
+ if (txType === 2) {
132
+ tx.maxFeePerGas = gasPrice;
133
+ tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
134
+ tx.type = 2;
135
+ }
136
+ else {
137
+ tx.gasPrice = gasPrice;
138
+ tx.type = 0;
139
+ }
140
+ return await wallet.signTransaction(tx);
141
+ }
50
142
  /**
51
143
  * 构建原生代币转账交易
52
144
  */
@@ -207,26 +299,97 @@ async function buildV3BuyTx(wallet, tokenAddress, buyAmount, nonce, gasPrice, ga
207
299
  }
208
300
  return await wallet.signTransaction(tx);
209
301
  }
302
+ /**
303
+ * 构建 V2 买入交易(ERC20 输入)
304
+ * USDT/USDC → Token (使用 swapExactTokensForTokensSupportingFeeOnTransferTokens)
305
+ */
306
+ async function buildV2BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType) {
307
+ // 路径: USDT/USDC → Token
308
+ const path = [baseTokenAddress, tokenAddress];
309
+ const deadline = getDeadline();
310
+ const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, wallet);
311
+ const unsigned = await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, // amountIn
312
+ 0n, // amountOutMin(不设滑点保护)
313
+ path, wallet.address, deadline);
314
+ const tx = {
315
+ ...unsigned,
316
+ nonce,
317
+ gasLimit: BigInt(gasLimit),
318
+ chainId
319
+ };
320
+ if (txType === 2) {
321
+ tx.maxFeePerGas = gasPrice;
322
+ tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
323
+ tx.type = 2;
324
+ }
325
+ else {
326
+ tx.gasPrice = gasPrice;
327
+ tx.type = 0;
328
+ }
329
+ return await wallet.signTransaction(tx);
330
+ }
331
+ /**
332
+ * 构建 V3 买入交易(ERC20 输入)
333
+ * USDT/USDC → Token (使用 exactInputSingle + multicall)
334
+ */
335
+ async function buildV3BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType, v3Fee = 2500) {
336
+ const deadline = getDeadline();
337
+ const v3RouterIface = new ethers.Interface(V3_ROUTER02_ABI);
338
+ // 构建 exactInputSingle calldata
339
+ const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
340
+ tokenIn: baseTokenAddress,
341
+ tokenOut: tokenAddress,
342
+ fee: v3Fee,
343
+ recipient: wallet.address,
344
+ amountIn: buyAmount,
345
+ amountOutMinimum: 0n,
346
+ sqrtPriceLimitX96: 0n
347
+ }]);
348
+ // 使用 multicall 包装(无需 ETH value)
349
+ const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER02_ABI, wallet);
350
+ const unsigned = await v3Router.multicall.populateTransaction(deadline, [exactInputSingleData]);
351
+ const tx = {
352
+ ...unsigned,
353
+ nonce,
354
+ gasLimit: BigInt(gasLimit),
355
+ chainId
356
+ };
357
+ if (txType === 2) {
358
+ tx.maxFeePerGas = gasPrice;
359
+ tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
360
+ tx.type = 2;
361
+ }
362
+ else {
363
+ tx.gasPrice = gasPrice;
364
+ tx.type = 0;
365
+ }
366
+ return await wallet.signTransaction(tx);
367
+ }
210
368
  // ============================================================================
211
369
  // 主方法
212
370
  // ============================================================================
213
371
  /**
214
372
  * 刷持有人(一个 bundle 完成分发+买入)
215
373
  *
216
- * 流程(同一个 bundle):
374
+ * 原生代币模式流程(同一个 bundle):
217
375
  * 1. 贿赂交易
218
- * 2. 分发原生代币(dev → 新钱包1, dev → 新钱包2, ...)
219
- * 3. 买入交易(新钱包1 买入, 新钱包2 买入, ...)
376
+ * 2. 分发原生代币(dev → 新钱包)
377
+ * 3. 买入交易(新钱包 nonce=0)
220
378
  * 4. 利润多跳
221
379
  *
222
- * ⚠️ 仅支持原生代币(BNB/MON)模式
223
- * ERC20 模式需要授权,无法在同一个 bundle 中完成
380
+ * ERC20 模式流程(同一个 bundle):
381
+ * 1. 贿赂交易
382
+ * 2. 分发原生代币(gas 费)
383
+ * 3. 分发 ERC20 代币(购买资金)
384
+ * 4. 授权交易(新钱包 nonce=0)
385
+ * 5. 买入交易(新钱包 nonce=1)
386
+ * 6. 利润多跳
224
387
  *
225
388
  * @returns 包含所有签名交易的结果,由前端提交
226
389
  */
227
390
  export async function holdersMaker(params) {
228
- const { payerPrivateKey, holdersCount, buyAmountPerHolder, tokenAddress, baseToken = 'native', config } = params;
229
- const { rpcUrl, chain = 'BSC', chainId: configChainId, tradeType = 'flap', gasLimit = DEFAULT_GAS_LIMIT, gasPriceGwei = DEFAULT_GAS_PRICE_GWEI, bribeAmount = 0.000001, txType = 0, maxWalletsPerBatch = DEFAULT_MAX_WALLETS_PER_BATCH } = config;
391
+ const { payerPrivateKey, holdersCount, buyAmountPerHolder, tokenAddress, baseToken = 'native', baseTokenAddress, baseTokenDecimals = 18, config } = params;
392
+ const { rpcUrl, chain = 'BSC', chainId: configChainId, tradeType = 'flap', gasLimit = DEFAULT_GAS_LIMIT, gasPriceGwei = DEFAULT_GAS_PRICE_GWEI, bribeAmount = 0.000001, txType = 0 } = config;
230
393
  const result = {
231
394
  success: false,
232
395
  newWallets: [],
@@ -235,9 +398,10 @@ export async function holdersMaker(params) {
235
398
  successBatchCount: 0,
236
399
  totalBatchCount: 0
237
400
  };
238
- // ⚠️ 目前仅支持原生代币模式
239
- if (baseToken !== 'native') {
240
- result.error = '一个 bundle 模式仅支持原生代币(BNB/MON),ERC20 模式需要分开提交';
401
+ const isERC20Mode = baseToken !== 'native';
402
+ // ERC20 模式只支持 v2/v3 外盘
403
+ if (isERC20Mode && (tradeType === 'four' || tradeType === 'flap')) {
404
+ result.error = `ERC20 模式(${baseToken})不支持 ${tradeType} 内盘,仅支持 v2/v3 外盘`;
241
405
  return result;
242
406
  }
243
407
  // ✅ 支持 four、flap 内盘和 v2、v3 外盘
@@ -246,85 +410,139 @@ export async function holdersMaker(params) {
246
410
  result.error = `不支持的交易类型: ${tradeType},支持: ${supportedTradeTypes.join(', ')}`;
247
411
  return result;
248
412
  }
413
+ // 根据模式选择每批最大钱包数
414
+ const maxWalletsPerBatch = config.maxWalletsPerBatch ||
415
+ (isERC20Mode ? DEFAULT_MAX_WALLETS_PER_BATCH_ERC20 : DEFAULT_MAX_WALLETS_PER_BATCH_NATIVE);
249
416
  try {
250
417
  // 1. 初始化
251
418
  const provider = new JsonRpcProvider(rpcUrl);
252
419
  const chainId = configChainId || Number((await provider.getNetwork()).chainId);
253
420
  const payer = new Wallet(payerPrivateKey, provider);
254
421
  const nonceManager = new NonceManager(provider);
255
- console.log(`[HoldersMaker] 开始刷持有人: chain=${chain}, chainId=${chainId}, holdersCount=${holdersCount}`);
422
+ console.log(`[HoldersMaker] 开始刷持有人: chain=${chain}, chainId=${chainId}, holdersCount=${holdersCount}, baseToken=${baseToken}`);
256
423
  // 2. 生成新钱包
257
424
  const newWallets = generateWallets(holdersCount);
258
425
  result.newWallets = newWallets;
259
426
  console.log(`[HoldersMaker] 生成 ${newWallets.length} 个新钱包`);
260
427
  // 3. 计算金额
261
- const buyAmountWei = ethers.parseEther(buyAmountPerHolder);
262
- const gasFeePerWallet = BigInt(Math.floor(gasLimit * gasPriceGwei * 1e9));
263
- const gasWithBuffer = gasFeePerWallet * BigInt(GAS_BUFFER_MULTIPLIER);
264
- const transferAmountPerWallet = buyAmountWei + gasWithBuffer;
265
- console.log(`[HoldersMaker] 每个钱包分发: ${ethers.formatEther(transferAmountPerWallet)} BNB (买入 ${buyAmountPerHolder} + Gas ${ethers.formatEther(gasWithBuffer)})`);
266
- // 4. 获取 Gas Price(支持小数 Gwei,如 0.1 Gwei)
267
428
  const gasPriceWei = BigInt(Math.floor(gasPriceGwei * 1e9));
268
429
  const gasPrice = await getOptimizedGasPrice(provider, { minGasPrice: gasPriceWei });
269
430
  const bribeAmountWei = ethers.parseEther(String(bribeAmount));
270
- // 5. 分批处理
431
+ // ERC20 模式需要更多 gas(授权 + 购买)
432
+ const gasMultiplier = isERC20Mode ? 2 : 1;
433
+ const gasFeePerWallet = BigInt(Math.floor(gasLimit * gasPriceGwei * 1e9)) * BigInt(gasMultiplier);
434
+ const gasWithBuffer = gasFeePerWallet * BigInt(GAS_BUFFER_MULTIPLIER);
435
+ let buyAmountWei;
436
+ let transferNativePerWallet;
437
+ let erc20TokenAddress;
438
+ if (isERC20Mode) {
439
+ // ERC20 模式:解析购买金额(考虑精度)
440
+ buyAmountWei = ethers.parseUnits(buyAmountPerHolder, baseTokenDecimals);
441
+ // 只需要分发 gas 费
442
+ transferNativePerWallet = gasWithBuffer;
443
+ // 获取 ERC20 地址
444
+ erc20TokenAddress = getBaseTokenAddress(baseToken, chain, baseTokenAddress);
445
+ console.log(`[HoldersMaker] ERC20 模式: ${baseToken} (${erc20TokenAddress})`);
446
+ console.log(`[HoldersMaker] 每个钱包分发: ${ethers.formatEther(transferNativePerWallet)} BNB (Gas) + ${buyAmountPerHolder} ${baseToken.toUpperCase()}`);
447
+ }
448
+ else {
449
+ // 原生代币模式
450
+ buyAmountWei = ethers.parseEther(buyAmountPerHolder);
451
+ transferNativePerWallet = buyAmountWei + gasWithBuffer;
452
+ console.log(`[HoldersMaker] 每个钱包分发: ${ethers.formatEther(transferNativePerWallet)} BNB (买入 ${buyAmountPerHolder} + Gas ${ethers.formatEther(gasWithBuffer)})`);
453
+ }
454
+ // 4. 分批处理
271
455
  const walletBatches = chunkArray(newWallets, maxWalletsPerBatch);
272
456
  result.totalBatchCount = walletBatches.length;
273
457
  console.log(`[HoldersMaker] 分 ${walletBatches.length} 批处理,每批最多 ${maxWalletsPerBatch} 个钱包`);
274
- // 6. 计算利润
275
- const totalBuyAmount = buyAmountWei * BigInt(holdersCount);
458
+ // 5. 计算利润(基于原生代币)
459
+ const totalBuyAmountForProfit = isERC20Mode
460
+ ? ethers.parseEther(buyAmountPerHolder) * BigInt(holdersCount) // 按等值 BNB 计算
461
+ : buyAmountWei * BigInt(holdersCount);
276
462
  const profitRateBps = PROFIT_CONFIG.RATE_BPS_SWAP;
277
- const totalProfit = (totalBuyAmount * BigInt(profitRateBps)) / 10000n;
463
+ const totalProfit = (totalBuyAmountForProfit * BigInt(profitRateBps)) / 10000n;
278
464
  const profitPerBatch = totalProfit / BigInt(walletBatches.length);
279
465
  console.log(`[HoldersMaker] 总利润: ${ethers.formatEther(totalProfit)} BNB`);
280
- // 7. 并行生成所有批次的签名
466
+ // 6. 并行生成所有批次的签名
281
467
  const batchPromises = walletBatches.map(async (batch, batchIdx) => {
282
468
  try {
283
469
  const signedTxs = [];
284
- // 计算这批需要的 nonce 数量
285
- // 贿赂 1 + 分发 N + 利润 (PROFIT_HOP_COUNT + 1)
286
- const payerNonceCount = 1 + batch.length + PROFIT_HOP_COUNT + 1;
470
+ // 计算这批需要的 payer nonce 数量
471
+ // 原生模式: 贿赂 1 + 分发 N + 利润 (PROFIT_HOP_COUNT + 1)
472
+ // ERC20模式: 贿赂 1 + 分发BNB N + 分发ERC20 N + 利润 (PROFIT_HOP_COUNT + 1)
473
+ const payerNonceCount = isERC20Mode
474
+ ? 1 + batch.length * 2 + PROFIT_HOP_COUNT + 1
475
+ : 1 + batch.length + PROFIT_HOP_COUNT + 1;
287
476
  const payerNonces = await nonceManager.getNextNonceBatch(payer, payerNonceCount);
288
477
  let payerNonceIdx = 0;
289
478
  // (1) 贿赂交易
290
479
  const bribeTx = await buildNativeTransferTx(payer, BLOCKRAZOR_BUILDER_EOA, bribeAmountWei, payerNonces[payerNonceIdx++], gasPrice, chainId, txType);
291
480
  signedTxs.push(bribeTx);
292
- // (2) 分发交易
481
+ // (2) 分发原生代币(gas 费或买入资金)
293
482
  for (const newWallet of batch) {
294
- const transferTx = await buildNativeTransferTx(payer, newWallet.address, transferAmountPerWallet, payerNonces[payerNonceIdx++], gasPrice, chainId, txType);
483
+ const transferTx = await buildNativeTransferTx(payer, newWallet.address, transferNativePerWallet, payerNonces[payerNonceIdx++], gasPrice, chainId, txType);
295
484
  signedTxs.push(transferTx);
296
485
  }
297
- // (3) 买入交易(新钱包,nonce=0)
486
+ // (3) ERC20 模式:分发 ERC20 代币
487
+ if (isERC20Mode && erc20TokenAddress) {
488
+ for (const newWallet of batch) {
489
+ const erc20TransferTx = await buildERC20TransferTx(payer, erc20TokenAddress, newWallet.address, buyAmountWei, payerNonces[payerNonceIdx++], gasPrice, chainId, txType);
490
+ signedTxs.push(erc20TransferTx);
491
+ }
492
+ }
493
+ // (4) ERC20 模式:授权交易(新钱包 nonce=0)
494
+ if (isERC20Mode && erc20TokenAddress) {
495
+ const routerAddress = getRouterAddress(tradeType);
496
+ for (const newWallet of batch) {
497
+ const buyerWallet = new Wallet(newWallet.privateKey, provider);
498
+ const approveTx = await buildERC20ApproveTx(buyerWallet, erc20TokenAddress, routerAddress, ethers.MaxUint256, // 无限授权
499
+ 0, // 新钱包 nonce=0
500
+ gasPrice, chainId, txType);
501
+ signedTxs.push(approveTx);
502
+ }
503
+ }
504
+ // (5) 买入交易
505
+ // 原生模式: nonce=0
506
+ // ERC20模式: nonce=1(授权后)
507
+ const buyNonce = isERC20Mode ? 1 : 0;
298
508
  for (const newWallet of batch) {
299
509
  const buyerWallet = new Wallet(newWallet.privateKey, provider);
300
510
  let buyTx;
301
- switch (tradeType) {
302
- case 'four':
303
- // Four 内盘
304
- buyTx = await buildFourBuyTx(buyerWallet, tokenAddress, buyAmountWei, 0, // 新钱包 nonce=0
305
- gasPrice, gasLimit, chainId, txType);
306
- break;
307
- case 'flap':
308
- // Flap 内盘
309
- buyTx = await buildFlapBuyTx(buyerWallet, tokenAddress, buyAmountWei, 0, // 新钱包 nonce=0
310
- gasPrice, gasLimit, chainId, txType, chain);
311
- break;
312
- case 'v2':
313
- // PancakeSwap V2
314
- buyTx = await buildV2BuyTx(buyerWallet, tokenAddress, buyAmountWei, 0, // 新钱包 nonce=0
315
- gasPrice, gasLimit, chainId, txType, config.v2Path);
316
- break;
317
- case 'v3':
318
- // PancakeSwap V3
319
- buyTx = await buildV3BuyTx(buyerWallet, tokenAddress, buyAmountWei, 0, // 新钱包 nonce=0
320
- gasPrice, gasLimit, chainId, txType, config.v3Fee);
321
- break;
322
- default:
323
- throw new Error(`不支持的交易类型: ${tradeType}`);
511
+ if (isERC20Mode && erc20TokenAddress) {
512
+ // ERC20 购买
513
+ switch (tradeType) {
514
+ case 'v2':
515
+ buyTx = await buildV2BuyTxWithERC20(buyerWallet, tokenAddress, erc20TokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType);
516
+ break;
517
+ case 'v3':
518
+ buyTx = await buildV3BuyTxWithERC20(buyerWallet, tokenAddress, erc20TokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, config.v3Fee);
519
+ break;
520
+ default:
521
+ throw new Error(`ERC20 模式不支持交易类型: ${tradeType}`);
522
+ }
523
+ }
524
+ else {
525
+ // 原生代币购买
526
+ switch (tradeType) {
527
+ case 'four':
528
+ buyTx = await buildFourBuyTx(buyerWallet, tokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType);
529
+ break;
530
+ case 'flap':
531
+ buyTx = await buildFlapBuyTx(buyerWallet, tokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, chain);
532
+ break;
533
+ case 'v2':
534
+ buyTx = await buildV2BuyTx(buyerWallet, tokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, config.v2Path);
535
+ break;
536
+ case 'v3':
537
+ buyTx = await buildV3BuyTx(buyerWallet, tokenAddress, buyAmountWei, buyNonce, gasPrice, gasLimit, chainId, txType, config.v3Fee);
538
+ break;
539
+ default:
540
+ throw new Error(`不支持的交易类型: ${tradeType}`);
541
+ }
324
542
  }
325
543
  signedTxs.push(buyTx);
326
544
  }
327
- // (4) 利润多跳
545
+ // (6) 利润多跳
328
546
  if (profitPerBatch > 0n) {
329
547
  const profitHopResult = await buildProfitHopTransactions({
330
548
  provider,
@@ -356,7 +574,7 @@ export async function holdersMaker(params) {
356
574
  }
357
575
  });
358
576
  const batchResults = await Promise.all(batchPromises);
359
- // 8. 处理结果
577
+ // 7. 处理结果
360
578
  for (const res of batchResults) {
361
579
  if (res.success && res.signedTransactions) {
362
580
  result.signedTransactions.push(res.signedTransactions);
@@ -383,18 +601,34 @@ export async function holdersMaker(params) {
383
601
  * 获取刷持有人预估费用
384
602
  */
385
603
  export function estimateHoldersMakerCost(params) {
386
- const { holdersCount, buyAmountPerHolder, gasLimit = DEFAULT_GAS_LIMIT, gasPriceGwei = DEFAULT_GAS_PRICE_GWEI } = params;
387
- const gasFeePerWallet = (gasLimit * gasPriceGwei) / 1e9;
604
+ const { holdersCount, buyAmountPerHolder, baseToken = 'native', gasLimit = DEFAULT_GAS_LIMIT, gasPriceGwei = DEFAULT_GAS_PRICE_GWEI } = params;
605
+ const isERC20Mode = baseToken !== 'native';
606
+ const gasMultiplier = isERC20Mode ? 2 : 1; // ERC20 模式需要授权+购买
607
+ const gasFeePerWallet = (gasLimit * gasPriceGwei * gasMultiplier) / 1e9;
388
608
  const gasWithBuffer = gasFeePerWallet * GAS_BUFFER_MULTIPLIER;
389
609
  const buyAmount = parseFloat(buyAmountPerHolder);
390
- // 每个钱包成本 = 购买金额 + Gas 费
391
- const costPerWallet = buyAmount + gasWithBuffer;
392
- const totalNativeCost = costPerWallet * holdersCount;
393
610
  // 批次数
394
- const batchCount = Math.ceil(holdersCount / DEFAULT_MAX_WALLETS_PER_BATCH);
395
- return {
396
- totalNativeCost: totalNativeCost.toFixed(6),
397
- gasEstimate: (gasWithBuffer * holdersCount).toFixed(6),
398
- batchCount
399
- };
611
+ const maxPerBatch = isERC20Mode ? DEFAULT_MAX_WALLETS_PER_BATCH_ERC20 : DEFAULT_MAX_WALLETS_PER_BATCH_NATIVE;
612
+ const batchCount = Math.ceil(holdersCount / maxPerBatch);
613
+ if (isERC20Mode) {
614
+ // ERC20 模式:原生代币只用于 gas
615
+ const totalNativeCost = gasWithBuffer * holdersCount;
616
+ const totalERC20Cost = buyAmount * holdersCount;
617
+ return {
618
+ totalNativeCost: totalNativeCost.toFixed(6),
619
+ totalERC20Cost: totalERC20Cost.toFixed(6),
620
+ gasEstimate: (gasWithBuffer * holdersCount).toFixed(6),
621
+ batchCount
622
+ };
623
+ }
624
+ else {
625
+ // 原生代币模式:包含购买金额 + gas
626
+ const costPerWallet = buyAmount + gasWithBuffer;
627
+ const totalNativeCost = costPerWallet * holdersCount;
628
+ return {
629
+ totalNativeCost: totalNativeCost.toFixed(6),
630
+ gasEstimate: (gasWithBuffer * holdersCount).toFixed(6),
631
+ batchCount
632
+ };
633
+ }
400
634
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.52",
3
+ "version": "1.4.53",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",