four-flap-meme-sdk 1.4.78 → 1.4.79
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/contracts/tm-bundle-merkle/swap-buy-first.d.ts +4 -2
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +191 -2
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +4 -2
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +219 -2
- package/dist/pancake/bundle-buy-first.d.ts +4 -2
- package/dist/pancake/bundle-buy-first.js +290 -2
- package/package.json +1 -1
|
@@ -18,10 +18,12 @@ export interface FourBuyFirstConfig extends CommonBundleConfig {
|
|
|
18
18
|
waitTimeoutMs?: number;
|
|
19
19
|
}
|
|
20
20
|
export interface FourBundleBuyFirstSignParams {
|
|
21
|
-
buyerPrivateKey
|
|
21
|
+
buyerPrivateKey?: string;
|
|
22
|
+
buyerPrivateKeys?: string[];
|
|
22
23
|
buyerFunds?: string;
|
|
23
24
|
buyerFundsPercentage?: number;
|
|
24
|
-
sellerPrivateKey
|
|
25
|
+
sellerPrivateKey?: string;
|
|
26
|
+
sellerPrivateKeys?: string[];
|
|
25
27
|
tokenAddress: string;
|
|
26
28
|
config: FourBuyFirstSignConfig;
|
|
27
29
|
buyCount?: number;
|
|
@@ -83,10 +83,26 @@ function splitAmount(totalAmount, count) {
|
|
|
83
83
|
return amounts;
|
|
84
84
|
}
|
|
85
85
|
export async function fourBundleBuyFirstMerkle(params) {
|
|
86
|
-
const { buyerPrivateKey, buyerFunds, buyerFundsPercentage, sellerPrivateKey, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
87
|
-
// ✅
|
|
86
|
+
const { buyerPrivateKey, buyerPrivateKeys, buyerFunds, buyerFundsPercentage, sellerPrivateKey, sellerPrivateKeys, tokenAddress, config, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
87
|
+
// ✅ 判断是否为多钱包模式
|
|
88
|
+
const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
|
|
89
|
+
!!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
|
|
88
90
|
const buyCount = _buyCount ?? 1;
|
|
89
91
|
const sellCount = _sellCount ?? 1;
|
|
92
|
+
// ✅ 多钱包模式:使用单独的处理逻辑
|
|
93
|
+
if (isMultiWalletMode) {
|
|
94
|
+
return await fourBundleBuyFirstMultiWallet({
|
|
95
|
+
buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
|
|
96
|
+
sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
|
|
97
|
+
tokenAddress,
|
|
98
|
+
buyerFunds,
|
|
99
|
+
config
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// ✅ 单钱包模式(向后兼容)
|
|
103
|
+
if (!buyerPrivateKey || !sellerPrivateKey) {
|
|
104
|
+
throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
|
|
105
|
+
}
|
|
90
106
|
validateSwapCounts(buyCount, sellCount);
|
|
91
107
|
// ✅ 计算利润比例:每笔万分之3
|
|
92
108
|
const totalTxCount = buyCount + sellCount;
|
|
@@ -292,3 +308,176 @@ async function planMultiNonces({ buyer, seller, buyCount, sellCount, extractProf
|
|
|
292
308
|
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
293
309
|
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
294
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* ✅ Four 多钱包捆绑换手
|
|
313
|
+
* - 多个买方钱包执行买入(每个钱包1笔)
|
|
314
|
+
* - 多个卖方钱包执行卖出(每个钱包1笔)
|
|
315
|
+
* - 买入总价值 = 卖出总价值
|
|
316
|
+
*/
|
|
317
|
+
async function fourBundleBuyFirstMultiWallet(params) {
|
|
318
|
+
const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config } = params;
|
|
319
|
+
const buyCount = buyerPrivateKeys.length;
|
|
320
|
+
const sellCount = sellerPrivateKeys.length;
|
|
321
|
+
if (buyCount === 0)
|
|
322
|
+
throw new Error('买方钱包数量不能为0');
|
|
323
|
+
if (sellCount === 0)
|
|
324
|
+
throw new Error('卖方钱包数量不能为0');
|
|
325
|
+
// 验证总交易数不超过限制
|
|
326
|
+
validateSwapCounts(buyCount, sellCount);
|
|
327
|
+
// ✅ 计算利润比例:每笔万分之6
|
|
328
|
+
const totalTxCount = buyCount + sellCount;
|
|
329
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
330
|
+
const chainIdNum = config.chainId ?? 56;
|
|
331
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
332
|
+
chainId: chainIdNum,
|
|
333
|
+
name: 'BSC'
|
|
334
|
+
});
|
|
335
|
+
const nonceManager = new NonceManager(provider);
|
|
336
|
+
// 创建所有钱包实例
|
|
337
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, provider));
|
|
338
|
+
const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, provider));
|
|
339
|
+
// 使用第一个卖方作为主卖方(支付贿赂和利润)
|
|
340
|
+
const mainSeller = sellers[0];
|
|
341
|
+
// ✅ 计算总交易金额
|
|
342
|
+
if (!buyerFunds) {
|
|
343
|
+
throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
|
|
344
|
+
}
|
|
345
|
+
const totalFundsWei = ethers.parseEther(String(buyerFunds));
|
|
346
|
+
if (totalFundsWei <= 0n) {
|
|
347
|
+
throw new Error('交易金额必须大于0');
|
|
348
|
+
}
|
|
349
|
+
// ✅ 获取报价:买入能获得多少代币
|
|
350
|
+
const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
|
|
351
|
+
const buyQuote = await helper3.tryBuy(tokenAddress, 0n, totalFundsWei);
|
|
352
|
+
const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
|
|
353
|
+
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
354
|
+
throw new Error('报价失败:无法估算可买入的代币数量');
|
|
355
|
+
}
|
|
356
|
+
// ✅ 将总金额平均分配给买方
|
|
357
|
+
const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
|
|
358
|
+
// ✅ 将代币平均分配给卖方
|
|
359
|
+
const sellAmountsWei = splitAmount(estimatedTokenAmount, sellCount);
|
|
360
|
+
const finalGasLimit = getGasLimit(config);
|
|
361
|
+
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
362
|
+
const txType = getTxType(config);
|
|
363
|
+
// ✅ 估算利润
|
|
364
|
+
const sellResult = await trySell('BSC', config.rpcUrl, tokenAddress, estimatedTokenAmount);
|
|
365
|
+
const estimatedSellFunds = sellResult.funds;
|
|
366
|
+
const profitAmount = (estimatedSellFunds * BigInt(profitRateBps)) / 10000n;
|
|
367
|
+
// ✅ 获取贿赂金额
|
|
368
|
+
const bribeAmount = getBribeAmount(config);
|
|
369
|
+
const needBribeTx = bribeAmount > 0n;
|
|
370
|
+
// ✅ 获取所有钱包的 nonces
|
|
371
|
+
const noncesMap = new Map();
|
|
372
|
+
await Promise.all([...sellers, ...buyers].map(async (wallet) => {
|
|
373
|
+
const addr = wallet.address.toLowerCase();
|
|
374
|
+
if (!noncesMap.has(addr)) {
|
|
375
|
+
const nonce = await nonceManager.getNextNonce(wallet);
|
|
376
|
+
noncesMap.set(addr, nonce);
|
|
377
|
+
}
|
|
378
|
+
}));
|
|
379
|
+
// ✅ 构建交易列表
|
|
380
|
+
const allTransactions = [];
|
|
381
|
+
// 1. 贿赂交易(由主卖方支付)
|
|
382
|
+
if (needBribeTx) {
|
|
383
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
384
|
+
const bribeNonce = noncesMap.get(mainSellerAddr);
|
|
385
|
+
noncesMap.set(mainSellerAddr, bribeNonce + 1);
|
|
386
|
+
const bribeTx = await mainSeller.signTransaction({
|
|
387
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
388
|
+
value: bribeAmount,
|
|
389
|
+
nonce: bribeNonce,
|
|
390
|
+
gasPrice,
|
|
391
|
+
gasLimit: 21000n,
|
|
392
|
+
chainId: chainIdNum,
|
|
393
|
+
type: txType
|
|
394
|
+
});
|
|
395
|
+
allTransactions.push(bribeTx);
|
|
396
|
+
}
|
|
397
|
+
// 2. 构建所有买入交易
|
|
398
|
+
const tm = new Contract(TM_ADDRESS, TM_ABI, provider);
|
|
399
|
+
const buyTxPromises = buyers.map(async (buyer, i) => {
|
|
400
|
+
const buyAmount = buyAmountsWei[i];
|
|
401
|
+
const buyerAddr = buyer.address.toLowerCase();
|
|
402
|
+
const nonce = noncesMap.get(buyerAddr);
|
|
403
|
+
noncesMap.set(buyerAddr, nonce + 1);
|
|
404
|
+
const tmBuyer = tm.connect(buyer);
|
|
405
|
+
const unsigned = await tmBuyer.buyTokenAMAP.populateTransaction(0n, tokenAddress, buyer.address, buyAmount, 0n, { value: buyAmount });
|
|
406
|
+
return buyer.signTransaction({
|
|
407
|
+
...unsigned,
|
|
408
|
+
from: buyer.address,
|
|
409
|
+
nonce,
|
|
410
|
+
gasLimit: finalGasLimit,
|
|
411
|
+
gasPrice,
|
|
412
|
+
chainId: chainIdNum,
|
|
413
|
+
type: txType,
|
|
414
|
+
value: buyAmount
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
// 3. 构建所有卖出交易
|
|
418
|
+
const sellTxPromises = sellers.map(async (seller, i) => {
|
|
419
|
+
const sellAmount = sellAmountsWei[i];
|
|
420
|
+
const sellerAddr = seller.address.toLowerCase();
|
|
421
|
+
const nonce = noncesMap.get(sellerAddr);
|
|
422
|
+
noncesMap.set(sellerAddr, nonce + 1);
|
|
423
|
+
const tmSeller = tm.connect(seller);
|
|
424
|
+
const unsigned = await tmSeller.sellToken.populateTransaction(0n, tokenAddress, sellAmount, 0n);
|
|
425
|
+
return seller.signTransaction({
|
|
426
|
+
...unsigned,
|
|
427
|
+
from: seller.address,
|
|
428
|
+
nonce,
|
|
429
|
+
gasLimit: finalGasLimit,
|
|
430
|
+
gasPrice,
|
|
431
|
+
chainId: chainIdNum,
|
|
432
|
+
type: txType,
|
|
433
|
+
value: 0n
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
// ✅ 并行签名所有买卖交易
|
|
437
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
438
|
+
Promise.all(buyTxPromises),
|
|
439
|
+
Promise.all(sellTxPromises)
|
|
440
|
+
]);
|
|
441
|
+
// 先买后卖:买入交易在前
|
|
442
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
443
|
+
// 4. 利润多跳转账(由主卖方支付)
|
|
444
|
+
let profitHopWallets;
|
|
445
|
+
if (profitAmount > 0n) {
|
|
446
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
447
|
+
const profitNonce = noncesMap.get(mainSellerAddr);
|
|
448
|
+
const profitHopResult = await buildProfitHopTransactions({
|
|
449
|
+
provider,
|
|
450
|
+
payerWallet: mainSeller,
|
|
451
|
+
profitAmount,
|
|
452
|
+
profitRecipient: getProfitRecipient(),
|
|
453
|
+
hopCount: PROFIT_HOP_COUNT,
|
|
454
|
+
gasPrice,
|
|
455
|
+
chainId: chainIdNum,
|
|
456
|
+
txType,
|
|
457
|
+
startNonce: profitNonce
|
|
458
|
+
});
|
|
459
|
+
allTransactions.push(...profitHopResult.signedTransactions);
|
|
460
|
+
profitHopWallets = profitHopResult.hopWallets;
|
|
461
|
+
}
|
|
462
|
+
nonceManager.clearTemp();
|
|
463
|
+
// 获取代币精度用于显示
|
|
464
|
+
const erc20 = new Contract(tokenAddress, [
|
|
465
|
+
'function decimals() view returns (uint8)'
|
|
466
|
+
], provider);
|
|
467
|
+
const decimals = await erc20.decimals();
|
|
468
|
+
return {
|
|
469
|
+
signedTransactions: allTransactions,
|
|
470
|
+
profitHopWallets,
|
|
471
|
+
metadata: {
|
|
472
|
+
buyerAddress: buyers.map(b => b.address).join(','),
|
|
473
|
+
sellerAddress: sellers.map(s => s.address).join(','),
|
|
474
|
+
buyAmount: ethers.formatEther(totalFundsWei),
|
|
475
|
+
sellAmount: ethers.formatUnits(estimatedTokenAmount, decimals),
|
|
476
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
477
|
+
buyCount,
|
|
478
|
+
sellCount,
|
|
479
|
+
buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
|
|
480
|
+
sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, decimals))
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
@@ -22,8 +22,10 @@ export interface FlapBuyFirstConfig extends CommonBundleConfig {
|
|
|
22
22
|
}
|
|
23
23
|
export interface FlapBundleBuyFirstSignParams {
|
|
24
24
|
chain: FlapChain;
|
|
25
|
-
buyerPrivateKey
|
|
26
|
-
|
|
25
|
+
buyerPrivateKey?: string;
|
|
26
|
+
buyerPrivateKeys?: string[];
|
|
27
|
+
sellerPrivateKey?: string;
|
|
28
|
+
sellerPrivateKeys?: string[];
|
|
27
29
|
tokenAddress: string;
|
|
28
30
|
buyerFunds?: string;
|
|
29
31
|
buyerFundsPercentage?: number;
|
|
@@ -100,10 +100,29 @@ function getNativeTokenName(chain) {
|
|
|
100
100
|
}
|
|
101
101
|
// 使用公共工具的 getGasLimit,移除本地重复实现
|
|
102
102
|
export async function flapBundleBuyFirstMerkle(params) {
|
|
103
|
-
const { chain, buyerPrivateKey, sellerPrivateKey, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
104
|
-
// ✅
|
|
103
|
+
const { chain, buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals, buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
104
|
+
// ✅ 判断是否为多钱包模式
|
|
105
|
+
const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
|
|
106
|
+
!!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
|
|
105
107
|
const buyCount = _buyCount ?? 1;
|
|
106
108
|
const sellCount = _sellCount ?? 1;
|
|
109
|
+
// ✅ 多钱包模式:使用单独的处理逻辑
|
|
110
|
+
if (isMultiWalletMode) {
|
|
111
|
+
return await flapBundleBuyFirstMultiWallet({
|
|
112
|
+
chain,
|
|
113
|
+
buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
|
|
114
|
+
sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
|
|
115
|
+
tokenAddress,
|
|
116
|
+
buyerFunds,
|
|
117
|
+
config,
|
|
118
|
+
quoteToken,
|
|
119
|
+
quoteTokenDecimals
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// ✅ 单钱包模式(向后兼容)
|
|
123
|
+
if (!buyerPrivateKey || !sellerPrivateKey) {
|
|
124
|
+
throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
|
|
125
|
+
}
|
|
107
126
|
validateSwapCounts(buyCount, sellCount);
|
|
108
127
|
// ✅ 计算利润比例:每笔万分之3
|
|
109
128
|
const totalTxCount = buyCount + sellCount;
|
|
@@ -585,3 +604,201 @@ function createChainContext(chain, rpcUrl) {
|
|
|
585
604
|
}
|
|
586
605
|
return { chainId, nativeToken, portalAddress, provider };
|
|
587
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* ✅ Flap 多钱包捆绑换手
|
|
609
|
+
* - 多个买方钱包执行买入(每个钱包1笔)
|
|
610
|
+
* - 多个卖方钱包执行卖出(每个钱包1笔)
|
|
611
|
+
* - 买入总价值 = 卖出总价值
|
|
612
|
+
*/
|
|
613
|
+
async function flapBundleBuyFirstMultiWallet(params) {
|
|
614
|
+
const { chain, buyerPrivateKeys, sellerPrivateKeys, tokenAddress, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
|
|
615
|
+
const buyCount = buyerPrivateKeys.length;
|
|
616
|
+
const sellCount = sellerPrivateKeys.length;
|
|
617
|
+
if (buyCount === 0)
|
|
618
|
+
throw new Error('买方钱包数量不能为0');
|
|
619
|
+
if (sellCount === 0)
|
|
620
|
+
throw new Error('卖方钱包数量不能为0');
|
|
621
|
+
// 验证总交易数不超过限制
|
|
622
|
+
validateSwapCounts(buyCount, sellCount);
|
|
623
|
+
// ✅ 计算利润比例:每笔万分之6
|
|
624
|
+
const totalTxCount = buyCount + sellCount;
|
|
625
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
626
|
+
const chainContext = createChainContext(chain, config.rpcUrl);
|
|
627
|
+
const nonceManager = new NonceManager(chainContext.provider);
|
|
628
|
+
// 创建所有钱包实例
|
|
629
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
630
|
+
const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
631
|
+
// 使用第一个卖方作为主卖方(支付贿赂和利润)
|
|
632
|
+
const mainSeller = sellers[0];
|
|
633
|
+
// ✅ 判断是否使用原生代币
|
|
634
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
635
|
+
const inputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
|
|
636
|
+
const outputToken = inputToken;
|
|
637
|
+
// ✅ 计算总交易金额
|
|
638
|
+
if (!buyerFunds) {
|
|
639
|
+
throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
|
|
640
|
+
}
|
|
641
|
+
const totalFundsWei = useNativeToken
|
|
642
|
+
? ethers.parseEther(String(buyerFunds))
|
|
643
|
+
: ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
|
|
644
|
+
if (totalFundsWei <= 0n) {
|
|
645
|
+
throw new Error('交易金额必须大于0');
|
|
646
|
+
}
|
|
647
|
+
// ✅ 获取报价:买入能获得多少代币
|
|
648
|
+
const quoteResult = await quoteBuyerOutput({
|
|
649
|
+
portalAddress: chainContext.portalAddress,
|
|
650
|
+
tokenAddress,
|
|
651
|
+
buyerFundsWei: totalFundsWei,
|
|
652
|
+
provider: chainContext.provider,
|
|
653
|
+
skipQuoteOnError: config.skipQuoteOnError,
|
|
654
|
+
inputToken
|
|
655
|
+
});
|
|
656
|
+
// ✅ 将总金额平均分配给买方
|
|
657
|
+
const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
|
|
658
|
+
// ✅ 将代币平均分配给卖方
|
|
659
|
+
const sellAmountsWei = splitAmount(quoteResult.sellAmountWei, sellCount);
|
|
660
|
+
const finalGasLimit = getGasLimit(config);
|
|
661
|
+
const gasPrice = await getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config));
|
|
662
|
+
const txType = getTxType(config);
|
|
663
|
+
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
664
|
+
// ✅ 估算利润
|
|
665
|
+
const portal = new Contract(chainContext.portalAddress, PORTAL_ABI, chainContext.provider);
|
|
666
|
+
const estimatedSellFunds = await estimateSellFunds(portal, tokenAddress, quoteResult.sellAmountWei, outputToken);
|
|
667
|
+
const profitBase = estimatedSellFunds > 0n ? estimatedSellFunds : totalFundsWei;
|
|
668
|
+
const tokenProfitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
|
|
669
|
+
// ERC20 购买:获取代币利润等值的原生代币报价
|
|
670
|
+
let nativeProfitAmount = tokenProfitAmount;
|
|
671
|
+
if (!useNativeToken && tokenProfitAmount > 0n) {
|
|
672
|
+
nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, inputToken, tokenProfitAmount, chainContext.chainId);
|
|
673
|
+
}
|
|
674
|
+
// ✅ 获取贿赂金额
|
|
675
|
+
const bribeAmount = getBribeAmount(config);
|
|
676
|
+
const needBribeTx = bribeAmount > 0n;
|
|
677
|
+
// ✅ 获取所有钱包的 nonces
|
|
678
|
+
const noncesMap = new Map();
|
|
679
|
+
await Promise.all([...sellers, ...buyers].map(async (wallet) => {
|
|
680
|
+
const addr = wallet.address.toLowerCase();
|
|
681
|
+
if (!noncesMap.has(addr)) {
|
|
682
|
+
const nonce = await nonceManager.getNextNonce(wallet);
|
|
683
|
+
noncesMap.set(addr, nonce);
|
|
684
|
+
}
|
|
685
|
+
}));
|
|
686
|
+
// ✅ 构建交易列表
|
|
687
|
+
const allTransactions = [];
|
|
688
|
+
// 1. 贿赂交易(由主卖方支付)
|
|
689
|
+
if (needBribeTx) {
|
|
690
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
691
|
+
const bribeNonce = noncesMap.get(mainSellerAddr);
|
|
692
|
+
noncesMap.set(mainSellerAddr, bribeNonce + 1);
|
|
693
|
+
const bribeTx = await mainSeller.signTransaction({
|
|
694
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
695
|
+
value: bribeAmount,
|
|
696
|
+
nonce: bribeNonce,
|
|
697
|
+
gasPrice,
|
|
698
|
+
gasLimit: 21000n,
|
|
699
|
+
chainId: chainContext.chainId,
|
|
700
|
+
type: txType
|
|
701
|
+
});
|
|
702
|
+
allTransactions.push(bribeTx);
|
|
703
|
+
}
|
|
704
|
+
// 2. 构建所有买入交易
|
|
705
|
+
const buyTxPromises = buyers.map(async (buyer, i) => {
|
|
706
|
+
const buyAmount = buyAmountsWei[i];
|
|
707
|
+
const buyerAddr = buyer.address.toLowerCase();
|
|
708
|
+
const nonce = noncesMap.get(buyerAddr);
|
|
709
|
+
noncesMap.set(buyerAddr, nonce + 1);
|
|
710
|
+
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
711
|
+
const unsigned = await portalBuyer.swapExactInput.populateTransaction({
|
|
712
|
+
inputToken,
|
|
713
|
+
outputToken: tokenAddress,
|
|
714
|
+
inputAmount: buyAmount,
|
|
715
|
+
minOutputAmount: 0n,
|
|
716
|
+
permitData: '0x'
|
|
717
|
+
}, useNativeToken ? { value: buyAmount } : {});
|
|
718
|
+
return buyer.signTransaction(buildTransactionRequest(unsigned, {
|
|
719
|
+
from: buyer.address,
|
|
720
|
+
nonce,
|
|
721
|
+
gasLimit: finalGasLimit,
|
|
722
|
+
gasPrice,
|
|
723
|
+
priorityFee,
|
|
724
|
+
chainId: chainContext.chainId,
|
|
725
|
+
txType,
|
|
726
|
+
value: useNativeToken ? buyAmount : 0n
|
|
727
|
+
}));
|
|
728
|
+
});
|
|
729
|
+
// 3. 构建所有卖出交易
|
|
730
|
+
const sellTxPromises = sellers.map(async (seller, i) => {
|
|
731
|
+
const sellAmount = sellAmountsWei[i];
|
|
732
|
+
const sellerAddr = seller.address.toLowerCase();
|
|
733
|
+
const nonce = noncesMap.get(sellerAddr);
|
|
734
|
+
noncesMap.set(sellerAddr, nonce + 1);
|
|
735
|
+
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
736
|
+
const unsigned = await portalSeller.swapExactInput.populateTransaction({
|
|
737
|
+
inputToken: tokenAddress,
|
|
738
|
+
outputToken,
|
|
739
|
+
inputAmount: sellAmount,
|
|
740
|
+
minOutputAmount: 0n,
|
|
741
|
+
permitData: '0x'
|
|
742
|
+
});
|
|
743
|
+
return seller.signTransaction(buildTransactionRequest(unsigned, {
|
|
744
|
+
from: seller.address,
|
|
745
|
+
nonce,
|
|
746
|
+
gasLimit: finalGasLimit,
|
|
747
|
+
gasPrice,
|
|
748
|
+
priorityFee,
|
|
749
|
+
chainId: chainContext.chainId,
|
|
750
|
+
txType,
|
|
751
|
+
value: 0n
|
|
752
|
+
}));
|
|
753
|
+
});
|
|
754
|
+
// ✅ 并行签名所有买卖交易
|
|
755
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
756
|
+
Promise.all(buyTxPromises),
|
|
757
|
+
Promise.all(sellTxPromises)
|
|
758
|
+
]);
|
|
759
|
+
// 先买后卖:买入交易在前
|
|
760
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
761
|
+
// 4. 利润多跳转账(由主卖方支付)
|
|
762
|
+
let profitHopWallets;
|
|
763
|
+
if (nativeProfitAmount > 0n) {
|
|
764
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
765
|
+
const profitNonce = noncesMap.get(mainSellerAddr);
|
|
766
|
+
const profitResult = await buildProfitTransaction({
|
|
767
|
+
provider: chainContext.provider,
|
|
768
|
+
seller: mainSeller,
|
|
769
|
+
profitAmount: nativeProfitAmount,
|
|
770
|
+
profitNonce,
|
|
771
|
+
gasPrice,
|
|
772
|
+
chainId: chainContext.chainId,
|
|
773
|
+
txType
|
|
774
|
+
});
|
|
775
|
+
if (profitResult) {
|
|
776
|
+
allTransactions.push(...profitResult.signedTransactions);
|
|
777
|
+
profitHopWallets = profitResult.hopWallets;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
nonceManager.clearTemp();
|
|
781
|
+
// 获取代币精度
|
|
782
|
+
const sellerInfo = await ensureSellerBalance({
|
|
783
|
+
tokenAddress,
|
|
784
|
+
provider: chainContext.provider,
|
|
785
|
+
seller: mainSeller,
|
|
786
|
+
sellAmountWei: 0n,
|
|
787
|
+
skipBalanceCheck: true
|
|
788
|
+
});
|
|
789
|
+
return {
|
|
790
|
+
signedTransactions: allTransactions,
|
|
791
|
+
profitHopWallets,
|
|
792
|
+
metadata: {
|
|
793
|
+
buyerAddress: buyers.map(b => b.address).join(','),
|
|
794
|
+
sellerAddress: sellers.map(s => s.address).join(','),
|
|
795
|
+
buyAmount: ethers.formatEther(totalFundsWei),
|
|
796
|
+
sellAmount: ethers.formatUnits(quoteResult.sellAmountWei, sellerInfo.decimals),
|
|
797
|
+
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
|
|
798
|
+
buyCount,
|
|
799
|
+
sellCount,
|
|
800
|
+
buyAmounts: buyAmountsWei.map(amt => ethers.formatEther(amt)),
|
|
801
|
+
sellAmounts: sellAmountsWei.map(amt => ethers.formatUnits(amt, sellerInfo.decimals))
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
}
|
|
@@ -45,8 +45,10 @@ export interface PancakeBuyFirstConfig extends CommonBundleConfig {
|
|
|
45
45
|
waitTimeoutMs?: number;
|
|
46
46
|
}
|
|
47
47
|
export interface PancakeBundleBuyFirstSignParams {
|
|
48
|
-
buyerPrivateKey
|
|
49
|
-
|
|
48
|
+
buyerPrivateKey?: string;
|
|
49
|
+
buyerPrivateKeys?: string[];
|
|
50
|
+
sellerPrivateKey?: string;
|
|
51
|
+
sellerPrivateKeys?: string[];
|
|
50
52
|
tokenAddress: string;
|
|
51
53
|
routeParams: RouteParams;
|
|
52
54
|
buyerFunds?: string;
|
|
@@ -109,11 +109,32 @@ function splitAmount(totalAmount, count) {
|
|
|
109
109
|
return amounts;
|
|
110
110
|
}
|
|
111
111
|
export async function pancakeBundleBuyFirstMerkle(params) {
|
|
112
|
-
const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
|
|
112
|
+
const { buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
|
|
113
113
|
buyCount: _buyCount, sellCount: _sellCount } = params;
|
|
114
|
-
// ✅
|
|
114
|
+
// ✅ 判断是否为多钱包模式
|
|
115
|
+
const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
|
|
116
|
+
!!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
|
|
117
|
+
// ✅ 多钱包模式:buyCount/sellCount 代表钱包数量
|
|
118
|
+
// 单钱包模式(向后兼容):buyCount/sellCount 代表同一钱包执行的交易笔数
|
|
115
119
|
const buyCount = _buyCount ?? 1;
|
|
116
120
|
const sellCount = _sellCount ?? 1;
|
|
121
|
+
// ✅ 多钱包模式:使用单独的处理逻辑
|
|
122
|
+
if (isMultiWalletMode) {
|
|
123
|
+
return await pancakeBundleBuyFirstMultiWallet({
|
|
124
|
+
buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
|
|
125
|
+
sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
|
|
126
|
+
tokenAddress,
|
|
127
|
+
routeParams,
|
|
128
|
+
buyerFunds,
|
|
129
|
+
config,
|
|
130
|
+
quoteToken,
|
|
131
|
+
quoteTokenDecimals
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// ✅ 单钱包模式(向后兼容):验证买卖笔数
|
|
135
|
+
if (!buyerPrivateKey || !sellerPrivateKey) {
|
|
136
|
+
throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
|
|
137
|
+
}
|
|
117
138
|
validateSwapCounts(buyCount, sellCount);
|
|
118
139
|
// ✅ 计算利润比例:每笔万分之3
|
|
119
140
|
const totalTxCount = buyCount + sellCount;
|
|
@@ -728,3 +749,270 @@ function buildNoncePlanFromStartNonces(startNonces, sameAddress, profitNeeded, n
|
|
|
728
749
|
const buyerNonce = startNonces[1];
|
|
729
750
|
return { buyerNonce, sellerNonce, bribeNonce, profitNonce };
|
|
730
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* ✅ 多钱包捆绑换手
|
|
754
|
+
* - 多个买方钱包执行买入(每个钱包1笔)
|
|
755
|
+
* - 多个卖方钱包执行卖出(每个钱包1笔)
|
|
756
|
+
* - 买入总价值 = 卖出总价值
|
|
757
|
+
*/
|
|
758
|
+
async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
759
|
+
const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, config, quoteToken, quoteTokenDecimals = 18 } = params;
|
|
760
|
+
const buyCount = buyerPrivateKeys.length;
|
|
761
|
+
const sellCount = sellerPrivateKeys.length;
|
|
762
|
+
if (buyCount === 0)
|
|
763
|
+
throw new Error('买方钱包数量不能为0');
|
|
764
|
+
if (sellCount === 0)
|
|
765
|
+
throw new Error('卖方钱包数量不能为0');
|
|
766
|
+
// 验证总交易数不超过限制
|
|
767
|
+
validateSwapCounts(buyCount, sellCount);
|
|
768
|
+
// ✅ 计算利润比例:每笔万分之6
|
|
769
|
+
const totalTxCount = buyCount + sellCount;
|
|
770
|
+
const profitRateBps = PROFIT_RATE_PER_TX_BPS * totalTxCount;
|
|
771
|
+
// ✅ 判断是否使用原生代币
|
|
772
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
773
|
+
const context = createPancakeContext(config);
|
|
774
|
+
const nonceManager = new NonceManager(context.provider);
|
|
775
|
+
// 创建所有钱包实例
|
|
776
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
777
|
+
const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
778
|
+
// 使用第一个卖方作为主卖方(支付贿赂和利润)
|
|
779
|
+
const mainSeller = sellers[0];
|
|
780
|
+
// ✅ 计算总交易金额
|
|
781
|
+
let totalFundsWei;
|
|
782
|
+
if (buyerFunds) {
|
|
783
|
+
totalFundsWei = useNativeToken
|
|
784
|
+
? ethers.parseEther(String(buyerFunds))
|
|
785
|
+
: ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
|
|
789
|
+
}
|
|
790
|
+
if (totalFundsWei <= 0n) {
|
|
791
|
+
throw new Error('交易金额必须大于0');
|
|
792
|
+
}
|
|
793
|
+
// ✅ 获取报价:买入能获得多少代币
|
|
794
|
+
const quoteResult = await quoteTokenOutput({
|
|
795
|
+
routeParams,
|
|
796
|
+
buyerFundsWei: totalFundsWei,
|
|
797
|
+
provider: context.provider
|
|
798
|
+
});
|
|
799
|
+
// ✅ 将总金额平均分配给买方
|
|
800
|
+
const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
|
|
801
|
+
// ✅ 将代币平均分配给卖方
|
|
802
|
+
const sellAmountsWei = splitAmount(quoteResult.quotedTokenOut, sellCount);
|
|
803
|
+
const finalGasLimit = getGasLimit(config);
|
|
804
|
+
const gasPrice = await getGasPrice(context.provider, config);
|
|
805
|
+
const txType = config.txType ?? 0;
|
|
806
|
+
const deadline = BigInt(getDeadline());
|
|
807
|
+
// ✅ 估算利润
|
|
808
|
+
const estimatedProfitFromSell = await estimateProfitAmount({
|
|
809
|
+
provider: context.provider,
|
|
810
|
+
tokenAddress,
|
|
811
|
+
sellAmountToken: quoteResult.quotedTokenOut,
|
|
812
|
+
routeParams
|
|
813
|
+
});
|
|
814
|
+
const profitBase = estimatedProfitFromSell > 0n ? estimatedProfitFromSell : totalFundsWei;
|
|
815
|
+
const profitAmount = (profitBase * BigInt(profitRateBps)) / 10000n;
|
|
816
|
+
// ✅ 获取贿赂金额
|
|
817
|
+
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
818
|
+
? ethers.parseEther(String(config.bribeAmount))
|
|
819
|
+
: 0n;
|
|
820
|
+
const needBribeTx = bribeAmount > 0n;
|
|
821
|
+
// ✅ 获取所有钱包的 nonces
|
|
822
|
+
const allWallets = [...sellers, ...buyers];
|
|
823
|
+
const noncesMap = new Map();
|
|
824
|
+
await Promise.all(allWallets.map(async (wallet) => {
|
|
825
|
+
const addr = wallet.address.toLowerCase();
|
|
826
|
+
if (!noncesMap.has(addr)) {
|
|
827
|
+
const nonce = await nonceManager.getNextNonce(wallet);
|
|
828
|
+
noncesMap.set(addr, nonce);
|
|
829
|
+
}
|
|
830
|
+
}));
|
|
831
|
+
// ✅ 构建交易列表
|
|
832
|
+
const allTransactions = [];
|
|
833
|
+
// 1. 贿赂交易(由主卖方支付)
|
|
834
|
+
if (needBribeTx) {
|
|
835
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
836
|
+
const bribeNonce = noncesMap.get(mainSellerAddr);
|
|
837
|
+
noncesMap.set(mainSellerAddr, bribeNonce + 1);
|
|
838
|
+
const bribeTx = await mainSeller.signTransaction({
|
|
839
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
840
|
+
value: bribeAmount,
|
|
841
|
+
nonce: bribeNonce,
|
|
842
|
+
gasPrice,
|
|
843
|
+
gasLimit: 21000n,
|
|
844
|
+
chainId: context.chainId,
|
|
845
|
+
type: txType
|
|
846
|
+
});
|
|
847
|
+
allTransactions.push(bribeTx);
|
|
848
|
+
}
|
|
849
|
+
// 2. 构建所有买入交易
|
|
850
|
+
const buyTxPromises = buyers.map(async (buyer, i) => {
|
|
851
|
+
const buyAmount = buyAmountsWei[i];
|
|
852
|
+
const buyerAddr = buyer.address.toLowerCase();
|
|
853
|
+
const nonce = noncesMap.get(buyerAddr);
|
|
854
|
+
noncesMap.set(buyerAddr, nonce + 1);
|
|
855
|
+
const unsigned = await buildSingleBuyTx({
|
|
856
|
+
routeParams,
|
|
857
|
+
buyAmount,
|
|
858
|
+
buyer,
|
|
859
|
+
tokenAddress,
|
|
860
|
+
useNativeToken,
|
|
861
|
+
deadline
|
|
862
|
+
});
|
|
863
|
+
return buyer.signTransaction({
|
|
864
|
+
...unsigned,
|
|
865
|
+
from: buyer.address,
|
|
866
|
+
nonce,
|
|
867
|
+
gasLimit: finalGasLimit,
|
|
868
|
+
gasPrice,
|
|
869
|
+
chainId: context.chainId,
|
|
870
|
+
type: txType
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
// 3. 构建所有卖出交易
|
|
874
|
+
const sellTxPromises = sellers.map(async (seller, i) => {
|
|
875
|
+
const sellAmount = sellAmountsWei[i];
|
|
876
|
+
const sellerAddr = seller.address.toLowerCase();
|
|
877
|
+
const nonce = noncesMap.get(sellerAddr);
|
|
878
|
+
noncesMap.set(sellerAddr, nonce + 1);
|
|
879
|
+
const unsigned = await buildSingleSellTx({
|
|
880
|
+
routeParams,
|
|
881
|
+
sellAmount,
|
|
882
|
+
seller,
|
|
883
|
+
tokenAddress,
|
|
884
|
+
useNativeToken,
|
|
885
|
+
deadline
|
|
886
|
+
});
|
|
887
|
+
return seller.signTransaction({
|
|
888
|
+
...unsigned,
|
|
889
|
+
from: seller.address,
|
|
890
|
+
nonce,
|
|
891
|
+
gasLimit: finalGasLimit,
|
|
892
|
+
gasPrice,
|
|
893
|
+
chainId: context.chainId,
|
|
894
|
+
type: txType
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
// ✅ 并行签名所有买卖交易
|
|
898
|
+
const [signedBuys, signedSells] = await Promise.all([
|
|
899
|
+
Promise.all(buyTxPromises),
|
|
900
|
+
Promise.all(sellTxPromises)
|
|
901
|
+
]);
|
|
902
|
+
// 先买后卖:买入交易在前
|
|
903
|
+
allTransactions.push(...signedBuys, ...signedSells);
|
|
904
|
+
// 4. 利润多跳转账(由主卖方支付)
|
|
905
|
+
let profitHopWallets;
|
|
906
|
+
if (profitAmount > 0n) {
|
|
907
|
+
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
908
|
+
const profitNonce = noncesMap.get(mainSellerAddr);
|
|
909
|
+
const profitResult = await buildProfitTransaction({
|
|
910
|
+
provider: context.provider,
|
|
911
|
+
seller: mainSeller,
|
|
912
|
+
profitAmount,
|
|
913
|
+
profitNonce,
|
|
914
|
+
gasPrice,
|
|
915
|
+
chainId: context.chainId,
|
|
916
|
+
txType
|
|
917
|
+
});
|
|
918
|
+
if (profitResult) {
|
|
919
|
+
allTransactions.push(...profitResult.signedTransactions);
|
|
920
|
+
profitHopWallets = profitResult.hopWallets;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
nonceManager.clearTemp();
|
|
924
|
+
return {
|
|
925
|
+
signedTransactions: allTransactions,
|
|
926
|
+
profitHopWallets,
|
|
927
|
+
metadata: {
|
|
928
|
+
buyerAddress: buyers.map(b => b.address).join(','),
|
|
929
|
+
sellerAddress: sellers.map(s => s.address).join(','),
|
|
930
|
+
buyAmount: useNativeToken
|
|
931
|
+
? ethers.formatEther(totalFundsWei)
|
|
932
|
+
: ethers.formatUnits(totalFundsWei, quoteTokenDecimals),
|
|
933
|
+
sellAmount: quoteResult.quotedTokenOut.toString(),
|
|
934
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
935
|
+
buyCount,
|
|
936
|
+
sellCount,
|
|
937
|
+
buyAmounts: buyAmountsWei.map(amt => useNativeToken
|
|
938
|
+
? ethers.formatEther(amt)
|
|
939
|
+
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
940
|
+
sellAmounts: sellAmountsWei.map(amt => amt.toString())
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
// ✅ 构建单笔买入交易
|
|
945
|
+
async function buildSingleBuyTx({ routeParams, buyAmount, buyer, tokenAddress, useNativeToken, deadline }) {
|
|
946
|
+
if (routeParams.routeType === 'v2') {
|
|
947
|
+
const { v2Path } = routeParams;
|
|
948
|
+
const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
|
|
949
|
+
if (useNativeToken) {
|
|
950
|
+
return await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyAmount });
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, v2Path, buyer.address, deadline);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (routeParams.routeType === 'v3-single') {
|
|
957
|
+
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
958
|
+
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
959
|
+
const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
|
|
960
|
+
const buyValue = useNativeToken ? buyAmount : 0n;
|
|
961
|
+
const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
962
|
+
tokenIn: v3TokenIn,
|
|
963
|
+
tokenOut: v3TokenOut,
|
|
964
|
+
fee: v3Fee,
|
|
965
|
+
recipient: buyer.address,
|
|
966
|
+
amountIn: buyAmount,
|
|
967
|
+
amountOutMinimum: 0n,
|
|
968
|
+
sqrtPriceLimitX96: 0n
|
|
969
|
+
}]);
|
|
970
|
+
return await v3Router.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
|
|
971
|
+
}
|
|
972
|
+
throw new Error('V3 多跳路由暂不支持');
|
|
973
|
+
}
|
|
974
|
+
// ✅ 构建单笔卖出交易
|
|
975
|
+
async function buildSingleSellTx({ routeParams, sellAmount, seller, tokenAddress, useNativeToken, deadline }) {
|
|
976
|
+
if (routeParams.routeType === 'v2') {
|
|
977
|
+
const { v2Path } = routeParams;
|
|
978
|
+
const reversePath = [...v2Path].reverse();
|
|
979
|
+
const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
|
|
980
|
+
if (useNativeToken) {
|
|
981
|
+
return await v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (routeParams.routeType === 'v3-single') {
|
|
988
|
+
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
989
|
+
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
990
|
+
const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
|
|
991
|
+
if (useNativeToken) {
|
|
992
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
993
|
+
tokenIn: v3TokenOut,
|
|
994
|
+
tokenOut: v3TokenIn,
|
|
995
|
+
fee: v3Fee,
|
|
996
|
+
recipient: PANCAKE_V3_ROUTER_ADDRESS,
|
|
997
|
+
amountIn: sellAmount,
|
|
998
|
+
amountOutMinimum: 0n,
|
|
999
|
+
sqrtPriceLimitX96: 0n
|
|
1000
|
+
}]);
|
|
1001
|
+
const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
|
|
1002
|
+
return await v3Router.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
1006
|
+
tokenIn: v3TokenOut,
|
|
1007
|
+
tokenOut: v3TokenIn,
|
|
1008
|
+
fee: v3Fee,
|
|
1009
|
+
recipient: seller.address,
|
|
1010
|
+
amountIn: sellAmount,
|
|
1011
|
+
amountOutMinimum: 0n,
|
|
1012
|
+
sqrtPriceLimitX96: 0n
|
|
1013
|
+
}]);
|
|
1014
|
+
return await v3Router.multicall.populateTransaction(deadline, [sellSwapData]);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
throw new Error('V3 多跳路由暂不支持');
|
|
1018
|
+
}
|