four-flap-meme-sdk 1.4.52 → 1.4.54
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.
|
@@ -700,7 +700,10 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
700
700
|
_batchGetBalances(provider, addresses)
|
|
701
701
|
]);
|
|
702
702
|
const gasCostBase = nativeGasLimit * gasPrice;
|
|
703
|
-
|
|
703
|
+
// ✅ 修复:利润多跳需要 PROFIT_HOP_COUNT + 1 笔交易的 gas(payer → hop1 → hop2 → 利润地址)
|
|
704
|
+
// ✅ 统一使用 21055n 作为利润多跳的 gas limit
|
|
705
|
+
const PROFIT_HOP_GAS_LIMIT = 21055n;
|
|
706
|
+
const profitTxGas = PROFIT_HOP_GAS_LIMIT * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
704
707
|
// ✅ 第一步:计算所有钱包的归集金额和利润,找出归集金额最大的钱包
|
|
705
708
|
const sweepAmounts = [];
|
|
706
709
|
let maxSweepIndex = -1;
|
|
@@ -843,7 +846,10 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
843
846
|
config.checkBnbForErc20NoHop ? _batchGetBalances(provider, addresses) : Promise.resolve([])
|
|
844
847
|
]);
|
|
845
848
|
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
846
|
-
|
|
849
|
+
// ✅ 修复:利润多跳需要 PROFIT_HOP_COUNT + 1 笔交易的 gas
|
|
850
|
+
// ✅ 统一使用 21055n 作为利润多跳的 gas limit
|
|
851
|
+
const PROFIT_HOP_GAS_LIMIT_ERC20 = 21055n;
|
|
852
|
+
const profitTxGas = PROFIT_HOP_GAS_LIMIT_ERC20 * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
847
853
|
// ✅ 第一步:计算所有钱包的归集金额和利润,找出归集金额最大的钱包
|
|
848
854
|
const sweepAmounts = [];
|
|
849
855
|
let maxSweepIndex = -1;
|
|
@@ -502,7 +502,9 @@ export async function flapSweepWithBundleMerkle(params) {
|
|
|
502
502
|
batchGetBalances(provider, addresses)
|
|
503
503
|
]);
|
|
504
504
|
const gasCostBase = nativeGasLimit * gasPrice;
|
|
505
|
-
|
|
505
|
+
// ✅ 统一使用 21055n 作为利润多跳的 gas limit,需要 PROFIT_HOP_COUNT + 1 笔交易
|
|
506
|
+
const PROFIT_HOP_GAS_LIMIT = 21055n;
|
|
507
|
+
const profitTxGas = PROFIT_HOP_GAS_LIMIT * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
506
508
|
// 第一步:计算所有钱包的归集金额,找出余额最大的钱包
|
|
507
509
|
const sweepAmounts = [];
|
|
508
510
|
let maxSweepIndex = -1;
|
|
@@ -633,7 +635,9 @@ export async function flapSweepWithBundleMerkle(params) {
|
|
|
633
635
|
batchGetBalances(provider, addresses)
|
|
634
636
|
]);
|
|
635
637
|
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
636
|
-
|
|
638
|
+
// ✅ 统一使用 21055n 作为利润多跳的 gas limit,需要 PROFIT_HOP_COUNT + 1 笔交易
|
|
639
|
+
const PROFIT_HOP_GAS_LIMIT_ERC20 = 21055n;
|
|
640
|
+
const profitTxGas = PROFIT_HOP_GAS_LIMIT_ERC20 * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
637
641
|
const sweepAmounts = [];
|
|
638
642
|
let maxSweepIndex = -1;
|
|
639
643
|
let maxSweepAmount = 0n;
|
|
@@ -93,14 +93,19 @@ export type HoldersMakerResult = {
|
|
|
93
93
|
/**
|
|
94
94
|
* 刷持有人(一个 bundle 完成分发+买入)
|
|
95
95
|
*
|
|
96
|
-
*
|
|
96
|
+
* 原生代币模式流程(同一个 bundle):
|
|
97
97
|
* 1. 贿赂交易
|
|
98
|
-
* 2. 分发原生代币(dev →
|
|
99
|
-
* 3. 买入交易(新钱包
|
|
98
|
+
* 2. 分发原生代币(dev → 新钱包)
|
|
99
|
+
* 3. 买入交易(新钱包 nonce=0)
|
|
100
100
|
* 4. 利润多跳
|
|
101
101
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
|
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
|
-
*
|
|
374
|
+
* 原生代币模式流程(同一个 bundle):
|
|
217
375
|
* 1. 贿赂交易
|
|
218
|
-
* 2. 分发原生代币(dev →
|
|
219
|
-
* 3. 买入交易(新钱包
|
|
376
|
+
* 2. 分发原生代币(dev → 新钱包)
|
|
377
|
+
* 3. 买入交易(新钱包 nonce=0)
|
|
220
378
|
* 4. 利润多跳
|
|
221
379
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
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
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
275
|
-
const
|
|
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 = (
|
|
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
|
-
//
|
|
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
|
-
|
|
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,
|
|
483
|
+
const transferTx = await buildNativeTransferTx(payer, newWallet.address, transferNativePerWallet, payerNonces[payerNonceIdx++], gasPrice, chainId, txType);
|
|
295
484
|
signedTxs.push(transferTx);
|
|
296
485
|
}
|
|
297
|
-
// (3)
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
// (
|
|
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
|
-
//
|
|
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
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
}
|