four-flap-meme-sdk 1.4.54 → 1.4.56
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.
- package/dist/clients/four.js +1 -1
- package/dist/contracts/tm-bundle-merkle/swap-internal.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/swap-internal.js +2 -1
- package/dist/contracts/tm-bundle.js +7 -7
- package/dist/utils/constants.d.ts +0 -2
- package/dist/utils/constants.js +0 -3
- package/dist/utils/erc20.js +1 -1
- package/dist/utils/holders-maker.d.ts +4 -0
- package/dist/utils/holders-maker.js +240 -60
- package/package.json +1 -1
package/dist/clients/four.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class FourClient {
|
|
2
2
|
constructor(cfg) {
|
|
3
3
|
// 默认使用 Cloudflare Workers 代理,已配置 CORS,浏览器可直接使用
|
|
4
|
-
this.baseUrl = cfg?.baseUrl || 'https://
|
|
4
|
+
this.baseUrl = cfg?.baseUrl || 'https://bscfourapi.emit.tools';
|
|
5
5
|
}
|
|
6
6
|
async generateNonce(req) {
|
|
7
7
|
const r = await fetch(`${this.baseUrl}/v1/private/user/nonce/generate`, {
|
|
@@ -14,4 +14,5 @@ export const HELPER3_ABI = [
|
|
|
14
14
|
'function tryBuy(address token, uint256 amount, uint256 funds) view returns (address tokenManager, address quote, uint256 estimatedAmount, uint256 estimatedCost, uint256 estimatedFee, uint256 amountMsgValue, uint256 amountApproval, uint256 amountFunds)',
|
|
15
15
|
'function trySell(address token, uint256 amount) view returns (address tokenManager, address quote, uint256 funds, uint256 fee)'
|
|
16
16
|
];
|
|
17
|
-
|
|
17
|
+
// ✅ Four.meme 使用 TokenManagerOriginal(代理合约已移除)
|
|
18
|
+
export const TM_ADDRESS = ADDRESSES.BSC.TokenManagerOriginal;
|
|
@@ -176,7 +176,7 @@ export async function createTokenWithBundleBuy(params) {
|
|
|
176
176
|
clickFun: false,
|
|
177
177
|
});
|
|
178
178
|
// 4. 构建交易
|
|
179
|
-
const tmAddr = ADDRESSES.BSC.
|
|
179
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
180
180
|
// ✅ 获取 gas price
|
|
181
181
|
let gasPrice;
|
|
182
182
|
if (club48) {
|
|
@@ -403,7 +403,7 @@ export async function batchBuyWithBundle(params) {
|
|
|
403
403
|
explorerEndpoint: config.club48ExplorerEndpoint
|
|
404
404
|
});
|
|
405
405
|
const blockOffset = config.bundleBlockOffset ?? 100;
|
|
406
|
-
const tmAddr = ADDRESSES.BSC.
|
|
406
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
407
407
|
const gasPrice = await club48.getMinGasPrice();
|
|
408
408
|
const signedTxs = [];
|
|
409
409
|
const nextNonceMap = new Map();
|
|
@@ -535,7 +535,7 @@ export async function batchSellWithBundle(params) {
|
|
|
535
535
|
explorerEndpoint: config.club48ExplorerEndpoint
|
|
536
536
|
});
|
|
537
537
|
const blockOffset = config.bundleBlockOffset ?? 100;
|
|
538
|
-
const tmAddr = ADDRESSES.BSC.
|
|
538
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
539
539
|
const gasPrice = await club48.getMinGasPrice();
|
|
540
540
|
const signedTxs = [];
|
|
541
541
|
const nextNonceMap = new Map();
|
|
@@ -666,7 +666,7 @@ export async function fourPrivateBuy(params) {
|
|
|
666
666
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
667
667
|
const wallet = new Wallet(privateKey, provider);
|
|
668
668
|
const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint });
|
|
669
|
-
const tmAddr = ADDRESSES.BSC.
|
|
669
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
670
670
|
const gasPrice = await club48.getMinGasPrice();
|
|
671
671
|
const fundsWei = ethers.parseEther(funds);
|
|
672
672
|
const minAmount = 0n;
|
|
@@ -686,7 +686,7 @@ export async function fourPrivateSell(params) {
|
|
|
686
686
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
687
687
|
const wallet = new Wallet(privateKey, provider);
|
|
688
688
|
const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint });
|
|
689
|
-
const tmAddr = ADDRESSES.BSC.
|
|
689
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
690
690
|
const gasPrice = await club48.getMinGasPrice();
|
|
691
691
|
const amountWei = ethers.parseUnits(amount, 18);
|
|
692
692
|
const minOut = minFunds ?? 0n;
|
|
@@ -707,7 +707,7 @@ export async function fourBatchPrivateBuy(params) {
|
|
|
707
707
|
throw new Error('privateKeys and fundsList length mismatch');
|
|
708
708
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
709
709
|
const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint });
|
|
710
|
-
const tmAddr = ADDRESSES.BSC.
|
|
710
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
711
711
|
// ✅ 并行处理
|
|
712
712
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
713
713
|
const fundsWeiList = fundsList.map(f => ethers.parseEther(f));
|
|
@@ -757,7 +757,7 @@ export async function fourBatchPrivateSell(params) {
|
|
|
757
757
|
throw new Error('privateKeys and amounts length mismatch');
|
|
758
758
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
759
759
|
const club48 = new Club48Client({ endpoint: club48Endpoint, explorerEndpoint: club48ExplorerEndpoint });
|
|
760
|
-
const tmAddr = ADDRESSES.BSC.
|
|
760
|
+
const tmAddr = ADDRESSES.BSC.TokenManagerOriginal;
|
|
761
761
|
const minOut = minFundsEach ?? 0n;
|
|
762
762
|
// ✅ 并行处理
|
|
763
763
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
@@ -55,8 +55,6 @@ export declare const ADDRESSES: {
|
|
|
55
55
|
readonly USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
56
56
|
readonly BUSD: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56";
|
|
57
57
|
readonly TokenManagerOriginal: "0x5c952063c7fc8610FFDB798152D69F0B9550762b";
|
|
58
|
-
readonly TokenManagerV1Proxy: "0xf7F823d0E790219dBf727bDb971837574655fCB0";
|
|
59
|
-
readonly TokenManagerV2Proxy: "0x342399a59943B5815849657Aa0e06D7058D9d5C6";
|
|
60
58
|
readonly TokenManagerV1: "0xf7F823d0E790219dBf727bDb971837574655fCB0";
|
|
61
59
|
readonly TokenManagerV2: "0x342399a59943B5815849657Aa0e06D7058D9d5C6";
|
|
62
60
|
readonly TokenManagerHelper3: "0xF251F83e40a78868FcfA3FA4599Dad6494E46034";
|
package/dist/utils/constants.js
CHANGED
|
@@ -51,9 +51,6 @@ export const ADDRESSES = {
|
|
|
51
51
|
// ========== Four.meme 合约 ==========
|
|
52
52
|
// 原始合约(TokenManager2,创建代币专用,不收费)
|
|
53
53
|
TokenManagerOriginal: '0x5c952063c7fc8610FFDB798152D69F0B9550762b',
|
|
54
|
-
// 代理合约(收费版,仅交易)
|
|
55
|
-
TokenManagerV1Proxy: '0xf7F823d0E790219dBf727bDb971837574655fCB0',
|
|
56
|
-
TokenManagerV2Proxy: '0x342399a59943B5815849657Aa0e06D7058D9d5C6',
|
|
57
54
|
// ✅ 向后兼容别名(保持旧代码可用)
|
|
58
55
|
TokenManagerV1: '0xf7F823d0E790219dBf727bDb971837574655fCB0',
|
|
59
56
|
TokenManagerV2: '0x342399a59943B5815849657Aa0e06D7058D9d5C6',
|
package/dist/utils/erc20.js
CHANGED
|
@@ -299,7 +299,7 @@ function resolveSpenderAddress(chain, platform) {
|
|
|
299
299
|
MONAD: ADDRESSES.MONAD.FlapPortal, // ✅ Monad Flap Portal
|
|
300
300
|
},
|
|
301
301
|
four: {
|
|
302
|
-
// Four.meme 使用 TokenManagerOriginal
|
|
302
|
+
// ✅ Four.meme 使用 TokenManagerOriginal(代理合约已移除)
|
|
303
303
|
BSC: ADDRESSES.BSC.TokenManagerOriginal,
|
|
304
304
|
BASE: ADDRESSES.BASE.TokenManagerHelper3,
|
|
305
305
|
XLAYER: ZERO_ADDRESS, // XLAYER 暂不支持 Four.meme
|
|
@@ -34,6 +34,8 @@ export type HoldersMakerConfig = {
|
|
|
34
34
|
maxWalletsPerBatch?: number;
|
|
35
35
|
/** Four API URL */
|
|
36
36
|
fourApiUrl?: string;
|
|
37
|
+
/** 分发多跳数(0=直接转账,1=1跳,2=2跳...) */
|
|
38
|
+
disperseHopCount?: number;
|
|
37
39
|
/** V2 路由路径(如 [WBNB, TOKEN]) */
|
|
38
40
|
v2Path?: string[];
|
|
39
41
|
/** V3 单跳输入代币 */
|
|
@@ -79,6 +81,8 @@ export type HoldersMakerResult = {
|
|
|
79
81
|
success: boolean;
|
|
80
82
|
/** 生成的新钱包 */
|
|
81
83
|
newWallets: GeneratedWallet[];
|
|
84
|
+
/** 分发多跳中间钱包(可选,用于导出) */
|
|
85
|
+
disperseHopWallets?: GeneratedWallet[];
|
|
82
86
|
/** 签名交易(每批一个数组) */
|
|
83
87
|
signedTransactions: string[][];
|
|
84
88
|
/** 批次结果 */
|
|
@@ -31,26 +31,46 @@ const ERC20_APPROVE_GAS_LIMIT = 50000n;
|
|
|
31
31
|
const DEFAULT_GAS_LIMIT = 800000;
|
|
32
32
|
const DEFAULT_GAS_PRICE_GWEI = 3;
|
|
33
33
|
const GAS_BUFFER_MULTIPLIER = 2; // 2倍 Gas 费安全系数
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
34
|
+
const NATIVE_TRANSFER_GAS_LIMIT = 21055n; // 原生代币转账 Gas Limit
|
|
35
|
+
/**
|
|
36
|
+
* 动态计算每批最大钱包数
|
|
37
|
+
*
|
|
38
|
+
* Bundle 限制 = 50 笔交易
|
|
39
|
+
* 固定开销 = 贿赂 1 笔 + 利润多跳 (PROFIT_HOP_COUNT + 1) 笔
|
|
40
|
+
*
|
|
41
|
+
* 原生模式(无分发多跳):
|
|
42
|
+
* 每钱包开销 = 分发 1 + 买入 1 = 2
|
|
43
|
+
* N ≤ (50 - 固定开销) / 2
|
|
44
|
+
*
|
|
45
|
+
* 原生模式(有分发多跳 H):
|
|
46
|
+
* 每钱包开销 = 分发 (H+1) + 买入 1 = H+2
|
|
47
|
+
* N ≤ (50 - 固定开销) / (H+2)
|
|
48
|
+
*
|
|
49
|
+
* ERC20 模式(无分发多跳):
|
|
50
|
+
* 每钱包开销 = 分发BNB 1 + 分发ERC20 1 + 授权 1 + 买入 1 = 4
|
|
51
|
+
* N ≤ (50 - 固定开销) / 4
|
|
52
|
+
*
|
|
53
|
+
* ERC20 模式(有分发多跳 H):
|
|
54
|
+
* 每钱包开销 = 分发BNB (H+1) + 分发ERC20 (H+1) + 授权 1 + 买入 1 = 2H+4
|
|
55
|
+
* N ≤ (50 - 固定开销) / (2H+4)
|
|
56
|
+
*/
|
|
57
|
+
function calculateMaxWalletsPerBatch(isERC20Mode, disperseHopCount) {
|
|
58
|
+
const fixedOverhead = 1 + PROFIT_HOP_COUNT + 1; // 贿赂 + 利润多跳
|
|
59
|
+
const maxTxs = 50 - fixedOverhead;
|
|
60
|
+
if (isERC20Mode) {
|
|
61
|
+
// ERC20: 分发BNB (H+1) + 分发ERC20 (H+1) + 授权 1 + 买入 1
|
|
62
|
+
const perWalletTxs = 2 * (disperseHopCount + 1) + 2;
|
|
63
|
+
return Math.floor(maxTxs / perWalletTxs);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// 原生: 分发 (H+1) + 买入 1
|
|
67
|
+
const perWalletTxs = disperseHopCount + 2;
|
|
68
|
+
return Math.floor(maxTxs / perWalletTxs);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 默认值(无分发多跳)
|
|
72
|
+
const DEFAULT_MAX_WALLETS_PER_BATCH_NATIVE = calculateMaxWalletsPerBatch(false, 0);
|
|
73
|
+
const DEFAULT_MAX_WALLETS_PER_BATCH_ERC20 = calculateMaxWalletsPerBatch(true, 0);
|
|
54
74
|
// ============================================================================
|
|
55
75
|
// 辅助函数
|
|
56
76
|
// ============================================================================
|
|
@@ -64,6 +84,127 @@ function chunkArray(arr, size) {
|
|
|
64
84
|
}
|
|
65
85
|
return chunks;
|
|
66
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* 生成分发多跳路径
|
|
89
|
+
* @param targetWallets 目标钱包地址列表
|
|
90
|
+
* @param hopCount 多跳数量(0=直接转账,1=1个中间钱包,2=2个中间钱包...)
|
|
91
|
+
* @param provider Provider 实例
|
|
92
|
+
* @returns 每个目标钱包的多跳路径
|
|
93
|
+
*/
|
|
94
|
+
function generateDisperseHopPaths(targetWallets, hopCount, provider) {
|
|
95
|
+
if (hopCount <= 0) {
|
|
96
|
+
// 无多跳,直接返回空路径
|
|
97
|
+
return targetWallets.map(w => ({
|
|
98
|
+
targetAddress: w.address,
|
|
99
|
+
hopWallets: [],
|
|
100
|
+
hopWalletsInfo: []
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
// 为每个目标钱包生成中间钱包
|
|
104
|
+
return targetWallets.map(targetWallet => {
|
|
105
|
+
const hopWalletsInfo = generateWallets(hopCount);
|
|
106
|
+
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
107
|
+
return {
|
|
108
|
+
targetAddress: targetWallet.address,
|
|
109
|
+
hopWallets,
|
|
110
|
+
hopWalletsInfo
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 构建分发多跳交易链(原生代币)
|
|
116
|
+
*
|
|
117
|
+
* 路径: payer → hop1 → hop2 → ... → target
|
|
118
|
+
*
|
|
119
|
+
* @param isERC20Mode 是否为 ERC20 模式(中间钱包需要预留 ERC20 转账 gas)
|
|
120
|
+
* @returns 签名交易数组
|
|
121
|
+
*/
|
|
122
|
+
async function buildDisperseHopChainNative(payer, path, finalAmount, gasPrice, chainId, txType, payerNonce, isERC20Mode = false) {
|
|
123
|
+
const signedTxs = [];
|
|
124
|
+
const hopCount = path.hopWallets.length;
|
|
125
|
+
if (hopCount === 0) {
|
|
126
|
+
// 无多跳,直接转账
|
|
127
|
+
const tx = await buildNativeTransferTx(payer, path.targetAddress, finalAmount, payerNonce, gasPrice, chainId, txType);
|
|
128
|
+
signedTxs.push(tx);
|
|
129
|
+
return { signedTxs, payerNoncesUsed: 1 };
|
|
130
|
+
}
|
|
131
|
+
// 计算每一跳需要转出的金额(需要预留后续跳的 gas 费)
|
|
132
|
+
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
133
|
+
// ERC20 模式下,中间钱包还需要预留 ERC20 转账的 gas
|
|
134
|
+
const erc20TransferGasCost = isERC20Mode ? ERC20_TRANSFER_GAS_LIMIT * gasPrice : 0n;
|
|
135
|
+
// 从后往前计算每一跳需要的金额
|
|
136
|
+
// 原生模式:
|
|
137
|
+
// 最后一跳(hopN → target)需要: finalAmount + hopGasCost
|
|
138
|
+
// 倒数第二跳需要: finalAmount + 2 * hopGasCost
|
|
139
|
+
// ERC20 模式(中间钱包还需要预留 ERC20 转账 gas):
|
|
140
|
+
// 每个中间钱包额外需要: erc20TransferGasCost
|
|
141
|
+
const amountsPerHop = [];
|
|
142
|
+
for (let i = 0; i < hopCount; i++) {
|
|
143
|
+
const remainingHops = hopCount - i;
|
|
144
|
+
// 中间钱包需要的 BNB = 最终金额 + 后续 BNB 转账 gas + (ERC20模式下) 自己的 ERC20 转账 gas
|
|
145
|
+
const amount = finalAmount
|
|
146
|
+
+ hopGasCost * BigInt(remainingHops)
|
|
147
|
+
+ (isERC20Mode ? erc20TransferGasCost * BigInt(remainingHops) : 0n);
|
|
148
|
+
amountsPerHop.push(amount);
|
|
149
|
+
}
|
|
150
|
+
// 构建交易链
|
|
151
|
+
// 1. payer → hop1 (使用 payer nonce)
|
|
152
|
+
const firstTx = await buildNativeTransferTx(payer, path.hopWallets[0].address, amountsPerHop[0], payerNonce, gasPrice, chainId, txType);
|
|
153
|
+
signedTxs.push(firstTx);
|
|
154
|
+
// 2. hop1 → hop2 → ... → hopN → target (每个中间钱包 nonce=0)
|
|
155
|
+
for (let i = 0; i < hopCount; i++) {
|
|
156
|
+
const fromWallet = path.hopWallets[i];
|
|
157
|
+
const toAddress = i === hopCount - 1
|
|
158
|
+
? path.targetAddress // 最后一跳到目标
|
|
159
|
+
: path.hopWallets[i + 1].address; // 中间跳到下一个 hop
|
|
160
|
+
// 最后一跳只需转最终金额,中间跳需要预留后续费用
|
|
161
|
+
const amount = i === hopCount - 1
|
|
162
|
+
? finalAmount
|
|
163
|
+
: amountsPerHop[i + 1];
|
|
164
|
+
const tx = await buildNativeTransferTx(fromWallet, toAddress, amount, 0, // 中间钱包都是新生成的,nonce=0
|
|
165
|
+
gasPrice, chainId, txType);
|
|
166
|
+
signedTxs.push(tx);
|
|
167
|
+
}
|
|
168
|
+
return { signedTxs, payerNoncesUsed: 1 };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 构建分发多跳交易链(ERC20 代币)
|
|
172
|
+
*
|
|
173
|
+
* 路径: payer → hop1 → hop2 → ... → target
|
|
174
|
+
*
|
|
175
|
+
* 注意:ERC20 多跳需要配合 BNB 多跳使用,中间钱包需要先收到 BNB 作为 gas
|
|
176
|
+
* BNB 多跳先执行,确保中间钱包有 gas 费后再转发 ERC20
|
|
177
|
+
*
|
|
178
|
+
* 中间钱包的 nonce 安排:
|
|
179
|
+
* - nonce=0: BNB 转发(在 buildDisperseHopChainNative 中完成)
|
|
180
|
+
* - nonce=1: ERC20 转发(在本函数中完成)
|
|
181
|
+
*/
|
|
182
|
+
async function buildDisperseHopChainERC20(payer, path, erc20Address, erc20Amount, gasPrice, chainId, txType, payerNonce) {
|
|
183
|
+
const signedTxs = [];
|
|
184
|
+
const hopCount = path.hopWallets.length;
|
|
185
|
+
if (hopCount === 0) {
|
|
186
|
+
// 无多跳,直接转账
|
|
187
|
+
const tx = await buildERC20TransferTx(payer, erc20Address, path.targetAddress, erc20Amount, payerNonce, gasPrice, chainId, txType);
|
|
188
|
+
signedTxs.push(tx);
|
|
189
|
+
return { signedTxs, payerNoncesUsed: 1 };
|
|
190
|
+
}
|
|
191
|
+
// 构建交易链
|
|
192
|
+
// payer 发送 ERC20 到 hop1
|
|
193
|
+
const firstTx = await buildERC20TransferTx(payer, erc20Address, path.hopWallets[0].address, erc20Amount, payerNonce, gasPrice, chainId, txType);
|
|
194
|
+
signedTxs.push(firstTx);
|
|
195
|
+
// 每个中间钱包转发 ERC20
|
|
196
|
+
// nonce=1(因为 nonce=0 已经用于 BNB 转发)
|
|
197
|
+
for (let i = 0; i < hopCount; i++) {
|
|
198
|
+
const fromWallet = path.hopWallets[i];
|
|
199
|
+
const toAddress = i === hopCount - 1
|
|
200
|
+
? path.targetAddress
|
|
201
|
+
: path.hopWallets[i + 1].address;
|
|
202
|
+
const tx = await buildERC20TransferTx(fromWallet, erc20Address, toAddress, erc20Amount, 1, // ✅ nonce=1(nonce=0 用于 BNB 转发)
|
|
203
|
+
gasPrice, chainId, txType);
|
|
204
|
+
signedTxs.push(tx);
|
|
205
|
+
}
|
|
206
|
+
return { signedTxs, payerNoncesUsed: 1 };
|
|
207
|
+
}
|
|
67
208
|
/**
|
|
68
209
|
* 获取 ERC20 稳定币地址
|
|
69
210
|
*/
|
|
@@ -264,26 +405,35 @@ async function buildV2BuyTx(wallet, tokenAddress, buyAmount, nonce, gasPrice, ga
|
|
|
264
405
|
/**
|
|
265
406
|
* 构建 V3 单跳买入交易
|
|
266
407
|
* BNB → Token (使用 exactInputSingle + multicall)
|
|
408
|
+
*
|
|
409
|
+
* ✅ 修复:使用 ethers.Interface 手动编码 calldata,避免 multicall 重载问题
|
|
267
410
|
*/
|
|
268
411
|
async function buildV3BuyTx(wallet, tokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType, v3Fee = 2500 // 默认 0.25% 手续费
|
|
269
412
|
) {
|
|
270
413
|
const deadline = getDeadline();
|
|
271
414
|
const v3RouterIface = new ethers.Interface(V3_ROUTER02_ABI);
|
|
272
415
|
// 构建 exactInputSingle calldata
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
416
|
+
const swapParams = {
|
|
417
|
+
tokenIn: WBNB_ADDRESS,
|
|
418
|
+
tokenOut: tokenAddress,
|
|
419
|
+
fee: v3Fee,
|
|
420
|
+
recipient: wallet.address,
|
|
421
|
+
amountIn: buyAmount,
|
|
422
|
+
amountOutMinimum: 0n,
|
|
423
|
+
sqrtPriceLimitX96: 0n
|
|
424
|
+
};
|
|
425
|
+
const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
426
|
+
// ✅ 添加 refundETH,退还多余的 ETH
|
|
427
|
+
const refundETHData = v3RouterIface.encodeFunctionData('refundETH', []);
|
|
428
|
+
// ✅ 使用明确的函数签名编码 multicall,避免重载问题
|
|
429
|
+
const multicallData = v3RouterIface.encodeFunctionData('multicall(uint256,bytes[])', [
|
|
430
|
+
deadline,
|
|
431
|
+
[exactInputSingleData, refundETHData]
|
|
432
|
+
]);
|
|
285
433
|
const tx = {
|
|
286
|
-
|
|
434
|
+
to: PANCAKE_V3_ROUTER_ADDRESS,
|
|
435
|
+
data: multicallData,
|
|
436
|
+
value: buyAmount,
|
|
287
437
|
nonce,
|
|
288
438
|
gasLimit: BigInt(gasLimit),
|
|
289
439
|
chainId
|
|
@@ -331,25 +481,32 @@ async function buildV2BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buy
|
|
|
331
481
|
/**
|
|
332
482
|
* 构建 V3 买入交易(ERC20 输入)
|
|
333
483
|
* USDT/USDC → Token (使用 exactInputSingle + multicall)
|
|
484
|
+
*
|
|
485
|
+
* ✅ 修复:使用 ethers.Interface 手动编码 calldata,避免 multicall 重载问题
|
|
334
486
|
*/
|
|
335
487
|
async function buildV3BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buyAmount, nonce, gasPrice, gasLimit, chainId, txType, v3Fee = 2500) {
|
|
336
488
|
const deadline = getDeadline();
|
|
337
489
|
const v3RouterIface = new ethers.Interface(V3_ROUTER02_ABI);
|
|
338
490
|
// 构建 exactInputSingle calldata
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
491
|
+
const swapParams = {
|
|
492
|
+
tokenIn: baseTokenAddress,
|
|
493
|
+
tokenOut: tokenAddress,
|
|
494
|
+
fee: v3Fee,
|
|
495
|
+
recipient: wallet.address,
|
|
496
|
+
amountIn: buyAmount,
|
|
497
|
+
amountOutMinimum: 0n,
|
|
498
|
+
sqrtPriceLimitX96: 0n
|
|
499
|
+
};
|
|
500
|
+
const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
501
|
+
// ✅ 使用明确的函数签名编码 multicall,避免重载问题
|
|
502
|
+
const multicallData = v3RouterIface.encodeFunctionData('multicall(uint256,bytes[])', [
|
|
503
|
+
deadline,
|
|
504
|
+
[exactInputSingleData]
|
|
505
|
+
]);
|
|
351
506
|
const tx = {
|
|
352
|
-
|
|
507
|
+
to: PANCAKE_V3_ROUTER_ADDRESS,
|
|
508
|
+
data: multicallData,
|
|
509
|
+
value: 0n,
|
|
353
510
|
nonce,
|
|
354
511
|
gasLimit: BigInt(gasLimit),
|
|
355
512
|
chainId
|
|
@@ -389,10 +546,12 @@ async function buildV3BuyTxWithERC20(wallet, tokenAddress, baseTokenAddress, buy
|
|
|
389
546
|
*/
|
|
390
547
|
export async function holdersMaker(params) {
|
|
391
548
|
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
|
|
549
|
+
const { rpcUrl, chain = 'BSC', chainId: configChainId, tradeType = 'flap', gasLimit = DEFAULT_GAS_LIMIT, gasPriceGwei = DEFAULT_GAS_PRICE_GWEI, bribeAmount = 0.000001, txType = 0, disperseHopCount = 0 // ✅ 分发多跳数(默认0=直接转账)
|
|
550
|
+
} = config;
|
|
393
551
|
const result = {
|
|
394
552
|
success: false,
|
|
395
553
|
newWallets: [],
|
|
554
|
+
disperseHopWallets: [], // ✅ 中间钱包列表
|
|
396
555
|
signedTransactions: [],
|
|
397
556
|
batchResults: [],
|
|
398
557
|
successBatchCount: 0,
|
|
@@ -410,9 +569,10 @@ export async function holdersMaker(params) {
|
|
|
410
569
|
result.error = `不支持的交易类型: ${tradeType},支持: ${supportedTradeTypes.join(', ')}`;
|
|
411
570
|
return result;
|
|
412
571
|
}
|
|
413
|
-
//
|
|
572
|
+
// ✅ 根据分发多跳数动态计算每批最大钱包数
|
|
414
573
|
const maxWalletsPerBatch = config.maxWalletsPerBatch ||
|
|
415
|
-
(isERC20Mode
|
|
574
|
+
calculateMaxWalletsPerBatch(isERC20Mode, disperseHopCount);
|
|
575
|
+
console.log(`[HoldersMaker] 分发多跳数: ${disperseHopCount}, 每批最大钱包数: ${maxWalletsPerBatch}`);
|
|
416
576
|
try {
|
|
417
577
|
// 1. 初始化
|
|
418
578
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
@@ -463,13 +623,27 @@ export async function holdersMaker(params) {
|
|
|
463
623
|
const totalProfit = (totalBuyAmountForProfit * BigInt(profitRateBps)) / 10000n;
|
|
464
624
|
const profitPerBatch = totalProfit / BigInt(walletBatches.length);
|
|
465
625
|
console.log(`[HoldersMaker] 总利润: ${ethers.formatEther(totalProfit)} BNB`);
|
|
466
|
-
// 6.
|
|
626
|
+
// 6. 生成分发多跳路径(如果启用)
|
|
627
|
+
let allDisperseHopWallets = [];
|
|
628
|
+
const disperseHopPaths = generateDisperseHopPaths(newWallets, disperseHopCount, provider);
|
|
629
|
+
if (disperseHopCount > 0) {
|
|
630
|
+
// 收集所有中间钱包信息用于导出
|
|
631
|
+
for (const path of disperseHopPaths) {
|
|
632
|
+
allDisperseHopWallets.push(...path.hopWalletsInfo);
|
|
633
|
+
}
|
|
634
|
+
result.disperseHopWallets = allDisperseHopWallets;
|
|
635
|
+
console.log(`[HoldersMaker] 分发多跳: ${disperseHopCount} 跳,共生成 ${allDisperseHopWallets.length} 个中间钱包`);
|
|
636
|
+
}
|
|
637
|
+
// 7. 并行生成所有批次的签名
|
|
467
638
|
const batchPromises = walletBatches.map(async (batch, batchIdx) => {
|
|
468
639
|
try {
|
|
469
640
|
const signedTxs = [];
|
|
470
641
|
// 计算这批需要的 payer nonce 数量
|
|
471
|
-
//
|
|
472
|
-
//
|
|
642
|
+
// 原生模式(无多跳): 贿赂 1 + 分发 N + 利润 (PROFIT_HOP_COUNT + 1)
|
|
643
|
+
// 原生模式(有多跳H): 贿赂 1 + 分发首跳 N + 利润 (PROFIT_HOP_COUNT + 1)
|
|
644
|
+
// ERC20模式(无多跳): 贿赂 1 + 分发BNB N + 分发ERC20 N + 利润 (PROFIT_HOP_COUNT + 1)
|
|
645
|
+
// ERC20模式(有多跳H): 贿赂 1 + 分发BNB首跳 N + 分发ERC20首跳 N + 利润 (PROFIT_HOP_COUNT + 1)
|
|
646
|
+
// 注:多跳中间钱包的交易不消耗 payer nonce
|
|
473
647
|
const payerNonceCount = isERC20Mode
|
|
474
648
|
? 1 + batch.length * 2 + PROFIT_HOP_COUNT + 1
|
|
475
649
|
: 1 + batch.length + PROFIT_HOP_COUNT + 1;
|
|
@@ -478,16 +652,22 @@ export async function holdersMaker(params) {
|
|
|
478
652
|
// (1) 贿赂交易
|
|
479
653
|
const bribeTx = await buildNativeTransferTx(payer, BLOCKRAZOR_BUILDER_EOA, bribeAmountWei, payerNonces[payerNonceIdx++], gasPrice, chainId, txType);
|
|
480
654
|
signedTxs.push(bribeTx);
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
655
|
+
// 获取当前批次对应的多跳路径
|
|
656
|
+
const batchStartIdx = batchIdx * maxWalletsPerBatch;
|
|
657
|
+
const batchPaths = disperseHopPaths.slice(batchStartIdx, batchStartIdx + batch.length);
|
|
658
|
+
// (2) 分发原生代币(支持多跳)
|
|
659
|
+
for (let i = 0; i < batch.length; i++) {
|
|
660
|
+
const path = batchPaths[i];
|
|
661
|
+
const { signedTxs: hopTxs } = await buildDisperseHopChainNative(payer, path, transferNativePerWallet, gasPrice, chainId, txType, payerNonces[payerNonceIdx++], isERC20Mode // ✅ ERC20 模式下,中间钱包需要预留 ERC20 转账 gas
|
|
662
|
+
);
|
|
663
|
+
signedTxs.push(...hopTxs);
|
|
485
664
|
}
|
|
486
|
-
// (3) ERC20 模式:分发 ERC20
|
|
665
|
+
// (3) ERC20 模式:分发 ERC20 代币(支持多跳)
|
|
487
666
|
if (isERC20Mode && erc20TokenAddress) {
|
|
488
|
-
for (
|
|
489
|
-
const
|
|
490
|
-
signedTxs
|
|
667
|
+
for (let i = 0; i < batch.length; i++) {
|
|
668
|
+
const path = batchPaths[i];
|
|
669
|
+
const { signedTxs: hopTxs } = await buildDisperseHopChainERC20(payer, path, erc20TokenAddress, buyAmountWei, gasPrice, chainId, txType, payerNonces[payerNonceIdx++]);
|
|
670
|
+
signedTxs.push(...hopTxs);
|
|
491
671
|
}
|
|
492
672
|
}
|
|
493
673
|
// (4) ERC20 模式:授权交易(新钱包 nonce=0)
|