four-flap-meme-sdk 2.2.15 → 2.2.16

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,23 +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
+ /** 单次 quote 估 WBNB 深度(市值场景优先速度) */
126
137
  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) {
138
+ try {
139
+ const bnbOut = await portal.quoteExactInput({
140
+ inputToken: tokenAddress,
141
+ outputToken: ZERO_ADDRESS,
142
+ inputAmount: 110000000n * 10n ** 18n,
143
+ });
144
+ if (bnbOut > 0n)
145
+ return formatEther(bnbOut);
146
+ }
147
+ catch {
130
148
  try {
131
149
  const bnbOut = await portal.quoteExactInput({
132
150
  inputToken: tokenAddress,
133
- outputToken: inputQuote,
134
- inputAmount: amt,
151
+ outputToken: ZERO_ADDRESS,
152
+ inputAmount: 100000000n * 10n ** 18n,
135
153
  });
136
154
  if (bnbOut > 0n)
137
155
  return formatEther(bnbOut);
138
156
  }
139
- catch {
140
- /* try smaller */
141
- }
157
+ catch { /* ignore */ }
142
158
  }
143
159
  return '0';
144
160
  }
@@ -152,7 +168,7 @@ async function getInfinityCLPoolTokenReserve(params) {
152
168
  if (!vault)
153
169
  return '0';
154
170
  try {
155
- const provider = new JsonRpcProvider(params.rpcUrl);
171
+ const provider = params.provider ?? getCachedRpcProvider(params.rpcUrl);
156
172
  const bal = (await new Contract(params.tokenAddress, ERC20_ABI, provider).balanceOf(vault));
157
173
  return formatEther(bal);
158
174
  }
@@ -160,30 +176,19 @@ async function getInfinityCLPoolTokenReserve(params) {
160
176
  return '0';
161
177
  }
162
178
  }
179
+ /** 单笔 buy quote 估价(比买卖双 quote 少一次 RPC) */
163
180
  async function quotePortalPrice(portal, tokenAddress, quoteTokenAddress, wrappedNativeAddress) {
164
181
  try {
165
182
  const buyAmt = 10n ** 16n;
166
- const sellAmt = 10n ** 21n;
167
183
  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
- ]);
184
+ const tokensOut = await portal.quoteExactInput({
185
+ inputToken: inputQuote,
186
+ outputToken: tokenAddress,
187
+ inputAmount: buyAmt,
188
+ });
180
189
  if (tokensOut <= 0n)
181
190
  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);
191
+ return String(Number(formatEther(buyAmt)) / Number(formatEther(tokensOut)));
187
192
  }
188
193
  catch {
189
194
  return null;
@@ -199,18 +204,22 @@ function formatProgress(state) {
199
204
  }
200
205
  async function getGraduatedV3PairMetrics(params) {
201
206
  try {
202
- const provider = new JsonRpcProvider(params.rpcUrl);
207
+ const provider = getCachedRpcProvider(params.rpcUrl);
203
208
  const pool = new Contract(params.poolAddress, V3_PAIR_ABI, provider);
209
+ const tokenLower = params.tokenAddress.toLowerCase();
210
+ const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
211
+ ? params.wrappedNativeAddress
212
+ : params.quoteTokenAddress;
204
213
  const [t0, t1, slot0] = await Promise.all([
205
214
  pool.token0(),
206
215
  pool.token1(),
207
216
  pool.slot0(),
208
217
  ]);
218
+ const [bal0, bal1] = await Promise.all([
219
+ new Contract(t0, ERC20_ABI, provider).balanceOf(params.poolAddress),
220
+ new Contract(t1, ERC20_ABI, provider).balanceOf(params.poolAddress),
221
+ ]);
209
222
  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
223
  const spot = v3SpotPriceQuotePerToken({
215
224
  sqrtPriceX96,
216
225
  tokenAddress: params.tokenAddress,
@@ -218,13 +227,16 @@ async function getGraduatedV3PairMetrics(params) {
218
227
  token1: t1,
219
228
  quoteTokenAddress: quoteAddr,
220
229
  });
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
230
  const tokenIs0 = t0.toLowerCase() === tokenLower;
226
231
  const poolTokenAmount = formatEther(tokenIs0 ? bal0 : bal1);
227
232
  const poolBNBAmount = formatEther(tokenIs0 ? bal1 : bal0);
233
+ let price = spot;
234
+ if (!price && parseFloat(poolTokenAmount) > 0) {
235
+ price = String(parseFloat(poolBNBAmount) / parseFloat(poolTokenAmount));
236
+ }
237
+ if (!price) {
238
+ price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
239
+ }
228
240
  if (!price && poolTokenAmount === '0' && poolBNBAmount === '0')
229
241
  return null;
230
242
  return {
@@ -241,60 +253,65 @@ async function getGraduatedV3PairMetrics(params) {
241
253
  }
242
254
  async function getInfinityCLGraduatedMetrics(params) {
243
255
  const progress = formatProgress(params.state);
244
- let price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
256
+ const provider = getCachedRpcProvider(params.rpcUrl);
245
257
  let poolBNBAmount = '0';
246
258
  let poolTokenAmount = '0';
247
259
  let poolId;
248
260
  let infinityFee;
261
+ let price = null;
249
262
  const poolKey = buildFlapInfinityPoolKey({
250
263
  tokenAddress: params.tokenAddress,
251
264
  quoteTokenAddress: params.quoteTokenAddress,
252
265
  chain: params.chain,
253
266
  lpFeeProfile: params.state.lpFeeProfile,
254
267
  });
255
- if (poolKey) {
268
+ const quoteAddr = params.quoteTokenAddress.toLowerCase() === ZERO_ADDRESS
269
+ ? params.wrappedNativeAddress
270
+ : params.quoteTokenAddress;
271
+ const slot0Promise = (async () => {
272
+ if (!poolKey)
273
+ return null;
256
274
  poolId = computeInfinityPoolId(poolKey);
257
275
  infinityFee = poolKey.fee;
258
276
  try {
259
- const provider = new JsonRpcProvider(params.rpcUrl);
260
277
  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
- }
278
+ const slot0 = await manager.getSlot0(poolId);
279
+ return BigInt(slot0[0]);
277
280
  }
278
281
  catch {
279
- /* keep portal quote */
282
+ return null;
280
283
  }
281
- }
282
- const [bnbDepth, tokenReserve] = await Promise.all([
283
- estimateInfinityPoolQuoteBnbDepth(params.portal, params.tokenAddress),
284
+ })();
285
+ const [sqrtPriceX96, tokenReserve, bnbDepth] = await Promise.all([
286
+ slot0Promise,
284
287
  getInfinityCLPoolTokenReserve({
285
288
  tokenAddress: params.tokenAddress,
286
289
  rpcUrl: params.rpcUrl,
287
290
  chain: params.chain,
291
+ provider,
288
292
  }),
293
+ estimateInfinityPoolQuoteBnbDepth(params.portal, params.tokenAddress),
289
294
  ]);
290
- if (parseFloat(bnbDepth) > 0)
291
- poolBNBAmount = bnbDepth;
295
+ if (sqrtPriceX96 && sqrtPriceX96 > 0n && poolKey) {
296
+ const spot = infinitySpotPriceQuotePerToken({
297
+ sqrtPriceX96,
298
+ tokenAddress: params.tokenAddress,
299
+ currency0: poolKey.currency0,
300
+ currency1: poolKey.currency1,
301
+ quoteTokenAddress: quoteAddr,
302
+ });
303
+ if (spot)
304
+ price = spot;
305
+ }
292
306
  if (parseFloat(tokenReserve) > 0)
293
307
  poolTokenAmount = tokenReserve;
294
- if (!price)
295
- price = '0';
308
+ if (parseFloat(bnbDepth) > 0)
309
+ poolBNBAmount = bnbDepth;
310
+ if (!price) {
311
+ price = await quotePortalPrice(params.portal, params.tokenAddress, params.quoteTokenAddress, params.wrappedNativeAddress);
312
+ }
296
313
  return {
297
- price,
314
+ price: price ?? '0',
298
315
  progress,
299
316
  poolBNBAmount,
300
317
  poolTokenAmount,
@@ -311,14 +328,20 @@ async function getInfinityCLGraduatedMetrics(params) {
311
328
  */
312
329
  export async function getFlapGraduatedDexMetrics(params) {
313
330
  const chain = String(params.chain || 'BSC').toUpperCase();
331
+ const tokenKey = `${chain}:${params.tokenAddress.toLowerCase()}`;
332
+ const cached = gradMetricsCache.get(tokenKey);
333
+ if (cached && Date.now() - cached.at < GRAD_METRICS_TTL_MS) {
334
+ return cached.data;
335
+ }
314
336
  const wrappedNative = params.wrappedNativeAddress ?? '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
315
337
  const quoteToken = params.state.quoteTokenAddress &&
316
338
  params.state.quoteTokenAddress.toLowerCase() !== ZERO_ADDRESS
317
339
  ? params.state.quoteTokenAddress
318
340
  : wrappedNative;
319
341
  const progress = formatProgress(params.state);
342
+ let data;
320
343
  if (isFlapInfinityCLGraduated(params.state, chain)) {
321
- return getInfinityCLGraduatedMetrics({
344
+ data = await getInfinityCLGraduatedMetrics({
322
345
  portal: params.portal,
323
346
  tokenAddress: params.tokenAddress,
324
347
  state: params.state,
@@ -328,25 +351,42 @@ export async function getFlapGraduatedDexMetrics(params) {
328
351
  chain,
329
352
  });
330
353
  }
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;
354
+ else {
355
+ const pool = params.state.pool;
356
+ if (pool && pool.toLowerCase() !== ZERO_ADDRESS && !isInfinityCLPoolManagerAddress(pool, chain)) {
357
+ const v3 = await getGraduatedV3PairMetrics({
358
+ rpcUrl: params.rpcUrl,
359
+ tokenAddress: params.tokenAddress,
360
+ poolAddress: pool,
361
+ quoteTokenAddress: quoteToken,
362
+ wrappedNativeAddress: wrappedNative,
363
+ portal: params.portal,
364
+ });
365
+ if (v3) {
366
+ data = v3;
367
+ }
368
+ else {
369
+ const price = await quotePortalPrice(params.portal, params.tokenAddress, quoteToken, wrappedNative);
370
+ data = {
371
+ price: price ?? '0',
372
+ progress,
373
+ poolBNBAmount: '0',
374
+ poolTokenAmount: '0',
375
+ dexKind: 'PORTAL_QUOTE',
376
+ };
377
+ }
378
+ }
379
+ else {
380
+ const price = await quotePortalPrice(params.portal, params.tokenAddress, quoteToken, wrappedNative);
381
+ data = {
382
+ price: price ?? '0',
383
+ progress,
384
+ poolBNBAmount: '0',
385
+ poolTokenAmount: '0',
386
+ dexKind: 'PORTAL_QUOTE',
387
+ };
388
+ }
343
389
  }
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
- };
390
+ gradMetricsCache.set(tokenKey, { at: Date.now(), data });
391
+ return data;
352
392
  }
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.16",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",