four-flap-meme-sdk 1.4.6 → 1.4.8
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/dex/direct-router.js +154 -26
- package/package.json +1 -1
|
@@ -44,25 +44,34 @@ const DEFAULT_GAS_LIMIT = 300000;
|
|
|
44
44
|
const DEADLINE_MINUTES = 20;
|
|
45
45
|
// ✅ BlockRazor Builder EOA 地址(用于贿赂,仅 BSC 链)
|
|
46
46
|
const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
|
|
47
|
-
// ✅ Router ABI(用于 ERC20 → 原生代币报价)
|
|
47
|
+
// ✅ V2 Router ABI(用于 ERC20 → 原生代币报价)
|
|
48
48
|
const QUOTE_ROUTER_ABI = [
|
|
49
49
|
'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
|
|
50
50
|
];
|
|
51
|
-
// ✅
|
|
51
|
+
// ✅ V3 Quoter ABI(用于 V3 报价)
|
|
52
|
+
const V3_QUOTER_ABI = [
|
|
53
|
+
'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)'
|
|
54
|
+
];
|
|
55
|
+
// ✅ 各链的报价配置(V2 Router + V3 Quoter)
|
|
52
56
|
const QUOTE_CONFIG = {
|
|
53
57
|
BSC: {
|
|
54
|
-
router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2
|
|
58
|
+
router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2 Router
|
|
59
|
+
quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', // PancakeSwap V3 Quoter
|
|
55
60
|
wrappedNative: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
|
|
56
61
|
},
|
|
57
62
|
MONAD: {
|
|
58
63
|
router: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // PancakeSwap V2
|
|
64
|
+
quoter: '', // TODO: 添加 Monad V3 Quoter
|
|
59
65
|
wrappedNative: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // WMON
|
|
60
66
|
},
|
|
61
67
|
XLAYER: {
|
|
62
68
|
router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // PotatoSwap V2
|
|
69
|
+
quoter: '', // TODO: 添加 XLayer V3 Quoter
|
|
63
70
|
wrappedNative: '0xe538905cf8410324e03a5a23c1c177a474d59b2b', // WOKB
|
|
64
71
|
},
|
|
65
72
|
};
|
|
73
|
+
// ✅ V3 常用费率档位(用于报价尝试)
|
|
74
|
+
const V3_FEE_TIERS = [100, 500, 2500, 10000]; // 0.01%, 0.05%, 0.25%, 1%
|
|
66
75
|
/**
|
|
67
76
|
* 截断小数位数,避免超过代币精度导致 parseUnits 报错
|
|
68
77
|
* 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
|
|
@@ -551,16 +560,31 @@ async function getGasPrice(provider, config) {
|
|
|
551
560
|
function isNativeToken(quoteToken) {
|
|
552
561
|
return !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
553
562
|
}
|
|
563
|
+
// ✅ 常用稳定币地址(用于多跳报价)
|
|
564
|
+
const STABLE_COINS = {
|
|
565
|
+
BSC: [
|
|
566
|
+
'0x55d398326f99059fF775485246999027B3197955', // USDT
|
|
567
|
+
'0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
|
|
568
|
+
'0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
|
|
569
|
+
],
|
|
570
|
+
MONAD: [],
|
|
571
|
+
XLAYER: [
|
|
572
|
+
'0x1E4a5963aBFD975d8c9021ce480b42188849D41d', // USDT
|
|
573
|
+
],
|
|
574
|
+
};
|
|
554
575
|
/**
|
|
555
576
|
* ✅ 获取 ERC20 代币 → 原生代币的报价
|
|
556
577
|
* 用于将 ERC20 利润转换为原生代币(和 core.ts 逻辑一致)
|
|
578
|
+
*
|
|
557
579
|
* @param provider - Provider 实例
|
|
558
580
|
* @param tokenAddress - ERC20 代币地址
|
|
559
581
|
* @param tokenAmount - 代币数量(wei)
|
|
560
582
|
* @param chain - 链名称
|
|
583
|
+
* @param version - 'v2' | 'v3',指定使用哪个版本的报价
|
|
584
|
+
* @param fee - V3 费率档位(仅 V3 时使用)
|
|
561
585
|
* @returns 等值的原生代币数量(wei),失败返回 0n
|
|
562
586
|
*/
|
|
563
|
-
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain) {
|
|
587
|
+
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain, version = 'v2', fee) {
|
|
564
588
|
if (tokenAmount <= 0n)
|
|
565
589
|
return 0n;
|
|
566
590
|
const chainUpper = chain.toUpperCase();
|
|
@@ -569,16 +593,120 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain)
|
|
|
569
593
|
console.warn(`[getTokenToNativeQuote] 不支持的链: ${chain}`);
|
|
570
594
|
return 0n;
|
|
571
595
|
}
|
|
572
|
-
|
|
596
|
+
const tokenLower = tokenAddress.toLowerCase();
|
|
597
|
+
const wrappedNativeLower = config.wrappedNative.toLowerCase();
|
|
598
|
+
const stableCoins = STABLE_COINS[chainUpper] || [];
|
|
599
|
+
// 如果代币本身就是包装原生代币,直接返回
|
|
600
|
+
if (tokenLower === wrappedNativeLower) {
|
|
601
|
+
return tokenAmount;
|
|
602
|
+
}
|
|
603
|
+
// ==================== V2 报价 ====================
|
|
604
|
+
if (version === 'v2') {
|
|
573
605
|
const router = new Contract(config.router, QUOTE_ROUTER_ABI, provider);
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
606
|
+
// V2 策略 1:直接路径 代币 → WBNB
|
|
607
|
+
try {
|
|
608
|
+
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, config.wrappedNative]);
|
|
609
|
+
const nativeAmount = amounts[1];
|
|
610
|
+
if (nativeAmount > 0n) {
|
|
611
|
+
console.log(`[getTokenToNativeQuote] V2 直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
|
|
612
|
+
return nativeAmount;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// V2 直接路径失败,尝试多跳
|
|
617
|
+
}
|
|
618
|
+
// V2 策略 2:多跳路径 代币 → 稳定币 → WBNB
|
|
619
|
+
for (const stableCoin of stableCoins) {
|
|
620
|
+
// 跳过:代币本身就是稳定币
|
|
621
|
+
if (tokenLower === stableCoin.toLowerCase()) {
|
|
622
|
+
try {
|
|
623
|
+
const amounts = await router.getAmountsOut(tokenAmount, [stableCoin, config.wrappedNative]);
|
|
624
|
+
const nativeAmount = amounts[1];
|
|
625
|
+
if (nativeAmount > 0n) {
|
|
626
|
+
console.log(`[getTokenToNativeQuote] V2 稳定币直接路径成功: ${ethers.formatEther(nativeAmount)} BNB`);
|
|
627
|
+
return nativeAmount;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// 多跳:代币 → 稳定币 → WBNB
|
|
635
|
+
try {
|
|
636
|
+
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, stableCoin, config.wrappedNative]);
|
|
637
|
+
const nativeAmount = amounts[2];
|
|
638
|
+
if (nativeAmount > 0n) {
|
|
639
|
+
console.log(`[getTokenToNativeQuote] V2 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(nativeAmount)} BNB`);
|
|
640
|
+
return nativeAmount;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
console.warn(`[getTokenToNativeQuote] V2 所有报价路径均失败,跳过利润提取`);
|
|
648
|
+
return 0n;
|
|
577
649
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
650
|
+
// ==================== V3 报价 ====================
|
|
651
|
+
if (version === 'v3') {
|
|
652
|
+
if (!config.quoter) {
|
|
653
|
+
console.warn(`[getTokenToNativeQuote] 该链不支持 V3 Quoter: ${chain}`);
|
|
654
|
+
return 0n;
|
|
655
|
+
}
|
|
656
|
+
const quoter = new Contract(config.quoter, V3_QUOTER_ABI, provider);
|
|
657
|
+
// V3 策略 1:直接路径 代币 → WBNB
|
|
658
|
+
// 如果指定了 fee,只用指定的 fee;否则尝试所有费率
|
|
659
|
+
const feesToTry = fee ? [fee] : V3_FEE_TIERS;
|
|
660
|
+
console.log(`[getTokenToNativeQuote] 开始 V3 报价: token=${tokenAddress.slice(0, 10)}..., amount=${tokenAmount}, feesToTry=${feesToTry}`);
|
|
661
|
+
for (const tryFee of feesToTry) {
|
|
662
|
+
try {
|
|
663
|
+
const amountOut = await quoter.quoteExactInputSingle.staticCall(tokenAddress, config.wrappedNative, tryFee, tokenAmount, 0n);
|
|
664
|
+
if (amountOut > 0n) {
|
|
665
|
+
console.log(`[getTokenToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
|
|
666
|
+
return amountOut;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
console.log(`[getTokenToNativeQuote] V3 直接路径失败 (fee=${tryFee}): ${String(err).slice(0, 100)}`);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// V3 策略 2:多跳路径 代币 → USDT → WBNB
|
|
675
|
+
for (const stableCoin of stableCoins) {
|
|
676
|
+
if (tokenLower === stableCoin.toLowerCase())
|
|
677
|
+
continue;
|
|
678
|
+
for (const fee1 of feesToTry) {
|
|
679
|
+
try {
|
|
680
|
+
// 第一跳:代币 → 稳定币
|
|
681
|
+
const midAmount = await quoter.quoteExactInputSingle.staticCall(tokenAddress, stableCoin, fee1, tokenAmount, 0n);
|
|
682
|
+
if (midAmount <= 0n)
|
|
683
|
+
continue;
|
|
684
|
+
console.log(`[getTokenToNativeQuote] V3 第一跳成功: 代币 → ${stableCoin.slice(0, 10)}... = ${midAmount}`);
|
|
685
|
+
// 第二跳:稳定币 → WBNB(尝试多个费率)
|
|
686
|
+
const stableFees = [100, 500, 2500]; // 0.01%, 0.05%, 0.25%
|
|
687
|
+
for (const fee2 of stableFees) {
|
|
688
|
+
try {
|
|
689
|
+
const amountOut = await quoter.quoteExactInputSingle.staticCall(stableCoin, config.wrappedNative, fee2, midAmount, 0n);
|
|
690
|
+
if (amountOut > 0n) {
|
|
691
|
+
console.log(`[getTokenToNativeQuote] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
|
|
692
|
+
return amountOut;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
console.log(`[getTokenToNativeQuote] V3 第一跳失败 (fee=${fee1}): ${err}`);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
console.warn(`[getTokenToNativeQuote] V3 所有报价路径均失败,跳过利润提取`);
|
|
707
|
+
return 0n;
|
|
581
708
|
}
|
|
709
|
+
return 0n;
|
|
582
710
|
}
|
|
583
711
|
/**
|
|
584
712
|
* ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
|
|
@@ -713,9 +841,9 @@ export async function directV2BatchBuy(params) {
|
|
|
713
841
|
? Promise.resolve(startNonces)
|
|
714
842
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
715
843
|
getGasPrice(provider, config),
|
|
716
|
-
// ERC20
|
|
844
|
+
// ERC20 报价提前并行获取(V2 买入用 V2 报价)
|
|
717
845
|
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
718
|
-
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
|
|
846
|
+
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
|
|
719
847
|
: Promise.resolve(baseProfitWei)
|
|
720
848
|
]);
|
|
721
849
|
// 确定最终利润金额
|
|
@@ -852,8 +980,8 @@ export async function directV2BatchSell(params) {
|
|
|
852
980
|
const nativeProfitPromise = (async () => {
|
|
853
981
|
try {
|
|
854
982
|
if (useNativeOutput) {
|
|
855
|
-
// 卖出代币 → 得到 BNB
|
|
856
|
-
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
|
|
983
|
+
// 卖出代币 → 得到 BNB:先获取 V2 报价,再计算利润
|
|
984
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
|
|
857
985
|
console.log(`[V2 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
|
|
858
986
|
if (estimatedBNBOut <= 0n)
|
|
859
987
|
return 0n;
|
|
@@ -862,13 +990,12 @@ export async function directV2BatchSell(params) {
|
|
|
862
990
|
return profit;
|
|
863
991
|
}
|
|
864
992
|
else if (quoteToken) {
|
|
865
|
-
// 卖出代币 → 得到 ERC20
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (estimatedQuoteOut <= 0n)
|
|
993
|
+
// 卖出代币 → 得到 ERC20:先获取 V2 报价,再计算利润
|
|
994
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
|
|
995
|
+
if (estimatedBNBOut <= 0n)
|
|
869
996
|
return 0n;
|
|
870
|
-
const
|
|
871
|
-
return
|
|
997
|
+
const profit = calculateProfitAmount(estimatedBNBOut);
|
|
998
|
+
return profit;
|
|
872
999
|
}
|
|
873
1000
|
return 0n;
|
|
874
1001
|
}
|
|
@@ -1000,8 +1127,9 @@ export async function directV3BatchBuy(params) {
|
|
|
1000
1127
|
? Promise.resolve(startNonces)
|
|
1001
1128
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
1002
1129
|
getGasPrice(provider, config),
|
|
1130
|
+
// ERC20 报价(V3 买入用 V3 报价)
|
|
1003
1131
|
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
1004
|
-
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
|
|
1132
|
+
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3', fee)
|
|
1005
1133
|
: Promise.resolve(baseProfitWei)
|
|
1006
1134
|
]);
|
|
1007
1135
|
const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
@@ -1114,8 +1242,8 @@ export async function directV3BatchSell(params) {
|
|
|
1114
1242
|
const nativeProfitPromise = (async () => {
|
|
1115
1243
|
try {
|
|
1116
1244
|
if (useNativeOutput) {
|
|
1117
|
-
// 卖出代币 → 得到 BNB
|
|
1118
|
-
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
|
|
1245
|
+
// 卖出代币 → 得到 BNB:先获取 V3 报价,再计算利润
|
|
1246
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
|
|
1119
1247
|
console.log(`[V3 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
|
|
1120
1248
|
if (estimatedBNBOut <= 0n)
|
|
1121
1249
|
return 0n;
|
|
@@ -1124,8 +1252,8 @@ export async function directV3BatchSell(params) {
|
|
|
1124
1252
|
return profit;
|
|
1125
1253
|
}
|
|
1126
1254
|
else if (quoteToken) {
|
|
1127
|
-
// 卖出代币 → 得到 ERC20
|
|
1128
|
-
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
|
|
1255
|
+
// 卖出代币 → 得到 ERC20:先获取 V3 报价,再计算利润
|
|
1256
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
|
|
1129
1257
|
if (estimatedBNBOut <= 0n)
|
|
1130
1258
|
return 0n;
|
|
1131
1259
|
const profit = calculateProfitAmount(estimatedBNBOut);
|