four-flap-meme-sdk 1.4.8 → 1.4.10

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.
@@ -48,9 +48,31 @@ const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
48
48
  const QUOTE_ROUTER_ABI = [
49
49
  'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
50
50
  ];
51
- // ✅ V3 Quoter ABI(用于 V3 报价)
51
+ // ✅ V3 QuoterV2 ABI(用于 V3 报价)
52
+ // PancakeSwap V3 使用结构体参数版本
52
53
  const V3_QUOTER_ABI = [
53
- 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)'
54
+ {
55
+ "inputs": [{
56
+ "components": [
57
+ { "name": "tokenIn", "type": "address" },
58
+ { "name": "tokenOut", "type": "address" },
59
+ { "name": "amountIn", "type": "uint256" },
60
+ { "name": "fee", "type": "uint24" },
61
+ { "name": "sqrtPriceLimitX96", "type": "uint160" }
62
+ ],
63
+ "name": "params",
64
+ "type": "tuple"
65
+ }],
66
+ "name": "quoteExactInputSingle",
67
+ "outputs": [
68
+ { "name": "amountOut", "type": "uint256" },
69
+ { "name": "sqrtPriceX96After", "type": "uint160" },
70
+ { "name": "initializedTicksCrossed", "type": "uint32" },
71
+ { "name": "gasEstimate", "type": "uint256" }
72
+ ],
73
+ "stateMutability": "nonpayable",
74
+ "type": "function"
75
+ }
54
76
  ];
55
77
  // ✅ 各链的报价配置(V2 Router + V3 Quoter)
56
78
  const QUOTE_CONFIG = {
@@ -660,7 +682,16 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain,
660
682
  console.log(`[getTokenToNativeQuote] 开始 V3 报价: token=${tokenAddress.slice(0, 10)}..., amount=${tokenAmount}, feesToTry=${feesToTry}`);
661
683
  for (const tryFee of feesToTry) {
662
684
  try {
663
- const amountOut = await quoter.quoteExactInputSingle.staticCall(tokenAddress, config.wrappedNative, tryFee, tokenAmount, 0n);
685
+ // 使用结构体参数调用 QuoterV2
686
+ const result = await quoter.quoteExactInputSingle.staticCall({
687
+ tokenIn: tokenAddress,
688
+ tokenOut: config.wrappedNative,
689
+ amountIn: tokenAmount,
690
+ fee: tryFee,
691
+ sqrtPriceLimitX96: 0n
692
+ });
693
+ // QuoterV2 返回多个值,第一个是 amountOut
694
+ const amountOut = result[0];
664
695
  if (amountOut > 0n) {
665
696
  console.log(`[getTokenToNativeQuote] V3 直接路径成功 (fee=${tryFee}): ${ethers.formatEther(amountOut)} BNB`);
666
697
  return amountOut;
@@ -677,8 +708,15 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain,
677
708
  continue;
678
709
  for (const fee1 of feesToTry) {
679
710
  try {
680
- // 第一跳:代币 → 稳定币
681
- const midAmount = await quoter.quoteExactInputSingle.staticCall(tokenAddress, stableCoin, fee1, tokenAmount, 0n);
711
+ // 第一跳:代币 → 稳定币(使用结构体参数)
712
+ const midResult = await quoter.quoteExactInputSingle.staticCall({
713
+ tokenIn: tokenAddress,
714
+ tokenOut: stableCoin,
715
+ amountIn: tokenAmount,
716
+ fee: fee1,
717
+ sqrtPriceLimitX96: 0n
718
+ });
719
+ const midAmount = midResult[0];
682
720
  if (midAmount <= 0n)
683
721
  continue;
684
722
  console.log(`[getTokenToNativeQuote] V3 第一跳成功: 代币 → ${stableCoin.slice(0, 10)}... = ${midAmount}`);
@@ -686,7 +724,14 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain,
686
724
  const stableFees = [100, 500, 2500]; // 0.01%, 0.05%, 0.25%
687
725
  for (const fee2 of stableFees) {
688
726
  try {
689
- const amountOut = await quoter.quoteExactInputSingle.staticCall(stableCoin, config.wrappedNative, fee2, midAmount, 0n);
727
+ const finalResult = await quoter.quoteExactInputSingle.staticCall({
728
+ tokenIn: stableCoin,
729
+ tokenOut: config.wrappedNative,
730
+ amountIn: midAmount,
731
+ fee: fee2,
732
+ sqrtPriceLimitX96: 0n
733
+ });
734
+ const amountOut = finalResult[0];
690
735
  if (amountOut > 0n) {
691
736
  console.log(`[getTokenToNativeQuote] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
692
737
  return amountOut;
@@ -33,34 +33,137 @@ async function ensureSellerApproval({ tokenAddress, seller, provider, decimals,
33
33
  });
34
34
  }
35
35
  async function quoteSellOutput({ routeParams, sellAmountWei, provider }) {
36
+ const tokenIn = routeParams.routeType === 'v2'
37
+ ? routeParams.v2Path[0]
38
+ : routeParams.v3TokenIn;
39
+ const tokenInLower = tokenIn.toLowerCase();
40
+ // ==================== V2 报价 ====================
36
41
  if (routeParams.routeType === 'v2') {
37
42
  const { v2Path } = routeParams;
38
43
  const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
39
- const amounts = await v2Router.getAmountsOut(sellAmountWei, v2Path);
40
- return { estimatedBNBOut: amounts[amounts.length - 1] };
44
+ // V2 策略 1:直接路径
45
+ try {
46
+ const amounts = await v2Router.getAmountsOut(sellAmountWei, v2Path);
47
+ const amountOut = amounts[amounts.length - 1];
48
+ if (amountOut > 0n) {
49
+ console.log(`[quoteSellOutput] V2 直接路径成功: ${ethers.formatEther(amountOut)} BNB`);
50
+ return { estimatedBNBOut: amountOut };
51
+ }
52
+ }
53
+ catch (err) {
54
+ console.log(`[quoteSellOutput] V2 直接路径失败: ${String(err).slice(0, 100)}`);
55
+ }
56
+ // V2 策略 2:多跳路径 (代币 → 稳定币 → WBNB)
57
+ for (const stableCoin of STABLE_COINS) {
58
+ if (tokenInLower === stableCoin.toLowerCase())
59
+ continue;
60
+ try {
61
+ const multiHopPath = [tokenIn, stableCoin, WBNB_ADDRESS];
62
+ const amounts = await v2Router.getAmountsOut(sellAmountWei, multiHopPath);
63
+ const amountOut = amounts[amounts.length - 1];
64
+ if (amountOut > 0n) {
65
+ console.log(`[quoteSellOutput] V2 多跳路径成功 (via ${stableCoin.slice(0, 10)}...): ${ethers.formatEther(amountOut)} BNB`);
66
+ return { estimatedBNBOut: amountOut };
67
+ }
68
+ }
69
+ catch (err) {
70
+ continue;
71
+ }
72
+ }
73
+ throw new Error('V2 报价失败: 所有路径均无效');
41
74
  }
75
+ // ==================== V3 报价 ====================
42
76
  if (routeParams.routeType === 'v3-single') {
43
77
  const params = routeParams;
44
78
  const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, PANCAKE_V3_QUOTER_ABI, provider);
45
- try {
46
- const result = await quoter.quoteExactInputSingle.staticCall({
47
- tokenIn: params.v3TokenIn,
48
- tokenOut: params.v3TokenOut,
49
- amountIn: sellAmountWei,
50
- fee: params.v3Fee,
51
- sqrtPriceLimitX96: 0
52
- });
53
- return { estimatedBNBOut: result[0] };
79
+ // 如果指定了 fee,只用指定的 fee;否则尝试所有费率
80
+ const feesToTry = params.v3Fee ? [params.v3Fee] : V3_FEE_TIERS;
81
+ console.log(`[quoteSellOutput] 开始 V3 报价: tokenIn=${params.v3TokenIn.slice(0, 10)}..., tokenOut=${params.v3TokenOut.slice(0, 10)}..., feesToTry=${feesToTry}`);
82
+ // V3 策略 1:直接路径(尝试多个费率)
83
+ for (const fee of feesToTry) {
84
+ try {
85
+ const result = await quoter.quoteExactInputSingle.staticCall({
86
+ tokenIn: params.v3TokenIn,
87
+ tokenOut: params.v3TokenOut,
88
+ amountIn: sellAmountWei,
89
+ fee: fee,
90
+ sqrtPriceLimitX96: 0n
91
+ });
92
+ const amountOut = Array.isArray(result) ? result[0] : result;
93
+ if (amountOut && amountOut > 0n) {
94
+ console.log(`[quoteSellOutput] V3 直接路径成功 (fee=${fee}): ${ethers.formatEther(amountOut)} BNB`);
95
+ return { estimatedBNBOut: amountOut };
96
+ }
97
+ }
98
+ catch (err) {
99
+ console.log(`[quoteSellOutput] V3 直接路径失败 (fee=${fee}): ${String(err).slice(0, 100)}`);
100
+ continue;
101
+ }
102
+ }
103
+ // V3 策略 2:多跳路径 (代币 → 稳定币 → WBNB)
104
+ for (const stableCoin of STABLE_COINS) {
105
+ if (tokenInLower === stableCoin.toLowerCase())
106
+ continue;
107
+ for (const fee1 of feesToTry) {
108
+ try {
109
+ // 第一跳:代币 → 稳定币
110
+ const midResult = await quoter.quoteExactInputSingle.staticCall({
111
+ tokenIn: params.v3TokenIn,
112
+ tokenOut: stableCoin,
113
+ amountIn: sellAmountWei,
114
+ fee: fee1,
115
+ sqrtPriceLimitX96: 0n
116
+ });
117
+ const midAmount = Array.isArray(midResult) ? midResult[0] : midResult;
118
+ if (!midAmount || midAmount <= 0n)
119
+ continue;
120
+ console.log(`[quoteSellOutput] V3 第一跳成功: 代币 → ${stableCoin.slice(0, 10)}... = ${midAmount}`);
121
+ // 第二跳:稳定币 → WBNB(尝试多个费率)
122
+ const stableFees = [100, 500, 2500];
123
+ for (const fee2 of stableFees) {
124
+ try {
125
+ const finalResult = await quoter.quoteExactInputSingle.staticCall({
126
+ tokenIn: stableCoin,
127
+ tokenOut: WBNB_ADDRESS,
128
+ amountIn: midAmount,
129
+ fee: fee2,
130
+ sqrtPriceLimitX96: 0n
131
+ });
132
+ const amountOut = Array.isArray(finalResult) ? finalResult[0] : finalResult;
133
+ if (amountOut && amountOut > 0n) {
134
+ console.log(`[quoteSellOutput] V3 多跳路径成功 (${fee1}→${fee2}): ${ethers.formatEther(amountOut)} BNB`);
135
+ return { estimatedBNBOut: amountOut };
136
+ }
137
+ }
138
+ catch {
139
+ continue;
140
+ }
141
+ }
142
+ }
143
+ catch (err) {
144
+ console.log(`[quoteSellOutput] V3 第一跳失败 (fee=${fee1}): ${String(err).slice(0, 100)}`);
145
+ continue;
146
+ }
147
+ }
54
148
  }
55
- catch (v3Error) {
56
- if (params.v2Path && params.v2Path.length >= 2) {
149
+ // V3 全部失败,尝试 V2 fallback
150
+ if (params.v2Path && params.v2Path.length >= 2) {
151
+ try {
57
152
  const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
58
153
  const amounts = await v2Router.getAmountsOut(sellAmountWei, params.v2Path);
59
- return { estimatedBNBOut: amounts[amounts.length - 1] };
154
+ const amountOut = amounts[amounts.length - 1];
155
+ if (amountOut > 0n) {
156
+ console.log(`[quoteSellOutput] V3→V2 fallback 成功: ${ethers.formatEther(amountOut)} BNB`);
157
+ return { estimatedBNBOut: amountOut };
158
+ }
159
+ }
160
+ catch (v2Error) {
161
+ console.log(`[quoteSellOutput] V3→V2 fallback 失败: ${String(v2Error).slice(0, 100)}`);
60
162
  }
61
- throw new Error(`V3 QuoterV2 失败 (fee=${params.v3Fee}) 且未提供 v2Path 作为备选。可能的原因: 1. V3 池子不存在 2. 费率档位错误 3. 流动性不足`);
62
163
  }
164
+ throw new Error(`V3 QuoterV2 报价失败 (尝试费率: ${feesToTry.join(', ')}) 且 V2 路径无效`);
63
165
  }
166
+ // ==================== V3 多跳 ====================
64
167
  const params = routeParams;
65
168
  if (params.v2Path && params.v2Path.length >= 2) {
66
169
  const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, provider);
@@ -291,6 +394,14 @@ const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
291
394
  // 代理合约手续费
292
395
  const FLAT_FEE = 0n; // ✅ 已移除合约固定手续费
293
396
  const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
397
+ // ✅ 常用稳定币地址(用于多跳报价)
398
+ const STABLE_COINS = [
399
+ '0x55d398326f99059fF775485246999027B3197955', // USDT
400
+ '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', // USDC
401
+ '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', // BUSD
402
+ ];
403
+ // ✅ V3 常用费率档位
404
+ const V3_FEE_TIERS = [100, 500, 2500, 10000]; // 0.01%, 0.05%, 0.25%, 1%
294
405
  const ERC20_ALLOWANCE_ABI = [
295
406
  'function allowance(address,address) view returns (uint256)',
296
407
  'function approve(address spender,uint256 amount) returns (bool)',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.8",
3
+ "version": "1.4.10",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",