four-flap-meme-sdk 2.2.15 → 2.2.17

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.
@@ -122,25 +122,39 @@ function v3SpotPriceQuotePerToken(params) {
122
122
  quoteTokenAddress: params.quoteTokenAddress,
123
123
  });
124
124
  }
125
- /** Infinity CL:用 quote 估算当前可卖出的 WBNB 深度(贴近 GMGN,勿把探测代币量当池子持仓) */
125
+ const providerByRpc = new Map();
126
+ const gradMetricsCache = new Map();
127
+ const GRAD_METRICS_TTL_MS = 2500;
128
+ function getCachedRpcProvider(rpcUrl) {
129
+ let p = providerByRpc.get(rpcUrl);
130
+ if (!p) {
131
+ p = new JsonRpcProvider(rpcUrl);
132
+ providerByRpc.set(rpcUrl, p);
133
+ }
134
+ return p;
135
+ }
136
+ /**
137
+ * Infinity CL 池子 WBNB:取多档卖出 quote 的最大值(贴近 GMGN「池内 WBNB」)
138
+ * 说明:不是 Vault 总 WBNB(全协议),也不是 L*sqrtP 虚拟储备(会高估到 ~5.8)。
139
+ */
126
140
  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) {
141
+ const probes = [107000000n, 105000000n, 100000000n, 90000000n].map((m) => m * 10n ** 18n);
142
+ let best = 0n;
143
+ for (const amt of probes) {
130
144
  try {
131
145
  const bnbOut = await portal.quoteExactInput({
132
146
  inputToken: tokenAddress,
133
- outputToken: inputQuote,
147
+ outputToken: ZERO_ADDRESS,
134
148
  inputAmount: amt,
135
149
  });
136
- if (bnbOut > 0n)
137
- return formatEther(bnbOut);
150
+ if (bnbOut > best)
151
+ best = bnbOut;
138
152
  }
139
153
  catch {
140
- /* try smaller */
154
+ /* 超过可成交上限会 revert,继续试更小档位 */
141
155
  }
142
156
  }
143
- return '0';
157
+ return best > 0n ? formatEther(best) : '0';
144
158
  }
145
159
  /**
146
160
  * Infinity CL 池内代币量:读 Vault 中该 token 余额(与 GMGN 池子代币列一致)
@@ -152,7 +166,7 @@ async function getInfinityCLPoolTokenReserve(params) {
152
166
  if (!vault)
153
167
  return '0';
154
168
  try {
155
- const provider = new JsonRpcProvider(params.rpcUrl);
169
+ const provider = params.provider ?? getCachedRpcProvider(params.rpcUrl);
156
170
  const bal = (await new Contract(params.tokenAddress, ERC20_ABI, provider).balanceOf(vault));
157
171
  return formatEther(bal);
158
172
  }
@@ -160,30 +174,19 @@ async function getInfinityCLPoolTokenReserve(params) {
160
174
  return '0';
161
175
  }
162
176
  }
177
+ /** 单笔 buy quote 估价(比买卖双 quote 少一次 RPC) */
163
178
  async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress, wrappedNativeAddress) {
164
179
  try {
165
180
  const buyAmt = 10n ** 16n;
166
- const sellAmt = 10n ** 21n;
167
181
  const inputQuote = resolvePortalQuoteInputToken(quoteTokenAddress, wrappedNativeAddress);
168
- const [tokensOut, quoteOut] = await Promise.all([
169
- portal.quoteExactInput({
170
- inputToken: inputQuote,
171
- outputToken: tokenAddress,
172
- inputAmount: buyAmt,
173
- }),
174
- portal.quoteExactInput({
175
- inputToken: tokenAddress,
176
- outputToken: inputQuote,
177
- inputAmount: sellAmt,
178
- }).catch(() => 0n),
179
- ]);
182
+ const tokensOut = await portal.quoteExactInput({
183
+ inputToken: inputQuote,
184
+ outputToken: tokenAddress,
185
+ inputAmount: buyAmt,
186
+ });
180
187
  if (tokensOut <= 0n)
181
188
  return null;
182
- const buyPrice = Number(formatEther(buyAmt)) / Number(formatEther(tokensOut));
183
- if (quoteOut <= 0n)
184
- return String(buyPrice);
185
- const sellPrice = Number(formatEther(quoteOut)) / Number(formatEther(sellAmt));
186
- return String((buyPrice + sellPrice) / 2);
189
+ return String(Number(formatEther(buyAmt)) / Number(formatEther(tokensOut)));
187
190
  }
188
191
  catch {
189
192
  return null;
@@ -199,18 +202,22 @@ function formatProgress(state) {
199
202
  }
200
203
  async function getGraduatedV3PairMetrics(params) {
201
204
  try {
202
- const provider = new JsonRpcProvider(params.rpcUrl);
205
+ const provider = getCachedRpcProvider(params.rpcUrl);
203
206
  const pool = new Contract(params.poolAddress, V3_PAIR_ABI, provider);
207
+ const tokenLower = params.tokenAddress.toLowerCase();
208
+ const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
209
+ ? params.wrappedNativeAddress
210
+ : params.quoteTokenAddress;
204
211
  const [t0, t1, slot0] = await Promise.all([
205
212
  pool.token0(),
206
213
  pool.token1(),
207
214
  pool.slot0(),
208
215
  ]);
216
+ const [bal0, bal1] = await Promise.all([
217
+ new Contract(t0, ERC20_ABI, provider).balanceOf(params.poolAddress),
218
+ new Contract(t1, ERC20_ABI, provider).balanceOf(params.poolAddress),
219
+ ]);
209
220
  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
221
  const spot = v3SpotPriceQuotePerToken({
215
222
  sqrtPriceX96,
216
223
  tokenAddress: params.tokenAddress,
@@ -218,13 +225,16 @@ async function getGraduatedV3PairMetrics(params) {
218
225
  token1: t1,
219
226
  quoteTokenAddress: quoteAddr,
220
227
  });
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
228
  const tokenIs0 = t0.toLowerCase() === tokenLower;
226
229
  const poolTokenAmount = formatEther(tokenIs0 ? bal0 : bal1);
227
230
  const poolBNBAmount = formatEther(tokenIs0 ? bal1 : bal0);
231
+ let price = spot;
232
+ if (!price && parseFloat(poolTokenAmount) > 0) {
233
+ price = String(parseFloat(poolBNBAmount) / parseFloat(poolTokenAmount));
234
+ }
235
+ if (!price) {
236
+ price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
237
+ }
228
238
  if (!price && poolTokenAmount === '0' && poolBNBAmount === '0')
229
239
  return null;
230
240
  return {
@@ -241,60 +251,65 @@ async function getGraduatedV3PairMetrics(params) {
241
251
  }
242
252
  async function getInfinityCLGraduatedMetrics(params) {
243
253
  const progress = formatProgress(params.state);
244
- let price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
254
+ const provider = getCachedRpcProvider(params.rpcUrl);
245
255
  let poolBNBAmount = '0';
246
256
  let poolTokenAmount = '0';
247
257
  let poolId;
248
258
  let infinityFee;
259
+ let price = null;
249
260
  const poolKey = buildFlapInfinityPoolKey({
250
261
  tokenAddress: params.tokenAddress,
251
262
  quoteTokenAddress: params.quoteTokenAddress,
252
263
  chain: params.chain,
253
264
  lpFeeProfile: params.state.lpFeeProfile,
254
265
  });
255
- if (poolKey) {
266
+ const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
267
+ ? params.wrappedNativeAddress
268
+ : params.quoteTokenAddress;
269
+ const slot0Promise = (async () => {
270
+ if (!poolKey)
271
+ return null;
256
272
  poolId = computeInfinityPoolId(poolKey);
257
273
  infinityFee = poolKey.fee;
258
274
  try {
259
- const provider = new JsonRpcProvider(params.rpcUrl);
260
275
  const manager = new Contract(poolKey.poolManager, CL_POOL_MANAGER_ABI, provider);
261
- const [slot0] = await Promise.all([manager.getSlot0(poolId)]);
262
- const sqrtPriceX96 = BigInt(slot0[0]);
263
- if (sqrtPriceX96 > 0n) {
264
- const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
265
- ? params.wrappedNativeAddress
266
- : params.quoteTokenAddress;
267
- const spot = infinitySpotPriceQuotePerToken({
268
- sqrtPriceX96,
269
- tokenAddress: params.tokenAddress,
270
- currency0: poolKey.currency0,
271
- currency1: poolKey.currency1,
272
- quoteTokenAddress: quoteAddr,
273
- });
274
- if (spot)
275
- price = spot;
276
- }
276
+ const slot0 = await manager.getSlot0(poolId);
277
+ return BigInt(slot0[0]);
277
278
  }
278
279
  catch {
279
- /* keep portal quote */
280
+ return null;
280
281
  }
281
- }
282
- const [bnbDepth, tokenReserve] = await Promise.all([
283
- estimateInfinityPoolQuoteBnbDepth(params.portal, params.tokenAddress),
282
+ })();
283
+ const [sqrtPriceX96, tokenReserve, bnbDepth] = await Promise.all([
284
+ slot0Promise,
284
285
  getInfinityCLPoolTokenReserve({
285
286
  tokenAddress: params.tokenAddress,
286
287
  rpcUrl: params.rpcUrl,
287
288
  chain: params.chain,
289
+ provider,
288
290
  }),
291
+ estimateInfinityPoolQuoteBnbDepth(params.portal, params.tokenAddress),
289
292
  ]);
290
- if (parseFloat(bnbDepth) > 0)
291
- poolBNBAmount = bnbDepth;
293
+ if (sqrtPriceX96 && sqrtPriceX96 > 0n && poolKey) {
294
+ const spot = infinitySpotPriceQuotePerToken({
295
+ sqrtPriceX96,
296
+ tokenAddress: params.tokenAddress,
297
+ currency0: poolKey.currency0,
298
+ currency1: poolKey.currency1,
299
+ quoteTokenAddress: quoteAddr,
300
+ });
301
+ if (spot)
302
+ price = spot;
303
+ }
292
304
  if (parseFloat(tokenReserve) > 0)
293
305
  poolTokenAmount = tokenReserve;
294
- if (!price)
295
- price = '0';
306
+ if (parseFloat(bnbDepth) > 0)
307
+ poolBNBAmount = bnbDepth;
308
+ if (!price) {
309
+ price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
310
+ }
296
311
  return {
297
- price,
312
+ price: price ?? '0',
298
313
  progress,
299
314
  poolBNBAmount,
300
315
  poolTokenAmount,
@@ -311,14 +326,20 @@ async function getInfinityCLGraduatedMetrics(params) {
311
326
  */
312
327
  export async function getFlapGraduatedDexMetrics(params) {
313
328
  const chain = String(params.chain || 'BSC').toUpperCase();
329
+ const tokenKey = `${chain}:${params.tokenAddress.toLowerCase()}`;
330
+ const cached = gradMetricsCache.get(tokenKey);
331
+ if (cached && Date.now() - cached.at < GRAD_METRICS_TTL_MS) {
332
+ return cached.data;
333
+ }
314
334
  const wrappedNative = params.wrappedNativeAddress ?? '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
315
335
  const quoteToken = params.state.quoteTokenAddress &&
316
336
  params.state.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
317
337
  ? params.state.quoteTokenAddress
318
338
  : wrappedNative;
319
339
  const progress = formatProgress(params.state);
340
+ let data;
320
341
  if (isFlapInfinityCLGraduated(params.state, chain)) {
321
- return getInfinityCLGraduatedMetrics({
342
+ data = await getInfinityCLGraduatedMetrics({
322
343
  portal: params.portal,
323
344
  tokenAddress: params.tokenAddress,
324
345
  state: params.state,
@@ -328,25 +349,42 @@ export async function getFlapGraduatedDexMetrics(params) {
328
349
  chain,
329
350
  });
330
351
  }
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;
352
+ else {
353
+ const pool = params.state.pool;
354
+ if (pool && pool.toLowerCase() !== ZERO_ADDRESS && !isInfinityCLPoolManagerAddress(pool, chain)) {
355
+ const v3 = await getGraduatedV3PairMetrics({
356
+ rpcUrl: params.rpcUrl,
357
+ tokenAddress: params.tokenAddress,
358
+ poolAddress: pool,
359
+ quoteTokenAddress: quoteToken,
360
+ wrappedNativeAddress: wrappedNative,
361
+ portal: params.portal,
362
+ });
363
+ if (v3) {
364
+ data = v3;
365
+ }
366
+ else {
367
+ const price = await quotePortalPrice(params.portal, params.tokenAddress, quoteToken, wrappedNative);
368
+ data = {
369
+ price: price ?? '0',
370
+ progress,
371
+ poolBNBAmount: '0',
372
+ poolTokenAmount: '0',
373
+ dexKind: 'PORTAL_QUOTE',
374
+ };
375
+ }
376
+ }
377
+ else {
378
+ const price = await quotePortalPrice(params.portal, params.tokenAddress, quoteToken, wrappedNative);
379
+ data = {
380
+ price: price ?? '0',
381
+ progress,
382
+ poolBNBAmount: '0',
383
+ poolTokenAmount: '0',
384
+ dexKind: 'PORTAL_QUOTE',
385
+ };
386
+ }
343
387
  }
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
- };
388
+ gradMetricsCache.set(tokenKey, { at: Date.now(), data });
389
+ return data;
352
390
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "2.2.15",
3
+ "version": "2.2.17",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",