four-flap-meme-sdk 2.2.13 → 2.2.15

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/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export { TM2, type FourChainV2 } from './contracts/tm2.js';
15
15
  export { Helper3, Helper3Writer } from './contracts/helper3.js';
16
16
  export { CDPV2 } from './shared/flap/curve.js';
17
17
  export { FlapPortal, FlapPortalWriter, type FlapChain, type PortalConfig, type TokenStateV2, type TokenStateV3, type TokenStateV4, type TokenStateV5, type TokenStateV7, type QuoteExactInputParams, type ExactInputParams, type ExactInputV3Params, type NewTokenV3Params, type NewTokenV4Params, type NewTokenV5Params, type TaxDistributionConfig, validateTaxDistribution, TokenStatus, TokenVersion, FeeType, DexThreshType, MigratorType, V3LPFeeProfile, lpFeeProfileToV3Fee, DEXId } from './shared/flap/portal.js';
18
- export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, PCS_INFINITY_CL_POOL_MANAGER_BSC, FLAP_INFINITY_HOOKS_BSC, type GraduatedDexMetrics, type InfinityPoolKey, } from './shared/flap/graduated-dex.js';
18
+ export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, isFlapInfinityCLGraduated, resolvePortalQuoteInputToken, PCS_INFINITY_CL_POOL_MANAGER_BSC, PCS_INFINITY_VAULT_BSC, FLAP_INFINITY_HOOKS_BSC, type GraduatedDexMetrics, type InfinityPoolKey, } from './shared/flap/graduated-dex.js';
19
19
  export { uploadTokenMeta, type TokenMetaInput } from './shared/flap/ipfs.js';
20
20
  export { buildPermitPiggybackAuto } from './shared/flap/permit.js';
21
21
  export { predictVanityTokenAddressByChain, findSaltEndingByChain } from './shared/flap/vanity.js';
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ export { TM2 } from './contracts/tm2.js';
48
48
  export { Helper3, Helper3Writer } from './contracts/helper3.js';
49
49
  export { CDPV2 } from './shared/flap/curve.js';
50
50
  export { FlapPortal, FlapPortalWriter, validateTaxDistribution, TokenStatus, TokenVersion, FeeType, DexThreshType, MigratorType, V3LPFeeProfile, lpFeeProfileToV3Fee, DEXId } from './shared/flap/portal.js';
51
- export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, PCS_INFINITY_CL_POOL_MANAGER_BSC, FLAP_INFINITY_HOOKS_BSC, } from './shared/flap/graduated-dex.js';
51
+ export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, isFlapInfinityCLGraduated, resolvePortalQuoteInputToken, PCS_INFINITY_CL_POOL_MANAGER_BSC, PCS_INFINITY_VAULT_BSC, FLAP_INFINITY_HOOKS_BSC, } from './shared/flap/graduated-dex.js';
52
52
  export { uploadTokenMeta } from './shared/flap/ipfs.js';
53
53
  export { buildPermitPiggybackAuto } from './shared/flap/permit.js';
54
54
  export { predictVanityTokenAddressByChain, findSaltEndingByChain } from './shared/flap/vanity.js';
@@ -1 +1 @@
1
- export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, PCS_INFINITY_CL_POOL_MANAGER_BSC, FLAP_INFINITY_HOOKS_BSC, type GraduatedDexMetrics, type InfinityPoolKey, } from '../../utils/pcs-infinity-cl.js';
1
+ export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, isFlapInfinityCLGraduated, resolvePortalQuoteInputToken, PCS_INFINITY_CL_POOL_MANAGER_BSC, PCS_INFINITY_VAULT_BSC, FLAP_INFINITY_HOOKS_BSC, type GraduatedDexMetrics, type InfinityPoolKey, } from '../../utils/pcs-infinity-cl.js';
@@ -1 +1 @@
1
- export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, PCS_INFINITY_CL_POOL_MANAGER_BSC, FLAP_INFINITY_HOOKS_BSC, } from '../../utils/pcs-infinity-cl.js';
1
+ export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, isFlapInfinityCLGraduated, resolvePortalQuoteInputToken, PCS_INFINITY_CL_POOL_MANAGER_BSC, PCS_INFINITY_VAULT_BSC, FLAP_INFINITY_HOOKS_BSC, } from '../../utils/pcs-infinity-cl.js';
@@ -3,7 +3,7 @@ import { ADDRESSES, ZERO_ADDRESS } from './constants.js';
3
3
  import { MULTICALL3_ABI, V2_FACTORY_ABI, V2_PAIR_ABI, V3_FACTORY_ABI, ERC20_ABI } from '../shared/abis/common.js';
4
4
  import { Helper3 } from '../contracts/helper3.js';
5
5
  import { FlapPortal } from '../shared/flap/portal.js';
6
- import { getFlapGraduatedDexMetrics, PCS_INFINITY_CL_POOL_MANAGER_BSC, } from './pcs-infinity-cl.js';
6
+ import { getFlapGraduatedDexMetrics, PCS_INFINITY_CL_POOL_MANAGER_BSC, isFlapInfinityCLGraduated, } from './pcs-infinity-cl.js';
7
7
  // ============================================================================
8
8
  // 链配置
9
9
  // ============================================================================
@@ -363,7 +363,7 @@ export async function inspectTokenLP(token, opts) {
363
363
  }
364
364
  if (st && st.status !== undefined) {
365
365
  const status = Number(st.status);
366
- // ✅ 已毕业:PCS Infinity CL 外盘(Portal pool 字段为 CLPoolManager)
366
+ // ✅ 已毕业:按池类型分流(Infinity CL / Pancake V3 pair / 继续扫外盘)
367
367
  if (status === 4) {
368
368
  const quoteAddr = st.quoteTokenAddress && st.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
369
369
  ? st.quoteTokenAddress
@@ -381,40 +381,51 @@ export async function inspectTokenLP(token, opts) {
381
381
  state: st,
382
382
  rpcUrl: opts.rpcUrl,
383
383
  wrappedNativeAddress: chainConfig.wrappedNative,
384
+ chain: opts.chain,
384
385
  });
385
- result.platform = 'FLAP';
386
- result.flap = {
387
- quoteToken: quoteAddr,
388
- quoteSymbol,
389
- quoteDecimals,
390
- reserveNative: metrics.poolBNBAmount,
391
- circulatingSupply: formatBalance(st.circulatingSupply, shouldFormat, tokenDecimals ?? 18),
392
- price: metrics.price,
393
- taxRate,
394
- };
395
- if (taxRate && taxRate > 0)
396
- result.taxRate = taxRate;
397
- const liqRaw = metrics.poolBNBAmount
398
- ? BigInt(Math.floor(parseFloat(metrics.poolBNBAmount) * 1e18))
399
- : 0n;
400
- const tokRaw = metrics.poolTokenAmount
401
- ? BigInt(Math.floor(parseFloat(metrics.poolTokenAmount) * 1e18))
402
- : 0n;
403
- result.bestPools.push({
404
- dex: 'PancakeSwap Infinity CL',
405
- dexKey: 'PCS_INFINITY_CL',
406
- version: 'v3',
407
- fee: metrics.infinityFee,
408
- quoteToken: quoteAddr,
409
- quoteSymbol,
410
- quoteDecimals,
411
- pairAddress: metrics.poolId ?? PCS_INFINITY_CL_POOL_MANAGER_BSC,
412
- liquidity: metrics.poolBNBAmount,
413
- liquidityRaw: liqRaw,
414
- reserveToken: metrics.poolTokenAmount,
415
- reserveTokenRaw: tokRaw,
416
- });
417
- return result;
386
+ const hasMetrics = (metrics.price && metrics.price !== '0') ||
387
+ parseFloat(metrics.poolBNBAmount || '0') > 0;
388
+ if (hasMetrics) {
389
+ result.platform = 'FLAP';
390
+ result.flap = {
391
+ quoteToken: quoteAddr,
392
+ quoteSymbol,
393
+ quoteDecimals,
394
+ reserveNative: metrics.poolBNBAmount,
395
+ circulatingSupply: formatBalance(st.circulatingSupply, shouldFormat, tokenDecimals ?? 18),
396
+ price: metrics.price,
397
+ taxRate,
398
+ };
399
+ if (taxRate && taxRate > 0)
400
+ result.taxRate = taxRate;
401
+ const liqRaw = metrics.poolBNBAmount
402
+ ? BigInt(Math.floor(parseFloat(metrics.poolBNBAmount) * 1e18))
403
+ : 0n;
404
+ const tokRaw = metrics.poolTokenAmount
405
+ ? BigInt(Math.floor(parseFloat(metrics.poolTokenAmount) * 1e18))
406
+ : 0n;
407
+ const isInfinity = isFlapInfinityCLGraduated(st, opts.chain);
408
+ result.bestPools.push({
409
+ dex: isInfinity ? 'PancakeSwap Infinity CL' : 'PancakeSwap V3',
410
+ dexKey: isInfinity ? 'PCS_INFINITY_CL' : 'PANCAKESWAP_V3',
411
+ version: 'v3',
412
+ fee: metrics.infinityFee,
413
+ quoteToken: quoteAddr,
414
+ quoteSymbol,
415
+ quoteDecimals,
416
+ pairAddress: isInfinity
417
+ ? (metrics.poolId ?? PCS_INFINITY_CL_POOL_MANAGER_BSC)
418
+ : (st.pool || ''),
419
+ liquidity: metrics.poolBNBAmount,
420
+ liquidityRaw: liqRaw,
421
+ reserveToken: metrics.poolTokenAmount,
422
+ reserveTokenRaw: tokRaw,
423
+ });
424
+ return result;
425
+ }
426
+ if (opts.debug) {
427
+ console.log('[LP Inspect] Flap graduated metrics empty, fallback to DEX scan');
428
+ }
418
429
  }
419
430
  result.platform = 'FLAP';
420
431
  let quoteDecimals = 18;
@@ -2,6 +2,8 @@ import type { FlapPortal } from '../shared/flap/portal.js';
2
2
  import type { TokenStateV7 } from '../shared/flap/portal.js';
3
3
  /** PancakeSwap Infinity CL PoolManager(BSC / Base 同址) */
4
4
  export declare const PCS_INFINITY_CL_POOL_MANAGER_BSC = "0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b";
5
+ /** PancakeSwap Infinity Vault(全池代币记账;BSC / Base 同址) */
6
+ export declare const PCS_INFINITY_VAULT_BSC = "0x238a358808379702088667322f80aC48bAd5e6c4";
5
7
  /** Flap Portal 毕业到 Infinity CL 时使用的 hooks(BSC) */
6
8
  export declare const FLAP_INFINITY_HOOKS_BSC = "0xF1A9aA042454b8553bE3896597ff11a0f011c1c1";
7
9
  export type InfinityPoolKey = {
@@ -12,6 +14,15 @@ export type InfinityPoolKey = {
12
14
  fee: number;
13
15
  parameters: string;
14
16
  };
17
+ export type GraduatedDexMetrics = {
18
+ price: string;
19
+ progress: string;
20
+ poolBNBAmount: string;
21
+ poolTokenAmount: string;
22
+ poolId?: string;
23
+ infinityFee?: number;
24
+ dexKind?: 'PCS_INFINITY_CL' | 'PANCAKE_V3' | 'PORTAL_QUOTE';
25
+ };
15
26
  export declare function sortInfinityCurrencies(a: string, b: string): [string, string];
16
27
  export declare function computeInfinityPoolId(key: InfinityPoolKey): string;
17
28
  export declare function buildFlapInfinityPoolKey(params: {
@@ -20,7 +31,15 @@ export declare function buildFlapInfinityPoolKey(params: {
20
31
  chain: string;
21
32
  lpFeeProfile?: number;
22
33
  }): InfinityPoolKey | null;
23
- /** slot0 计算 quote per token(与 memeweb V3 spot 公式一致) */
34
+ /** 判断 getTokenV7.pool 是否为 Infinity CL PoolManager(而非 V3 pair) */
35
+ export declare function isInfinityCLPoolManagerAddress(pool: string, chain: string): boolean;
36
+ /** 仅 V7 + Infinity CL 才走 Infinity 专用逻辑 */
37
+ export declare function isFlapInfinityCLGraduated(state: {
38
+ pool?: string;
39
+ tokenVersion?: bigint | number;
40
+ }, chain: string): boolean;
41
+ /** Portal quoteExactInput:链上 quoteToken 为 0 时必须用 native(0),不能用 WBNB */
42
+ export declare function resolvePortalQuoteInputToken(quoteTokenAddress: string, wrappedNativeAddress: string): string;
24
43
  export declare function infinitySpotPriceQuotePerToken(params: {
25
44
  sqrtPriceX96: bigint;
26
45
  tokenAddress: string;
@@ -30,26 +49,11 @@ export declare function infinitySpotPriceQuotePerToken(params: {
30
49
  tokenDecimals?: number;
31
50
  quoteDecimals?: number;
32
51
  }): string | null;
33
- /** 用活跃流动性 L 与 sqrtPrice 估算两侧虚拟储备(UI 展示用) */
34
- export declare function estimateInfinityReserves(params: {
35
- liquidity: bigint;
36
- sqrtPriceX96: bigint;
37
- tokenIsCurrency0: boolean;
38
- }): {
39
- reserveToken: string;
40
- reserveQuote: string;
41
- };
42
- export type GraduatedDexMetrics = {
43
- price: string;
44
- progress: string;
45
- poolBNBAmount: string;
46
- poolTokenAmount: string;
47
- poolId?: string;
48
- infinityFee?: number;
49
- };
50
52
  /**
51
- * Flap 毕业后(status=4)外盘:PCS Infinity CL + Portal quote
52
- * getTokenV7().pool V7 标准币上为 CLPoolManager 合约地址,非 V3 pair。
53
+ * Flap 毕业后(status=4)外盘统一入口:
54
+ * - V7 / Infinity CL Infinity + Portal quote 深度
55
+ * - 旧版 V3/V2(pool 为 pair 地址)→ 读 pair 余额 + slot0
56
+ * - 否则仅 Portal quote
53
57
  */
54
58
  export declare function getFlapGraduatedDexMetrics(params: {
55
59
  portal: FlapPortal;
@@ -57,6 +61,5 @@ export declare function getFlapGraduatedDexMetrics(params: {
57
61
  state: TokenStateV7;
58
62
  rpcUrl: string;
59
63
  wrappedNativeAddress?: string;
64
+ chain?: string;
60
65
  }): Promise<GraduatedDexMetrics>;
61
- /** 判断 getTokenV7.pool 是否为 Infinity CL PoolManager(而非 V3 pair) */
62
- export declare function isInfinityCLPoolManagerAddress(pool: string, chain: string): boolean;
@@ -1,13 +1,21 @@
1
1
  import { AbiCoder, Contract, JsonRpcProvider, formatEther, formatUnits, keccak256 } from 'ethers';
2
- import { FLAP_TOTAL_SUPPLY, ZERO_ADDRESS } from '../shared/flap/constants.js';
2
+ import { ZERO_ADDRESS } from '../shared/flap/constants.js';
3
+ import { ERC20_ABI } from '../shared/abis/common.js';
3
4
  /** PancakeSwap Infinity CL PoolManager(BSC / Base 同址) */
4
5
  export const PCS_INFINITY_CL_POOL_MANAGER_BSC = '0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b';
6
+ /** PancakeSwap Infinity Vault(全池代币记账;BSC / Base 同址) */
7
+ export const PCS_INFINITY_VAULT_BSC = '0x238a358808379702088667322f80aC48bAd5e6c4';
5
8
  /** Flap Portal 毕业到 Infinity CL 时使用的 hooks(BSC) */
6
9
  export const FLAP_INFINITY_HOOKS_BSC = '0xF1A9aA042454b8553bE3896597ff11a0f011c1c1';
7
10
  const CL_POOL_MANAGER_ABI = [
8
11
  'function getSlot0(bytes32 id) view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)',
9
12
  'function getLiquidity(bytes32 id) view returns (uint128 liquidity)',
10
13
  ];
14
+ const V3_PAIR_ABI = [
15
+ 'function slot0() view returns (uint160 sqrtPriceX96,int24 tick,uint16 observationIndex,uint16 observationCardinality,uint16 observationCardinalityNext,uint8 feeProtocol,bool unlocked)',
16
+ 'function token0() view returns (address)',
17
+ 'function token1() view returns (address)',
18
+ ];
11
19
  /** BSC:lpFeeProfile → Infinity PoolKey 的 fee / parameters(与链上 Initialize 一致) */
12
20
  const BSC_INFINITY_POOL_KEY_BY_PROFILE = {
13
21
  0: {
@@ -31,7 +39,7 @@ export function buildFlapInfinityPoolKey(params) {
31
39
  const quote = params.quoteTokenAddress &&
32
40
  params.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
33
41
  ? params.quoteTokenAddress
34
- : '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; // WBNB
42
+ : '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
35
43
  const [currency0, currency1] = sortInfinityCurrencies(params.tokenAddress, quote);
36
44
  return {
37
45
  currency0,
@@ -42,6 +50,32 @@ export function buildFlapInfinityPoolKey(params) {
42
50
  parameters: meta.parameters,
43
51
  };
44
52
  }
53
+ /** 判断 getTokenV7.pool 是否为 Infinity CL PoolManager(而非 V3 pair) */
54
+ export function isInfinityCLPoolManagerAddress(pool, chain) {
55
+ if (!pool)
56
+ return false;
57
+ const c = String(chain || '').toUpperCase();
58
+ if (c === 'BSC' || c === 'BASE') {
59
+ return pool.toLowerCase() === PCS_INFINITY_CL_POOL_MANAGER_BSC.toLowerCase();
60
+ }
61
+ return false;
62
+ }
63
+ /** 仅 V7 + Infinity CL 才走 Infinity 专用逻辑 */
64
+ export function isFlapInfinityCLGraduated(state, chain) {
65
+ if (isInfinityCLPoolManagerAddress(state.pool ?? '', chain))
66
+ return true;
67
+ return Number(state.tokenVersion ?? 0) === 7;
68
+ }
69
+ /** Portal quoteExactInput:链上 quoteToken 为 0 时必须用 native(0),不能用 WBNB */
70
+ export function resolvePortalQuoteInputToken(quoteTokenAddress, wrappedNativeAddress) {
71
+ if (!quoteTokenAddress || quoteTokenAddress.toLowerCase() === ZERO_ADDRESS) {
72
+ return ZERO_ADDRESS;
73
+ }
74
+ if (quoteTokenAddress.toLowerCase() === wrappedNativeAddress.toLowerCase()) {
75
+ return ZERO_ADDRESS;
76
+ }
77
+ return quoteTokenAddress;
78
+ }
45
79
  function formatRatio(n, d, precision = 18) {
46
80
  if (d === 0n)
47
81
  return '0';
@@ -52,7 +86,6 @@ function formatRatio(n, d, precision = 18) {
52
86
  const frac = s.slice(-precision).replace(/0+$/, '');
53
87
  return frac ? `${int}.${frac}` : int;
54
88
  }
55
- /** 由 slot0 计算 quote per token(与 memeweb V3 spot 公式一致) */
56
89
  export function infinitySpotPriceQuotePerToken(params) {
57
90
  const tokenLower = params.tokenAddress.toLowerCase();
58
91
  const quoteLower = params.quoteTokenAddress.toLowerCase();
@@ -80,32 +113,58 @@ export function infinitySpotPriceQuotePerToken(params) {
80
113
  }
81
114
  return formatRatio(n, d, 18);
82
115
  }
83
- /** 用活跃流动性 L 与 sqrtPrice 估算两侧虚拟储备(UI 展示用) */
84
- export function estimateInfinityReserves(params) {
85
- const Q96 = 2n ** 96n;
86
- if (params.liquidity === 0n || params.sqrtPriceX96 === 0n) {
87
- return { reserveToken: '0', reserveQuote: '0' };
116
+ function v3SpotPriceQuotePerToken(params) {
117
+ return infinitySpotPriceQuotePerToken({
118
+ sqrtPriceX96: params.sqrtPriceX96,
119
+ tokenAddress: params.tokenAddress,
120
+ currency0: params.token0,
121
+ currency1: params.token1,
122
+ quoteTokenAddress: params.quoteTokenAddress,
123
+ });
124
+ }
125
+ /** Infinity CL:用 quote 估算当前可卖出的 WBNB 深度(贴近 GMGN,勿把探测代币量当池子持仓) */
126
+ async function estimateInfinityPoolQuoteBnbDepth(portal, tokenAddress) {
127
+ const inputQuote = ZERO_ADDRESS;
128
+ const probeTokens = [110000000n, 100000000n, 50000000n, 10000000n].map((m) => m * 10n ** 18n);
129
+ for (const amt of probeTokens) {
130
+ try {
131
+ const bnbOut = await portal.quoteExactInput({
132
+ inputToken: tokenAddress,
133
+ outputToken: inputQuote,
134
+ inputAmount: amt,
135
+ });
136
+ if (bnbOut > 0n)
137
+ return formatEther(bnbOut);
138
+ }
139
+ catch {
140
+ /* try smaller */
141
+ }
88
142
  }
89
- // float 估算即可满足面板展示
90
- const sqrtP = Number(params.sqrtPriceX96) / Number(Q96);
91
- const L = Number(params.liquidity);
92
- if (!Number.isFinite(sqrtP) || sqrtP <= 0) {
93
- return { reserveToken: '0', reserveQuote: '0' };
143
+ return '0';
144
+ }
145
+ /**
146
+ * Infinity CL 池内代币量:读 Vault 中该 token 余额(与 GMGN 池子代币列一致)
147
+ * 注意:Vault 为全协议记账层,对 Flap 毕业主池通常即该交易对持仓。
148
+ */
149
+ async function getInfinityCLPoolTokenReserve(params) {
150
+ const chain = String(params.chain || '').toUpperCase();
151
+ const vault = chain === 'BSC' || chain === 'BASE' ? PCS_INFINITY_VAULT_BSC : '';
152
+ if (!vault)
153
+ return '0';
154
+ try {
155
+ const provider = new JsonRpcProvider(params.rpcUrl);
156
+ const bal = (await new Contract(params.tokenAddress, ERC20_ABI, provider).balanceOf(vault));
157
+ return formatEther(bal);
94
158
  }
95
- const amount1 = (L * sqrtP) / 1e18;
96
- const amount0 = L / sqrtP / 1e18;
97
- if (params.tokenIsCurrency0) {
98
- return { reserveToken: String(amount0), reserveQuote: String(amount1) };
159
+ catch {
160
+ return '0';
99
161
  }
100
- return { reserveToken: String(amount1), reserveQuote: String(amount0) };
101
162
  }
102
- async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress) {
163
+ async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress, wrappedNativeAddress) {
103
164
  try {
104
- const buyAmt = 10n ** 16n; // 0.01 native
105
- const sellAmt = 10n ** 21n; // 1000 tokens
106
- const inputQuote = quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
107
- ? ZERO_ADDRESS
108
- : quoteTokenAddress;
165
+ const buyAmt = 10n ** 16n;
166
+ const sellAmt = 10n ** 21n;
167
+ const inputQuote = resolvePortalQuoteInputToken(quoteTokenAddress, wrappedNativeAddress);
109
168
  const [tokensOut, quoteOut] = await Promise.all([
110
169
  portal.quoteExactInput({
111
170
  inputToken: inputQuote,
@@ -116,11 +175,13 @@ async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress) {
116
175
  inputToken: tokenAddress,
117
176
  outputToken: inputQuote,
118
177
  inputAmount: sellAmt,
119
- }),
178
+ }).catch(() => 0n),
120
179
  ]);
121
- if (tokensOut <= 0n || quoteOut <= 0n)
180
+ if (tokensOut <= 0n)
122
181
  return null;
123
182
  const buyPrice = Number(formatEther(buyAmt)) / Number(formatEther(tokensOut));
183
+ if (quoteOut <= 0n)
184
+ return String(buyPrice);
124
185
  const sellPrice = Number(formatEther(quoteOut)) / Number(formatEther(sellAmt));
125
186
  return String((buyPrice + sellPrice) / 2);
126
187
  }
@@ -128,31 +189,67 @@ async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress) {
128
189
  return null;
129
190
  }
130
191
  }
131
- /**
132
- * Flap 毕业后(status=4)外盘:PCS Infinity CL + Portal quote
133
- * getTokenV7().pool V7 标准币上为 CLPoolManager 合约地址,非 V3 pair。
134
- */
135
- export async function getFlapGraduatedDexMetrics(params) {
136
- const tokenAddress = params.tokenAddress;
137
- const quoteToken = params.state.quoteTokenAddress &&
138
- params.state.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
139
- ? params.state.quoteTokenAddress
140
- : params.wrappedNativeAddress ?? '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
141
- const progressRaw = params.state.progress ?? 0n;
142
- const progress = progressRaw > 10n ** 18n
143
- ? formatUnits(progressRaw, 18)
144
- : progressRaw >= 100n
145
- ? '100'
146
- : String(progressRaw);
147
- let price = await quotePortalPrice(params.portal, tokenAddress, quoteToken);
192
+ function formatProgress(state) {
193
+ const progressRaw = state.progress ?? 0n;
194
+ if (progressRaw > 10n ** 18n)
195
+ return formatUnits(progressRaw, 18);
196
+ if (progressRaw >= 100n)
197
+ return '100';
198
+ return String(progressRaw);
199
+ }
200
+ async function getGraduatedV3PairMetrics(params) {
201
+ try {
202
+ const provider = new JsonRpcProvider(params.rpcUrl);
203
+ const pool = new Contract(params.poolAddress, V3_PAIR_ABI, provider);
204
+ const [t0, t1, slot0] = await Promise.all([
205
+ pool.token0(),
206
+ pool.token1(),
207
+ pool.slot0(),
208
+ ]);
209
+ const sqrtPriceX96 = BigInt(Array.isArray(slot0) ? slot0[0] : slot0.sqrtPriceX96);
210
+ const tokenLower = params.tokenAddress.toLowerCase();
211
+ const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
212
+ ? params.wrappedNativeAddress
213
+ : params.quoteTokenAddress;
214
+ const spot = v3SpotPriceQuotePerToken({
215
+ sqrtPriceX96,
216
+ tokenAddress: params.tokenAddress,
217
+ token0: t0,
218
+ token1: t1,
219
+ quoteTokenAddress: quoteAddr,
220
+ });
221
+ const price = spot ??
222
+ (await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress));
223
+ const bal0 = (await new Contract(t0, ERC20_ABI, provider).balanceOf(params.poolAddress));
224
+ const bal1 = (await new Contract(t1, ERC20_ABI, provider).balanceOf(params.poolAddress));
225
+ const tokenIs0 = t0.toLowerCase() === tokenLower;
226
+ const poolTokenAmount = formatEther(tokenIs0 ? bal0 : bal1);
227
+ const poolBNBAmount = formatEther(tokenIs0 ? bal1 : bal0);
228
+ if (!price && poolTokenAmount === '0' && poolBNBAmount === '0')
229
+ return null;
230
+ return {
231
+ price: price ?? '0',
232
+ progress: '100',
233
+ poolBNBAmount,
234
+ poolTokenAmount,
235
+ dexKind: 'PANCAKE_V3',
236
+ };
237
+ }
238
+ catch {
239
+ return null;
240
+ }
241
+ }
242
+ async function getInfinityCLGraduatedMetrics(params) {
243
+ const progress = formatProgress(params.state);
244
+ let price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
148
245
  let poolBNBAmount = '0';
149
246
  let poolTokenAmount = '0';
150
247
  let poolId;
151
248
  let infinityFee;
152
249
  const poolKey = buildFlapInfinityPoolKey({
153
- tokenAddress,
154
- quoteTokenAddress: quoteToken,
155
- chain: 'BSC',
250
+ tokenAddress: params.tokenAddress,
251
+ quoteTokenAddress: params.quoteTokenAddress,
252
+ chain: params.chain,
156
253
  lpFeeProfile: params.state.lpFeeProfile,
157
254
  });
158
255
  if (poolKey) {
@@ -161,48 +258,41 @@ export async function getFlapGraduatedDexMetrics(params) {
161
258
  try {
162
259
  const provider = new JsonRpcProvider(params.rpcUrl);
163
260
  const manager = new Contract(poolKey.poolManager, CL_POOL_MANAGER_ABI, provider);
164
- const [slot0, liquidity] = await Promise.all([
165
- manager.getSlot0(poolId),
166
- manager.getLiquidity(poolId),
167
- ]);
261
+ const [slot0] = await Promise.all([manager.getSlot0(poolId)]);
168
262
  const sqrtPriceX96 = BigInt(slot0[0]);
169
- const liq = BigInt(liquidity);
170
263
  if (sqrtPriceX96 > 0n) {
171
- const tokenIs0 = poolKey.currency0.toLowerCase() === tokenAddress.toLowerCase();
264
+ const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
265
+ ? params.wrappedNativeAddress
266
+ : params.quoteTokenAddress;
172
267
  const spot = infinitySpotPriceQuotePerToken({
173
268
  sqrtPriceX96,
174
- tokenAddress,
269
+ tokenAddress: params.tokenAddress,
175
270
  currency0: poolKey.currency0,
176
271
  currency1: poolKey.currency1,
177
- quoteTokenAddress: quoteToken,
272
+ quoteTokenAddress: quoteAddr,
178
273
  });
179
274
  if (spot)
180
275
  price = spot;
181
- const reserves = estimateInfinityReserves({
182
- liquidity: liq,
183
- sqrtPriceX96,
184
- tokenIsCurrency0: tokenIs0,
185
- });
186
- poolBNBAmount = reserves.reserveQuote;
187
- poolTokenAmount = reserves.reserveToken;
188
276
  }
189
277
  }
190
278
  catch {
191
- // slot0 失败时保留 portal quote 价格
279
+ /* keep portal quote */
192
280
  }
193
281
  }
282
+ const [bnbDepth, tokenReserve] = await Promise.all([
283
+ estimateInfinityPoolQuoteBnbDepth(params.portal, params.tokenAddress),
284
+ getInfinityCLPoolTokenReserve({
285
+ tokenAddress: params.tokenAddress,
286
+ rpcUrl: params.rpcUrl,
287
+ chain: params.chain,
288
+ }),
289
+ ]);
290
+ if (parseFloat(bnbDepth) > 0)
291
+ poolBNBAmount = bnbDepth;
292
+ if (parseFloat(tokenReserve) > 0)
293
+ poolTokenAmount = tokenReserve;
194
294
  if (!price)
195
295
  price = '0';
196
- if (poolTokenAmount === '0' || poolTokenAmount === '0.0') {
197
- try {
198
- const circ = formatEther(params.state.circulatingSupply ?? 0n);
199
- const total = formatEther(FLAP_TOTAL_SUPPLY);
200
- poolTokenAmount = String(Math.max(0, Number(total) - Number(circ)));
201
- }
202
- catch {
203
- poolTokenAmount = '0';
204
- }
205
- }
206
296
  return {
207
297
  price,
208
298
  progress,
@@ -210,15 +300,53 @@ export async function getFlapGraduatedDexMetrics(params) {
210
300
  poolTokenAmount,
211
301
  poolId,
212
302
  infinityFee,
303
+ dexKind: 'PCS_INFINITY_CL',
213
304
  };
214
305
  }
215
- /** 判断 getTokenV7.pool 是否为 Infinity CL PoolManager(而非 V3 pair) */
216
- export function isInfinityCLPoolManagerAddress(pool, chain) {
217
- if (!pool)
218
- return false;
219
- const c = String(chain || '').toUpperCase();
220
- if (c === 'BSC' || c === 'BASE') {
221
- return pool.toLowerCase() === PCS_INFINITY_CL_POOL_MANAGER_BSC.toLowerCase();
306
+ /**
307
+ * Flap 毕业后(status=4)外盘统一入口:
308
+ * - V7 / Infinity CL → Infinity + Portal quote 深度
309
+ * - 旧版 V3/V2(pool 为 pair 地址)→ 读 pair 余额 + slot0
310
+ * - 否则仅 Portal quote
311
+ */
312
+ export async function getFlapGraduatedDexMetrics(params) {
313
+ const chain = String(params.chain || 'BSC').toUpperCase();
314
+ const wrappedNative = params.wrappedNativeAddress ?? '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
315
+ const quoteToken = params.state.quoteTokenAddress &&
316
+ params.state.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
317
+ ? params.state.quoteTokenAddress
318
+ : wrappedNative;
319
+ const progress = formatProgress(params.state);
320
+ if (isFlapInfinityCLGraduated(params.state, chain)) {
321
+ return getInfinityCLGraduatedMetrics({
322
+ portal: params.portal,
323
+ tokenAddress: params.tokenAddress,
324
+ state: params.state,
325
+ rpcUrl: params.rpcUrl,
326
+ quoteTokenAddress: quoteToken,
327
+ wrappedNativeAddress: wrappedNative,
328
+ chain,
329
+ });
222
330
  }
223
- return false;
331
+ const pool = params.state.pool;
332
+ if (pool && pool.toLowerCase() !== ZERO_ADDRESS && !isInfinityCLPoolManagerAddress(pool, chain)) {
333
+ const v3 = await getGraduatedV3PairMetrics({
334
+ rpcUrl: params.rpcUrl,
335
+ tokenAddress: params.tokenAddress,
336
+ poolAddress: pool,
337
+ quoteTokenAddress: quoteToken,
338
+ wrappedNativeAddress: wrappedNative,
339
+ portal: params.portal,
340
+ });
341
+ if (v3)
342
+ return v3;
343
+ }
344
+ const price = await quotePortalPrice(params.portal, params.tokenAddress, quoteToken, wrappedNative);
345
+ return {
346
+ price: price ?? '0',
347
+ progress,
348
+ poolBNBAmount: '0',
349
+ poolTokenAmount: '0',
350
+ dexKind: 'PORTAL_QUOTE',
351
+ };
224
352
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "2.2.13",
3
+ "version": "2.2.15",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",