four-flap-meme-sdk 1.4.83 → 1.4.84
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.
|
@@ -12,6 +12,19 @@ import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_
|
|
|
12
12
|
import { FLAP_PORTAL_ADDRESSES, FLAP_ORIGINAL_PORTAL_ADDRESSES } from '../constants.js';
|
|
13
13
|
import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../utils/constants.js';
|
|
14
14
|
import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from './config.js';
|
|
15
|
+
// ==================== ERC20 ABI ====================
|
|
16
|
+
const ERC20_ABI = [
|
|
17
|
+
{
|
|
18
|
+
type: "function",
|
|
19
|
+
name: "approve",
|
|
20
|
+
inputs: [
|
|
21
|
+
{ name: "spender", type: "address" },
|
|
22
|
+
{ name: "amount", type: "uint256" }
|
|
23
|
+
],
|
|
24
|
+
outputs: [{ name: "", type: "bool" }],
|
|
25
|
+
stateMutability: "nonpayable"
|
|
26
|
+
}
|
|
27
|
+
];
|
|
15
28
|
// ==================== 链常量 ====================
|
|
16
29
|
const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
|
|
17
30
|
const BSC_PANCAKE_V3_ROUTER = ADDRESSES.BSC.PancakeV3Router;
|
|
@@ -57,9 +70,36 @@ const PANCAKE_V3_ROUTER_ABI = [
|
|
|
57
70
|
],
|
|
58
71
|
"outputs": [{ "name": "amountOut", "type": "uint256" }],
|
|
59
72
|
"stateMutability": "payable"
|
|
60
|
-
}
|
|
73
|
+
},
|
|
61
74
|
];
|
|
75
|
+
// ==================== V3 费率配置 ====================
|
|
76
|
+
// USD1/USDT 使用 100 bps (0.01%),其他使用 2500 bps (0.25%)
|
|
77
|
+
const USD1_V3_FEE = 100;
|
|
78
|
+
const DEFAULT_V3_FEE = 2500;
|
|
62
79
|
// ==================== 工具函数 ====================
|
|
80
|
+
/** 构建 ERC20 approve 交易 */
|
|
81
|
+
async function buildApproveTransaction(wallet, tokenAddress, spenderAddress, amount, nonce, gasPrice, txType) {
|
|
82
|
+
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
83
|
+
const data = erc20Interface.encodeFunctionData('approve', [spenderAddress, amount]);
|
|
84
|
+
const tx = {
|
|
85
|
+
to: tokenAddress,
|
|
86
|
+
data,
|
|
87
|
+
from: wallet.address,
|
|
88
|
+
nonce,
|
|
89
|
+
gasLimit: BigInt(100000), // approve 交易 gas 较低
|
|
90
|
+
chainId: 56,
|
|
91
|
+
type: txType
|
|
92
|
+
};
|
|
93
|
+
if (txType === 2) {
|
|
94
|
+
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
95
|
+
tx.maxFeePerGas = gasPrice;
|
|
96
|
+
tx.maxPriorityFeePerGas = priorityFee;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
tx.gasPrice = gasPrice;
|
|
100
|
+
}
|
|
101
|
+
return wallet.signTransaction(tx);
|
|
102
|
+
}
|
|
63
103
|
function getGasLimit(config, defaultGas = 800000) {
|
|
64
104
|
if (config.gasLimit !== undefined) {
|
|
65
105
|
return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
|
|
@@ -232,17 +272,27 @@ export async function flapBundleCreateToDex(params) {
|
|
|
232
272
|
}
|
|
233
273
|
// 3. 内盘买入交易(包含毕业触发)
|
|
234
274
|
// 最后一个买入交易会触发毕业,需要更多 gas
|
|
275
|
+
// ✅ ERC20 需要先 approve,再买入
|
|
235
276
|
const curveBuyTxPromises = curveWallets.map(async ({ wallet }, i) => {
|
|
236
277
|
const addr = wallet.address.toLowerCase();
|
|
237
|
-
const
|
|
238
|
-
|
|
278
|
+
const signedTxs = [];
|
|
279
|
+
// ✅ ERC20: 先构建 approve 交易
|
|
280
|
+
if (!useNativeToken) {
|
|
281
|
+
const approveNonce = noncesMap.get(addr);
|
|
282
|
+
noncesMap.set(addr, approveNonce + 1);
|
|
283
|
+
const approveTx = await buildApproveTransaction(wallet, quoteToken, portalAddress, curveBuyAmounts[i], approveNonce, gasPrice, txType);
|
|
284
|
+
signedTxs.push(approveTx);
|
|
285
|
+
}
|
|
286
|
+
// 构建买入交易
|
|
287
|
+
const buyNonce = noncesMap.get(addr);
|
|
288
|
+
noncesMap.set(addr, buyNonce + 1);
|
|
239
289
|
const portal = new Contract(portalAddress, PORTAL_ABI, wallet);
|
|
240
290
|
const unsigned = await portal.swapExactInput.populateTransaction({
|
|
241
291
|
inputToken,
|
|
242
292
|
outputToken: tokenAddress,
|
|
243
293
|
inputAmount: curveBuyAmounts[i],
|
|
244
294
|
minOutputAmount: 0n,
|
|
245
|
-
permitData: '0x'
|
|
295
|
+
permitData: '0x' // 不使用 permit,使用链上 approve
|
|
246
296
|
}, useNativeToken ? { value: curveBuyAmounts[i] } : {});
|
|
247
297
|
// 最后一个买家触发毕业,需要更多 gas
|
|
248
298
|
const isLastBuyer = i === curveWallets.length - 1;
|
|
@@ -250,7 +300,7 @@ export async function flapBundleCreateToDex(params) {
|
|
|
250
300
|
const tx = {
|
|
251
301
|
...unsigned,
|
|
252
302
|
from: wallet.address,
|
|
253
|
-
nonce,
|
|
303
|
+
nonce: buyNonce,
|
|
254
304
|
gasLimit: buyGasLimit,
|
|
255
305
|
chainId: 56,
|
|
256
306
|
type: txType,
|
|
@@ -263,67 +313,63 @@ export async function flapBundleCreateToDex(params) {
|
|
|
263
313
|
else {
|
|
264
314
|
tx.gasPrice = gasPrice;
|
|
265
315
|
}
|
|
266
|
-
|
|
316
|
+
const signedBuy = await wallet.signTransaction(tx);
|
|
317
|
+
signedTxs.push(signedBuy);
|
|
318
|
+
return signedTxs;
|
|
267
319
|
});
|
|
268
|
-
const
|
|
269
|
-
|
|
320
|
+
const curveBuyResults = await Promise.all(curveBuyTxPromises);
|
|
321
|
+
// 展平数组:每个钱包可能有 [approve, buy] 或只有 [buy]
|
|
322
|
+
curveBuyResults.forEach(txs => allTransactions.push(...txs));
|
|
270
323
|
// 4. 外盘买入交易(PancakeSwap)
|
|
324
|
+
// ✅ ERC20 需要先 approve,再 swap
|
|
271
325
|
if (enableDexBuy && dexWallets.length > 0) {
|
|
272
326
|
const dexBuyTxPromises = dexWallets.map(async ({ wallet }, i) => {
|
|
273
327
|
const addr = wallet.address.toLowerCase();
|
|
274
|
-
const
|
|
275
|
-
noncesMap.set(addr, nonce + 1);
|
|
328
|
+
const signedTxs = [];
|
|
276
329
|
const buyAmount = dexBuyAmounts[i];
|
|
277
330
|
const smartRouter = dexPoolType === 'v3' ? BSC_PANCAKE_V3_ROUTER : BSC_PANCAKE_V2_ROUTER;
|
|
278
|
-
|
|
331
|
+
// ✅ 根据 quoteToken 选择 V3 费率
|
|
332
|
+
const actualV3Fee = v3Fee || (useNativeToken ? DEFAULT_V3_FEE : USD1_V3_FEE);
|
|
333
|
+
// ✅ ERC20: 先构建 approve 交易
|
|
334
|
+
if (!useNativeToken) {
|
|
335
|
+
const approveNonce = noncesMap.get(addr);
|
|
336
|
+
noncesMap.set(addr, approveNonce + 1);
|
|
337
|
+
const approveTx = await buildApproveTransaction(wallet, quoteToken, smartRouter, buyAmount, approveNonce, gasPrice, txType);
|
|
338
|
+
signedTxs.push(approveTx);
|
|
339
|
+
}
|
|
340
|
+
// 构建买入交易
|
|
341
|
+
const buyNonce = noncesMap.get(addr);
|
|
342
|
+
noncesMap.set(addr, buyNonce + 1);
|
|
343
|
+
let multicallData = [];
|
|
279
344
|
let value;
|
|
345
|
+
const routerInterface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
280
346
|
if (dexPoolType === 'v3') {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
value = buyAmount;
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
const params = {
|
|
297
|
-
tokenIn: quoteToken,
|
|
298
|
-
tokenOut: tokenAddress,
|
|
299
|
-
fee: v3Fee,
|
|
300
|
-
recipient: wallet.address,
|
|
301
|
-
amountIn: buyAmount,
|
|
302
|
-
amountOutMinimum: 0n,
|
|
303
|
-
sqrtPriceLimitX96: 0n
|
|
304
|
-
};
|
|
305
|
-
const exactInputSingleData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('exactInputSingle', [params]);
|
|
306
|
-
calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[exactInputSingleData]]);
|
|
307
|
-
value = 0n;
|
|
308
|
-
}
|
|
347
|
+
// V3: exactInputSingle
|
|
348
|
+
const params = {
|
|
349
|
+
tokenIn: useNativeToken ? BSC_WBNB : quoteToken,
|
|
350
|
+
tokenOut: tokenAddress,
|
|
351
|
+
fee: actualV3Fee,
|
|
352
|
+
recipient: wallet.address,
|
|
353
|
+
amountIn: buyAmount,
|
|
354
|
+
amountOutMinimum: 0n,
|
|
355
|
+
sqrtPriceLimitX96: 0n
|
|
356
|
+
};
|
|
357
|
+
multicallData = [routerInterface.encodeFunctionData('exactInputSingle', [params])];
|
|
358
|
+
value = useNativeToken ? buyAmount : 0n;
|
|
309
359
|
}
|
|
310
360
|
else {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
else {
|
|
317
|
-
const swapData = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('swapExactTokensForTokens', [buyAmount, 0n, [quoteToken, tokenAddress], wallet.address]);
|
|
318
|
-
calldata = new ethers.Interface(PANCAKE_V3_ROUTER_ABI).encodeFunctionData('multicall', [[swapData]]);
|
|
319
|
-
value = 0n;
|
|
320
|
-
}
|
|
361
|
+
// V2: swapExactTokensForTokens
|
|
362
|
+
const path = useNativeToken ? [BSC_WBNB, tokenAddress] : [quoteToken, tokenAddress];
|
|
363
|
+
const swapData = routerInterface.encodeFunctionData('swapExactTokensForTokens', [buyAmount, 0n, path, wallet.address]);
|
|
364
|
+
multicallData = [swapData];
|
|
365
|
+
value = useNativeToken ? buyAmount : 0n;
|
|
321
366
|
}
|
|
367
|
+
const calldata = routerInterface.encodeFunctionData('multicall', [multicallData]);
|
|
322
368
|
const tx = {
|
|
323
369
|
to: smartRouter,
|
|
324
370
|
data: calldata,
|
|
325
371
|
from: wallet.address,
|
|
326
|
-
nonce,
|
|
372
|
+
nonce: buyNonce,
|
|
327
373
|
gasLimit: BigInt(500000),
|
|
328
374
|
chainId: 56,
|
|
329
375
|
type: txType,
|
|
@@ -336,10 +382,13 @@ export async function flapBundleCreateToDex(params) {
|
|
|
336
382
|
else {
|
|
337
383
|
tx.gasPrice = gasPrice;
|
|
338
384
|
}
|
|
339
|
-
|
|
385
|
+
const signedBuy = await wallet.signTransaction(tx);
|
|
386
|
+
signedTxs.push(signedBuy);
|
|
387
|
+
return signedTxs;
|
|
340
388
|
});
|
|
341
|
-
const
|
|
342
|
-
|
|
389
|
+
const dexBuyResults = await Promise.all(dexBuyTxPromises);
|
|
390
|
+
// 展平数组:每个钱包可能有 [approve, buy] 或只有 [buy]
|
|
391
|
+
dexBuyResults.forEach(txs => allTransactions.push(...txs));
|
|
343
392
|
}
|
|
344
393
|
// 5. 利润多跳转账
|
|
345
394
|
let profitHopWallets;
|