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