four-flap-meme-sdk 1.4.72 → 1.4.73
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.
|
@@ -139,13 +139,16 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
139
139
|
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
140
140
|
throw new Error('报价失败:无法估算可买入的代币数量');
|
|
141
141
|
}
|
|
142
|
-
// ✅
|
|
143
|
-
//
|
|
144
|
-
//
|
|
142
|
+
// ✅ 多笔交易时添加安全系数:减少卖出数量以应对报价误差/税/滑点
|
|
143
|
+
// 原因:多笔买入会产生滑点累积(每笔买入后 AMM 价格上涨),实际获得的代币少于报价
|
|
144
|
+
// 单笔买入+单笔卖出时不需要安全系数(与普通捆绑换手一致)
|
|
145
145
|
let sellAmountWei = estimatedTokenAmount;
|
|
146
|
-
if (buyCount > 1) {
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
if (buyCount > 1 || sellCount > 1) {
|
|
147
|
+
// 根据买入笔数动态调整安全系数:2% + 每多一笔额外 0.5%
|
|
148
|
+
const baseSafetyBps = 200n; // 2% 基础安全系数
|
|
149
|
+
const extraSafetyBps = BigInt((buyCount - 1) * 50); // 每多一笔买入额外 0.5%
|
|
150
|
+
const totalSafetyBps = baseSafetyBps + extraSafetyBps;
|
|
151
|
+
sellAmountWei = estimatedTokenAmount * (10000n - totalSafetyBps) / 10000n;
|
|
149
152
|
}
|
|
150
153
|
// 卖方余额检查
|
|
151
154
|
if (!sameAddress && sellerTokenBal < sellAmountWei) {
|
|
@@ -140,13 +140,16 @@ export async function pancakeBundleBuyFirstMerkle(params) {
|
|
|
140
140
|
buyerFundsWei: buyerFundsInfo.buyerFundsWei,
|
|
141
141
|
provider: context.provider
|
|
142
142
|
});
|
|
143
|
-
// ✅
|
|
144
|
-
//
|
|
145
|
-
//
|
|
143
|
+
// ✅ 多笔交易时添加安全系数:减少卖出数量以应对报价误差/税/滑点
|
|
144
|
+
// 原因:多笔买入会产生滑点累积(每笔买入后 AMM 价格上涨),实际获得的代币少于报价
|
|
145
|
+
// 单笔买入+单笔卖出时不需要安全系数(与普通捆绑换手一致)
|
|
146
146
|
let adjustedSellAmount = quoteResult.quotedTokenOut;
|
|
147
|
-
if (buyCount > 1) {
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
if (buyCount > 1 || sellCount > 1) {
|
|
148
|
+
// 根据买入笔数动态调整安全系数:2% + 每多一笔额外 0.5%
|
|
149
|
+
const baseSafetyBps = 200n; // 2% 基础安全系数
|
|
150
|
+
const extraSafetyBps = BigInt((buyCount - 1) * 50); // 每多一笔买入额外 0.5%
|
|
151
|
+
const totalSafetyBps = baseSafetyBps + extraSafetyBps;
|
|
152
|
+
adjustedSellAmount = quoteResult.quotedTokenOut * (10000n - totalSafetyBps) / 10000n;
|
|
150
153
|
}
|
|
151
154
|
// ✅ 拆分买入和卖出金额
|
|
152
155
|
const buyAmountsWei = splitAmount(buyerFundsInfo.buyerFundsWei, buyCount);
|
|
@@ -409,26 +412,34 @@ function calculateBuyerNeed({ quotedNative, buyerBalance, reserveGas }) {
|
|
|
409
412
|
}
|
|
410
413
|
async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountToken, buyer, seller, tokenAddress, useNativeToken = true }) {
|
|
411
414
|
const deadline = getDeadline();
|
|
412
|
-
// ✅ ERC20 购买时,value 只需要 FLAT_FEE
|
|
413
|
-
const buyValue = useNativeToken ? buyerFundsWei + FLAT_FEE : FLAT_FEE;
|
|
414
415
|
if (routeParams.routeType === 'v2') {
|
|
415
416
|
const { v2Path } = routeParams;
|
|
416
417
|
const reversePath = [...v2Path].reverse();
|
|
417
418
|
// ✅ 使用官方 V2 Router
|
|
418
419
|
const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
|
|
419
420
|
const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
421
|
+
if (useNativeToken) {
|
|
422
|
+
// ✅ BNB 池子:使用 swapExactETHForTokens / swapExactTokensForETH
|
|
423
|
+
const buyValue = buyerFundsWei + FLAT_FEE;
|
|
424
|
+
const buyUnsigned = await v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyValue });
|
|
425
|
+
const sellUnsigned = await v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmountToken, 0n, reversePath, seller.address, deadline);
|
|
426
|
+
return { buyUnsigned, sellUnsigned };
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
// ✅ ERC20 池子(如 USDT):使用 swapExactTokensForTokens
|
|
430
|
+
const buyUnsigned = await v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyerFundsWei, 0n, v2Path, buyer.address, deadline);
|
|
431
|
+
const sellUnsigned = await v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmountToken, 0n, reversePath, seller.address, deadline);
|
|
432
|
+
return { buyUnsigned, sellUnsigned };
|
|
433
|
+
}
|
|
425
434
|
}
|
|
426
435
|
if (routeParams.routeType === 'v3-single') {
|
|
427
436
|
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
428
437
|
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
429
438
|
const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
|
|
430
439
|
const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
|
|
431
|
-
//
|
|
440
|
+
// ✅ V3 池子的处理
|
|
441
|
+
const buyValue = useNativeToken ? buyerFundsWei + FLAT_FEE : FLAT_FEE;
|
|
442
|
+
// 买入交易
|
|
432
443
|
const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
433
444
|
tokenIn: v3TokenIn,
|
|
434
445
|
tokenOut: v3TokenOut,
|
|
@@ -439,19 +450,36 @@ async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountTo
|
|
|
439
450
|
sqrtPriceLimitX96: 0n
|
|
440
451
|
}]);
|
|
441
452
|
const buyUnsigned = await v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
453
|
+
// 卖出交易
|
|
454
|
+
if (useNativeToken) {
|
|
455
|
+
// ✅ BNB 池子:卖出后 unwrap WBNB 为 BNB
|
|
456
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
457
|
+
tokenIn: v3TokenOut,
|
|
458
|
+
tokenOut: v3TokenIn,
|
|
459
|
+
fee: v3Fee,
|
|
460
|
+
recipient: PANCAKE_V3_ROUTER_ADDRESS,
|
|
461
|
+
amountIn: sellAmountToken,
|
|
462
|
+
amountOutMinimum: 0n,
|
|
463
|
+
sqrtPriceLimitX96: 0n
|
|
464
|
+
}]);
|
|
465
|
+
const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
|
|
466
|
+
const sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
|
|
467
|
+
return { buyUnsigned, sellUnsigned };
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
// ✅ ERC20 池子:卖出后直接获得 ERC20
|
|
471
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
472
|
+
tokenIn: v3TokenOut,
|
|
473
|
+
tokenOut: v3TokenIn,
|
|
474
|
+
fee: v3Fee,
|
|
475
|
+
recipient: seller.address, // 直接发送到卖方地址
|
|
476
|
+
amountIn: sellAmountToken,
|
|
477
|
+
amountOutMinimum: 0n,
|
|
478
|
+
sqrtPriceLimitX96: 0n
|
|
479
|
+
}]);
|
|
480
|
+
const sellUnsigned = await v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData]);
|
|
481
|
+
return { buyUnsigned, sellUnsigned };
|
|
482
|
+
}
|
|
455
483
|
}
|
|
456
484
|
// V3 多跳暂不支持官方合约
|
|
457
485
|
throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
|
|
@@ -466,14 +494,22 @@ async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmo
|
|
|
466
494
|
const reversePath = [...v2Path].reverse();
|
|
467
495
|
const v2RouterBuyer = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
|
|
468
496
|
const v2RouterSeller = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
497
|
+
// ✅ 根据是否使用原生代币选择不同的函数
|
|
498
|
+
if (useNativeToken) {
|
|
499
|
+
// BNB 池子:使用 swapExactETHForTokens / swapExactTokensForETH
|
|
500
|
+
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
|
|
501
|
+
const buyValue = amount + FLAT_FEE;
|
|
502
|
+
return v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyValue });
|
|
503
|
+
}));
|
|
504
|
+
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
|
|
505
|
+
return { buyUnsignedArray, sellUnsignedArray };
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
// ✅ ERC20 池子(如 USDT):使用 swapExactTokensForTokens
|
|
509
|
+
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, v2Path, buyer.address, deadline)));
|
|
510
|
+
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
|
|
511
|
+
return { buyUnsignedArray, sellUnsignedArray };
|
|
512
|
+
}
|
|
477
513
|
}
|
|
478
514
|
if (routeParams.routeType === 'v3-single') {
|
|
479
515
|
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
@@ -494,21 +530,40 @@ async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmo
|
|
|
494
530
|
}]);
|
|
495
531
|
return v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
|
|
496
532
|
}));
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
533
|
+
// ✅ 根据是否使用原生代币构建不同的卖出交易
|
|
534
|
+
if (useNativeToken) {
|
|
535
|
+
// BNB 池子:卖出后 unwrap WBNB 为 BNB
|
|
536
|
+
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => {
|
|
537
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
538
|
+
tokenIn: v3TokenOut,
|
|
539
|
+
tokenOut: v3TokenIn,
|
|
540
|
+
fee: v3Fee,
|
|
541
|
+
recipient: PANCAKE_V3_ROUTER_ADDRESS,
|
|
542
|
+
amountIn: amount,
|
|
543
|
+
amountOutMinimum: 0n,
|
|
544
|
+
sqrtPriceLimitX96: 0n
|
|
545
|
+
}]);
|
|
546
|
+
const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
|
|
547
|
+
return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
|
|
548
|
+
}));
|
|
549
|
+
return { buyUnsignedArray, sellUnsignedArray };
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// ✅ ERC20 池子:卖出后直接获得 ERC20
|
|
553
|
+
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => {
|
|
554
|
+
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
555
|
+
tokenIn: v3TokenOut,
|
|
556
|
+
tokenOut: v3TokenIn,
|
|
557
|
+
fee: v3Fee,
|
|
558
|
+
recipient: seller.address, // 直接发送到卖方地址
|
|
559
|
+
amountIn: amount,
|
|
560
|
+
amountOutMinimum: 0n,
|
|
561
|
+
sqrtPriceLimitX96: 0n
|
|
562
|
+
}]);
|
|
563
|
+
return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData]);
|
|
564
|
+
}));
|
|
565
|
+
return { buyUnsignedArray, sellUnsignedArray };
|
|
566
|
+
}
|
|
512
567
|
}
|
|
513
568
|
throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
|
|
514
569
|
}
|