four-flap-meme-sdk 1.4.5 → 1.4.7
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 +180 -42
- 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,109 @@ 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
|
+
for (const tryFee of feesToTry) {
|
|
661
|
+
try {
|
|
662
|
+
const amountOut = await quoter.quoteExactInputSingle.staticCall(tokenAddress, config.wrappedNative, tryFee, tokenAmount, 0n);
|
|
663
|
+
if (amountOut > 0n) {
|
|
664
|
+
console.log(`[getTokenToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
|
|
665
|
+
return amountOut;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// V3 策略 2:多跳路径 代币 → USDT → WBNB
|
|
673
|
+
for (const stableCoin of stableCoins) {
|
|
674
|
+
if (tokenLower === stableCoin.toLowerCase())
|
|
675
|
+
continue;
|
|
676
|
+
for (const fee1 of feesToTry) {
|
|
677
|
+
try {
|
|
678
|
+
// 第一跳:代币 → 稳定币
|
|
679
|
+
const midAmount = await quoter.quoteExactInputSingle.staticCall(tokenAddress, stableCoin, fee1, tokenAmount, 0n);
|
|
680
|
+
if (midAmount <= 0n)
|
|
681
|
+
continue;
|
|
682
|
+
// 第二跳:稳定币 → WBNB(使用固定费率 500 = 0.05%)
|
|
683
|
+
const amountOut = await quoter.quoteExactInputSingle.staticCall(stableCoin, config.wrappedNative, 500, // 稳定币/WBNB 通常使用 0.05% 费率
|
|
684
|
+
midAmount, 0n);
|
|
685
|
+
if (amountOut > 0n) {
|
|
686
|
+
console.log(`[getTokenToNativeQuote] V3 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(amountOut)} BNB`);
|
|
687
|
+
return amountOut;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
console.warn(`[getTokenToNativeQuote] V3 所有报价路径均失败,跳过利润提取`);
|
|
696
|
+
return 0n;
|
|
581
697
|
}
|
|
698
|
+
return 0n;
|
|
582
699
|
}
|
|
583
700
|
/**
|
|
584
701
|
* ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
|
|
@@ -713,9 +830,9 @@ export async function directV2BatchBuy(params) {
|
|
|
713
830
|
? Promise.resolve(startNonces)
|
|
714
831
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
715
832
|
getGasPrice(provider, config),
|
|
716
|
-
// ERC20
|
|
833
|
+
// ERC20 报价提前并行获取(V2 买入用 V2 报价)
|
|
717
834
|
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
718
|
-
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
|
|
835
|
+
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
|
|
719
836
|
: Promise.resolve(baseProfitWei)
|
|
720
837
|
]);
|
|
721
838
|
// 确定最终利润金额
|
|
@@ -848,23 +965,33 @@ export async function directV2BatchSell(params) {
|
|
|
848
965
|
}
|
|
849
966
|
const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
850
967
|
// ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
|
|
851
|
-
//
|
|
968
|
+
// 使用 V2 Router 获取卖出报价,然后基于预估得到的原生代币数量计算利润
|
|
852
969
|
const nativeProfitPromise = (async () => {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
970
|
+
try {
|
|
971
|
+
if (useNativeOutput) {
|
|
972
|
+
// 卖出代币 → 得到 BNB:先获取 V2 报价,再计算利润
|
|
973
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
|
|
974
|
+
console.log(`[V2 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
|
|
975
|
+
if (estimatedBNBOut <= 0n)
|
|
976
|
+
return 0n;
|
|
977
|
+
const profit = calculateProfitAmount(estimatedBNBOut);
|
|
978
|
+
console.log(`[V2 Sell Profit] profit: ${profit}, profitBNB: ${ethers.formatEther(profit)}`);
|
|
979
|
+
return profit;
|
|
980
|
+
}
|
|
981
|
+
else if (quoteToken) {
|
|
982
|
+
// 卖出代币 → 得到 ERC20:先获取 V2 报价,再计算利润
|
|
983
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v2');
|
|
984
|
+
if (estimatedBNBOut <= 0n)
|
|
985
|
+
return 0n;
|
|
986
|
+
const profit = calculateProfitAmount(estimatedBNBOut);
|
|
987
|
+
return profit;
|
|
988
|
+
}
|
|
989
|
+
return 0n;
|
|
859
990
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (baseProfitWei <= 0n)
|
|
864
|
-
return 0n;
|
|
865
|
-
return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
|
|
991
|
+
catch (error) {
|
|
992
|
+
console.error(`[V2 Sell Profit] Error:`, error);
|
|
993
|
+
return 0n;
|
|
866
994
|
}
|
|
867
|
-
return 0n;
|
|
868
995
|
})();
|
|
869
996
|
// 构建路径和 Router
|
|
870
997
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
@@ -989,8 +1116,9 @@ export async function directV3BatchBuy(params) {
|
|
|
989
1116
|
? Promise.resolve(startNonces)
|
|
990
1117
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
991
1118
|
getGasPrice(provider, config),
|
|
1119
|
+
// ERC20 报价(V3 买入用 V3 报价)
|
|
992
1120
|
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
993
|
-
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
|
|
1121
|
+
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v3', fee)
|
|
994
1122
|
: Promise.resolve(baseProfitWei)
|
|
995
1123
|
]);
|
|
996
1124
|
const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
@@ -1099,23 +1227,33 @@ export async function directV3BatchSell(params) {
|
|
|
1099
1227
|
}
|
|
1100
1228
|
const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
1101
1229
|
// ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
|
|
1102
|
-
//
|
|
1230
|
+
// 使用 V2 Router 获取卖出报价,然后基于预估得到的原生代币数量计算利润
|
|
1103
1231
|
const nativeProfitPromise = (async () => {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1232
|
+
try {
|
|
1233
|
+
if (useNativeOutput) {
|
|
1234
|
+
// 卖出代币 → 得到 BNB:先获取 V3 报价,再计算利润
|
|
1235
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
|
|
1236
|
+
console.log(`[V3 Sell Profit] totalSellAmount: ${totalSellAmount}, estimatedBNBOut: ${estimatedBNBOut}, estimatedBNB: ${ethers.formatEther(estimatedBNBOut)}`);
|
|
1237
|
+
if (estimatedBNBOut <= 0n)
|
|
1238
|
+
return 0n;
|
|
1239
|
+
const profit = calculateProfitAmount(estimatedBNBOut);
|
|
1240
|
+
console.log(`[V3 Sell Profit] profit: ${profit}, profitBNB: ${ethers.formatEther(profit)}`);
|
|
1241
|
+
return profit;
|
|
1242
|
+
}
|
|
1243
|
+
else if (quoteToken) {
|
|
1244
|
+
// 卖出代币 → 得到 ERC20:先获取 V3 报价,再计算利润
|
|
1245
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain, 'v3', fee);
|
|
1246
|
+
if (estimatedBNBOut <= 0n)
|
|
1247
|
+
return 0n;
|
|
1248
|
+
const profit = calculateProfitAmount(estimatedBNBOut);
|
|
1249
|
+
return profit;
|
|
1250
|
+
}
|
|
1251
|
+
return 0n;
|
|
1110
1252
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if (baseProfitWei <= 0n)
|
|
1115
|
-
return 0n;
|
|
1116
|
-
return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
|
|
1253
|
+
catch (error) {
|
|
1254
|
+
console.error(`[V3 Sell Profit] Error:`, error);
|
|
1255
|
+
return 0n;
|
|
1117
1256
|
}
|
|
1118
|
-
return 0n;
|
|
1119
1257
|
})();
|
|
1120
1258
|
const routerIface = new ethers.Interface(routerAbi);
|
|
1121
1259
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|