four-flap-meme-sdk 2.2.11 → 2.2.13
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 +1 -0
- package/dist/index.js +1 -0
- package/dist/shared/flap/graduated-dex.d.ts +1 -0
- package/dist/shared/flap/graduated-dex.js +1 -0
- package/dist/shared/flap/portal-bundle-merkle/core.js +21 -11
- package/dist/utils/lp-inspect.js +56 -1
- package/dist/utils/pcs-infinity-cl.d.ts +62 -0
- package/dist/utils/pcs-infinity-cl.js +224 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +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
19
|
export { uploadTokenMeta, type TokenMetaInput } from './shared/flap/ipfs.js';
|
|
19
20
|
export { buildPermitPiggybackAuto } from './shared/flap/permit.js';
|
|
20
21
|
export { predictVanityTokenAddressByChain, findSaltEndingByChain } from './shared/flap/vanity.js';
|
package/dist/index.js
CHANGED
|
@@ -48,6 +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
52
|
export { uploadTokenMeta } from './shared/flap/ipfs.js';
|
|
52
53
|
export { buildPermitPiggybackAuto } from './shared/flap/permit.js';
|
|
53
54
|
export { predictVanityTokenAddressByChain, findSaltEndingByChain } from './shared/flap/vanity.js';
|
|
@@ -0,0 +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';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getFlapGraduatedDexMetrics, buildFlapInfinityPoolKey, computeInfinityPoolId, isInfinityCLPoolManagerAddress, PCS_INFINITY_CL_POOL_MANAGER_BSC, FLAP_INFINITY_HOOKS_BSC, } from '../../utils/pcs-infinity-cl.js';
|
|
@@ -158,8 +158,13 @@ export async function createTokenWithBundleBuyMerkle(params) {
|
|
|
158
158
|
profitTokenAmount = totalProfit / divisor;
|
|
159
159
|
}
|
|
160
160
|
// ✅ 优化:并行获取 unsignedBuys 和 buyerNonces
|
|
161
|
+
// TOKEN_V3_PERMIT / newTokenV7 内盘买入须 swapExactInputV3(与 create-to-dex 一致)
|
|
162
|
+
const usePortalSwapV3 = useV7Portal ||
|
|
163
|
+
(params.lpFeeProfile !== undefined && !normalizedTax) ||
|
|
164
|
+
(!!params.extensionID &&
|
|
165
|
+
params.extensionID !== '0x' + '00'.repeat(32));
|
|
161
166
|
const [unsignedBuys, buyerNonces] = await Promise.all([
|
|
162
|
-
populateBuyTransactionsWithQuote(buyers, portalAddr, tokenAddress, adjustedFundsList, inputToken, useNativeToken),
|
|
167
|
+
populateBuyTransactionsWithQuote(buyers, portalAddr, tokenAddress, adjustedFundsList, inputToken, useNativeToken, usePortalSwapV3, params.extensionData ?? '0x'),
|
|
163
168
|
allocateBuyerNonces(buyers, extractProfit, maxFundsIndex, useNativeToken ? totalProfit : profitTokenAmount, nonceManager)
|
|
164
169
|
]);
|
|
165
170
|
// ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
|
|
@@ -667,17 +672,22 @@ function findMaxIndex(values) {
|
|
|
667
672
|
* ✅ 支持 quoteToken 的买入交易构建
|
|
668
673
|
* 当 inputToken 为非零地址(如 USDC)时,value 为 0
|
|
669
674
|
*/
|
|
670
|
-
async function populateBuyTransactionsWithQuote(buyers, portalAddr, tokenAddress, fundsList, inputToken, useNativeToken) {
|
|
675
|
+
async function populateBuyTransactionsWithQuote(buyers, portalAddr, tokenAddress, fundsList, inputToken, useNativeToken, usePortalSwapV3 = false, extensionData = '0x') {
|
|
671
676
|
const portals = buyers.map(wallet => new ethers.Contract(portalAddr, PORTAL_ABI, wallet));
|
|
672
|
-
return await Promise.all(portals.map((portal, i) =>
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
677
|
+
return await Promise.all(portals.map((portal, i) => {
|
|
678
|
+
const swapParams = {
|
|
679
|
+
inputToken,
|
|
680
|
+
outputToken: tokenAddress,
|
|
681
|
+
inputAmount: fundsList[i],
|
|
682
|
+
minOutputAmount: 0n,
|
|
683
|
+
permitData: '0x',
|
|
684
|
+
};
|
|
685
|
+
const overrides = useNativeToken ? { value: fundsList[i] } : {};
|
|
686
|
+
if (usePortalSwapV3) {
|
|
687
|
+
return portal.swapExactInputV3.populateTransaction({ ...swapParams, extensionData: extensionData ?? '0x' }, overrides);
|
|
688
|
+
}
|
|
689
|
+
return portal.swapExactInput.populateTransaction(swapParams, overrides);
|
|
690
|
+
}));
|
|
681
691
|
}
|
|
682
692
|
function buildGasLimitList(length, config) {
|
|
683
693
|
const gasLimit = getGasLimit(config);
|
package/dist/utils/lp-inspect.js
CHANGED
|
@@ -3,6 +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
7
|
// ============================================================================
|
|
7
8
|
// 链配置
|
|
8
9
|
// ============================================================================
|
|
@@ -360,7 +361,61 @@ export async function inspectTokenLP(token, opts) {
|
|
|
360
361
|
if (opts.debug)
|
|
361
362
|
console.log('[LP Inspect] Flap V7 failed, using V5');
|
|
362
363
|
}
|
|
363
|
-
if (st && st.status !== undefined
|
|
364
|
+
if (st && st.status !== undefined) {
|
|
365
|
+
const status = Number(st.status);
|
|
366
|
+
// ✅ 已毕业:PCS Infinity CL 外盘(Portal pool 字段为 CLPoolManager)
|
|
367
|
+
if (status === 4) {
|
|
368
|
+
const quoteAddr = st.quoteTokenAddress && st.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
|
|
369
|
+
? st.quoteTokenAddress
|
|
370
|
+
: chainConfig.wrappedNative;
|
|
371
|
+
let quoteDecimals = 18;
|
|
372
|
+
let quoteSymbol = chainConfig.wrappedNativeSymbol;
|
|
373
|
+
if (quoteAddr.toLowerCase() !== chainConfig.wrappedNative.toLowerCase()) {
|
|
374
|
+
quoteDecimals = await getTokenDecimals(quoteAddr, provider);
|
|
375
|
+
const stableCoin = chainConfig.stableCoins.find(c => c.address.toLowerCase() === quoteAddr.toLowerCase());
|
|
376
|
+
quoteSymbol = stableCoin?.symbol || 'TOKEN';
|
|
377
|
+
}
|
|
378
|
+
const metrics = await getFlapGraduatedDexMetrics({
|
|
379
|
+
portal: flap,
|
|
380
|
+
tokenAddress: token,
|
|
381
|
+
state: st,
|
|
382
|
+
rpcUrl: opts.rpcUrl,
|
|
383
|
+
wrappedNativeAddress: chainConfig.wrappedNative,
|
|
384
|
+
});
|
|
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;
|
|
418
|
+
}
|
|
364
419
|
result.platform = 'FLAP';
|
|
365
420
|
let quoteDecimals = 18;
|
|
366
421
|
let quoteSymbol = chainConfig.wrappedNativeSymbol;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { FlapPortal } from '../shared/flap/portal.js';
|
|
2
|
+
import type { TokenStateV7 } from '../shared/flap/portal.js';
|
|
3
|
+
/** PancakeSwap Infinity CL PoolManager(BSC / Base 同址) */
|
|
4
|
+
export declare const PCS_INFINITY_CL_POOL_MANAGER_BSC = "0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b";
|
|
5
|
+
/** Flap Portal 毕业到 Infinity CL 时使用的 hooks(BSC) */
|
|
6
|
+
export declare const FLAP_INFINITY_HOOKS_BSC = "0xF1A9aA042454b8553bE3896597ff11a0f011c1c1";
|
|
7
|
+
export type InfinityPoolKey = {
|
|
8
|
+
currency0: string;
|
|
9
|
+
currency1: string;
|
|
10
|
+
hooks: string;
|
|
11
|
+
poolManager: string;
|
|
12
|
+
fee: number;
|
|
13
|
+
parameters: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function sortInfinityCurrencies(a: string, b: string): [string, string];
|
|
16
|
+
export declare function computeInfinityPoolId(key: InfinityPoolKey): string;
|
|
17
|
+
export declare function buildFlapInfinityPoolKey(params: {
|
|
18
|
+
tokenAddress: string;
|
|
19
|
+
quoteTokenAddress: string;
|
|
20
|
+
chain: string;
|
|
21
|
+
lpFeeProfile?: number;
|
|
22
|
+
}): InfinityPoolKey | null;
|
|
23
|
+
/** 由 slot0 计算 quote per token(与 memeweb V3 spot 公式一致) */
|
|
24
|
+
export declare function infinitySpotPriceQuotePerToken(params: {
|
|
25
|
+
sqrtPriceX96: bigint;
|
|
26
|
+
tokenAddress: string;
|
|
27
|
+
currency0: string;
|
|
28
|
+
currency1: string;
|
|
29
|
+
quoteTokenAddress: string;
|
|
30
|
+
tokenDecimals?: number;
|
|
31
|
+
quoteDecimals?: number;
|
|
32
|
+
}): 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
|
+
/**
|
|
51
|
+
* Flap 毕业后(status=4)外盘:PCS Infinity CL + Portal quote
|
|
52
|
+
* getTokenV7().pool 在 V7 标准币上为 CLPoolManager 合约地址,非 V3 pair。
|
|
53
|
+
*/
|
|
54
|
+
export declare function getFlapGraduatedDexMetrics(params: {
|
|
55
|
+
portal: FlapPortal;
|
|
56
|
+
tokenAddress: string;
|
|
57
|
+
state: TokenStateV7;
|
|
58
|
+
rpcUrl: string;
|
|
59
|
+
wrappedNativeAddress?: string;
|
|
60
|
+
}): Promise<GraduatedDexMetrics>;
|
|
61
|
+
/** 判断 getTokenV7.pool 是否为 Infinity CL PoolManager(而非 V3 pair) */
|
|
62
|
+
export declare function isInfinityCLPoolManagerAddress(pool: string, chain: string): boolean;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { AbiCoder, Contract, JsonRpcProvider, formatEther, formatUnits, keccak256 } from 'ethers';
|
|
2
|
+
import { FLAP_TOTAL_SUPPLY, ZERO_ADDRESS } from '../shared/flap/constants.js';
|
|
3
|
+
/** PancakeSwap Infinity CL PoolManager(BSC / Base 同址) */
|
|
4
|
+
export const PCS_INFINITY_CL_POOL_MANAGER_BSC = '0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b';
|
|
5
|
+
/** Flap Portal 毕业到 Infinity CL 时使用的 hooks(BSC) */
|
|
6
|
+
export const FLAP_INFINITY_HOOKS_BSC = '0xF1A9aA042454b8553bE3896597ff11a0f011c1c1';
|
|
7
|
+
const CL_POOL_MANAGER_ABI = [
|
|
8
|
+
'function getSlot0(bytes32 id) view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)',
|
|
9
|
+
'function getLiquidity(bytes32 id) view returns (uint128 liquidity)',
|
|
10
|
+
];
|
|
11
|
+
/** BSC:lpFeeProfile → Infinity PoolKey 的 fee / parameters(与链上 Initialize 一致) */
|
|
12
|
+
const BSC_INFINITY_POOL_KEY_BY_PROFILE = {
|
|
13
|
+
0: {
|
|
14
|
+
fee: 8534,
|
|
15
|
+
parameters: '0x0000000000000000000000000000000000000000000000000000000000fa00d5',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export function sortInfinityCurrencies(a, b) {
|
|
19
|
+
return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
|
|
20
|
+
}
|
|
21
|
+
export function computeInfinityPoolId(key) {
|
|
22
|
+
const coder = AbiCoder.defaultAbiCoder();
|
|
23
|
+
return keccak256(coder.encode(['address', 'address', 'address', 'address', 'uint24', 'bytes32'], [key.currency0, key.currency1, key.hooks, key.poolManager, key.fee, key.parameters]));
|
|
24
|
+
}
|
|
25
|
+
export function buildFlapInfinityPoolKey(params) {
|
|
26
|
+
const chain = String(params.chain || '').toUpperCase();
|
|
27
|
+
if (chain !== 'BSC')
|
|
28
|
+
return null;
|
|
29
|
+
const meta = BSC_INFINITY_POOL_KEY_BY_PROFILE[Number(params.lpFeeProfile ?? 0)] ??
|
|
30
|
+
BSC_INFINITY_POOL_KEY_BY_PROFILE[0];
|
|
31
|
+
const quote = params.quoteTokenAddress &&
|
|
32
|
+
params.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
|
|
33
|
+
? params.quoteTokenAddress
|
|
34
|
+
: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; // WBNB
|
|
35
|
+
const [currency0, currency1] = sortInfinityCurrencies(params.tokenAddress, quote);
|
|
36
|
+
return {
|
|
37
|
+
currency0,
|
|
38
|
+
currency1,
|
|
39
|
+
hooks: FLAP_INFINITY_HOOKS_BSC,
|
|
40
|
+
poolManager: PCS_INFINITY_CL_POOL_MANAGER_BSC,
|
|
41
|
+
fee: meta.fee,
|
|
42
|
+
parameters: meta.parameters,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function formatRatio(n, d, precision = 18) {
|
|
46
|
+
if (d === 0n)
|
|
47
|
+
return '0';
|
|
48
|
+
const scale = 10n ** BigInt(precision);
|
|
49
|
+
const x = (n * scale) / d;
|
|
50
|
+
const s = x.toString().padStart(precision + 1, '0');
|
|
51
|
+
const int = s.slice(0, -precision);
|
|
52
|
+
const frac = s.slice(-precision).replace(/0+$/, '');
|
|
53
|
+
return frac ? `${int}.${frac}` : int;
|
|
54
|
+
}
|
|
55
|
+
/** 由 slot0 计算 quote per token(与 memeweb V3 spot 公式一致) */
|
|
56
|
+
export function infinitySpotPriceQuotePerToken(params) {
|
|
57
|
+
const tokenLower = params.tokenAddress.toLowerCase();
|
|
58
|
+
const quoteLower = params.quoteTokenAddress.toLowerCase();
|
|
59
|
+
const tokenIs0 = params.currency0.toLowerCase() === tokenLower;
|
|
60
|
+
const tokenIs1 = params.currency1.toLowerCase() === tokenLower;
|
|
61
|
+
const quoteIs0 = params.currency0.toLowerCase() === quoteLower;
|
|
62
|
+
const quoteIs1 = params.currency1.toLowerCase() === quoteLower;
|
|
63
|
+
if ((!tokenIs0 && !tokenIs1) || (!quoteIs0 && !quoteIs1))
|
|
64
|
+
return null;
|
|
65
|
+
const dec0 = params.tokenDecimals ?? 18;
|
|
66
|
+
const dec1 = params.quoteDecimals ?? 18;
|
|
67
|
+
const tokenDec = tokenIs0 ? dec0 : dec1;
|
|
68
|
+
const quoteDec = quoteIs0 ? dec0 : dec1;
|
|
69
|
+
const Q192 = 2n ** 192n;
|
|
70
|
+
const num = params.sqrtPriceX96 * params.sqrtPriceX96;
|
|
71
|
+
let n;
|
|
72
|
+
let d;
|
|
73
|
+
if (tokenIs0) {
|
|
74
|
+
n = num * 10n ** BigInt(tokenDec);
|
|
75
|
+
d = Q192 * 10n ** BigInt(quoteDec);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
n = Q192 * 10n ** BigInt(quoteDec);
|
|
79
|
+
d = num * 10n ** BigInt(tokenDec);
|
|
80
|
+
}
|
|
81
|
+
return formatRatio(n, d, 18);
|
|
82
|
+
}
|
|
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' };
|
|
88
|
+
}
|
|
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' };
|
|
94
|
+
}
|
|
95
|
+
const amount1 = (L * sqrtP) / 1e18;
|
|
96
|
+
const amount0 = L / sqrtP / 1e18;
|
|
97
|
+
if (params.tokenIsCurrency0) {
|
|
98
|
+
return { reserveToken: String(amount0), reserveQuote: String(amount1) };
|
|
99
|
+
}
|
|
100
|
+
return { reserveToken: String(amount1), reserveQuote: String(amount0) };
|
|
101
|
+
}
|
|
102
|
+
async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress) {
|
|
103
|
+
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;
|
|
109
|
+
const [tokensOut, quoteOut] = await Promise.all([
|
|
110
|
+
portal.quoteExactInput({
|
|
111
|
+
inputToken: inputQuote,
|
|
112
|
+
outputToken: tokenAddress,
|
|
113
|
+
inputAmount: buyAmt,
|
|
114
|
+
}),
|
|
115
|
+
portal.quoteExactInput({
|
|
116
|
+
inputToken: tokenAddress,
|
|
117
|
+
outputToken: inputQuote,
|
|
118
|
+
inputAmount: sellAmt,
|
|
119
|
+
}),
|
|
120
|
+
]);
|
|
121
|
+
if (tokensOut <= 0n || quoteOut <= 0n)
|
|
122
|
+
return null;
|
|
123
|
+
const buyPrice = Number(formatEther(buyAmt)) / Number(formatEther(tokensOut));
|
|
124
|
+
const sellPrice = Number(formatEther(quoteOut)) / Number(formatEther(sellAmt));
|
|
125
|
+
return String((buyPrice + sellPrice) / 2);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
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);
|
|
148
|
+
let poolBNBAmount = '0';
|
|
149
|
+
let poolTokenAmount = '0';
|
|
150
|
+
let poolId;
|
|
151
|
+
let infinityFee;
|
|
152
|
+
const poolKey = buildFlapInfinityPoolKey({
|
|
153
|
+
tokenAddress,
|
|
154
|
+
quoteTokenAddress: quoteToken,
|
|
155
|
+
chain: 'BSC',
|
|
156
|
+
lpFeeProfile: params.state.lpFeeProfile,
|
|
157
|
+
});
|
|
158
|
+
if (poolKey) {
|
|
159
|
+
poolId = computeInfinityPoolId(poolKey);
|
|
160
|
+
infinityFee = poolKey.fee;
|
|
161
|
+
try {
|
|
162
|
+
const provider = new JsonRpcProvider(params.rpcUrl);
|
|
163
|
+
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
|
+
]);
|
|
168
|
+
const sqrtPriceX96 = BigInt(slot0[0]);
|
|
169
|
+
const liq = BigInt(liquidity);
|
|
170
|
+
if (sqrtPriceX96 > 0n) {
|
|
171
|
+
const tokenIs0 = poolKey.currency0.toLowerCase() === tokenAddress.toLowerCase();
|
|
172
|
+
const spot = infinitySpotPriceQuotePerToken({
|
|
173
|
+
sqrtPriceX96,
|
|
174
|
+
tokenAddress,
|
|
175
|
+
currency0: poolKey.currency0,
|
|
176
|
+
currency1: poolKey.currency1,
|
|
177
|
+
quoteTokenAddress: quoteToken,
|
|
178
|
+
});
|
|
179
|
+
if (spot)
|
|
180
|
+
price = spot;
|
|
181
|
+
const reserves = estimateInfinityReserves({
|
|
182
|
+
liquidity: liq,
|
|
183
|
+
sqrtPriceX96,
|
|
184
|
+
tokenIsCurrency0: tokenIs0,
|
|
185
|
+
});
|
|
186
|
+
poolBNBAmount = reserves.reserveQuote;
|
|
187
|
+
poolTokenAmount = reserves.reserveToken;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// slot0 失败时保留 portal quote 价格
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!price)
|
|
195
|
+
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
|
+
return {
|
|
207
|
+
price,
|
|
208
|
+
progress,
|
|
209
|
+
poolBNBAmount,
|
|
210
|
+
poolTokenAmount,
|
|
211
|
+
poolId,
|
|
212
|
+
infinityFee,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
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();
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|