From 51ce8b52d63e7f0249c676555c8420b30d912279 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Apr 2025 10:54:00 +0200 Subject: [PATCH 1/4] feat: allow pool data injection --- src/cached.ts | 61 +++++++++++++++++++++++++++++++++++++++------------ src/curve.ts | 19 ++++++++++++++-- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/cached.ts b/src/cached.ts index 90a9d052..c6382c6a 100644 --- a/src/cached.ts +++ b/src/cached.ts @@ -1,22 +1,47 @@ -import memoize from "memoizee"; import {IDict, IExtendedPoolDataFromApi, INetworkName, IPoolType} from "./interfaces.js"; import {uncached_getAllPoolsFromApi, uncached_getCrvApyFromApi, uncached_getUsdPricesFromApi} from './external-api.js' import {curve} from "./curve"; -const _getCachedData = memoize( - async (network: INetworkName, isLiteChain: boolean) => { - const poolsDict = await uncached_getAllPoolsFromApi(network, isLiteChain); - const poolLists = Object.values(poolsDict) - const usdPrices = uncached_getUsdPricesFromApi(poolLists); - const crvApy = uncached_getCrvApyFromApi(poolLists) - return { poolsDict, poolLists, usdPrices, crvApy }; - }, - { - promise: true, - maxAge: 5 * 60 * 1000, // 5m - primitive: true, +const memoize = Promise>(fn: TFunc, {maxAge}: { + maxAge: number +}) => { + const cache: Record> = {}; + const cachedFn = async (...args: TParams): Promise => { + const key = JSON.stringify(args); + if (key in cache) { + return cache[key]; + } + const promise = fn(...args); + cache[key] = promise; + try { + const result = await promise; + setTimeout(() => delete cache[key], maxAge); + return result; + } catch (e) { + delete cache[key]; + throw e; + } + }; + cachedFn.set = (result: TResult, ...args: TParams) => { + const key = JSON.stringify(args); + setTimeout(() => delete cache[key], maxAge); + cache[key] = Promise.resolve(result); } -) + return cachedFn as TFunc & { set: (result: TResult, ...args: TParams) => void }; +} + +function createCache(poolsDict: Record<"main" | "crypto" | "factory" | "factory-crvusd" | "factory-eywa" | "factory-crypto" | "factory-twocrypto" | "factory-tricrypto" | "factory-stable-ng", IExtendedPoolDataFromApi>) { + const poolLists = Object.values(poolsDict) + const usdPrices = uncached_getUsdPricesFromApi(poolLists); + const crvApy = uncached_getCrvApyFromApi(poolLists) + return {poolsDict, poolLists, usdPrices, crvApy}; +} + +const _getCachedData = memoize( + async (network: INetworkName, isLiteChain: boolean) => createCache(await uncached_getAllPoolsFromApi(network, isLiteChain)) + , { + maxAge: 1000 * 60 * 5, // 5 minutes + }) export const _getPoolsFromApi = async (network: INetworkName, poolType: IPoolType, isLiteChain = false): Promise => { @@ -24,6 +49,14 @@ export const _getPoolsFromApi = return poolsDict[poolType] } +export const _setPoolsFromApi = + (network: INetworkName, isLiteChain: boolean, data: Record): void => + _getCachedData.set( + createCache(data), + network, + isLiteChain + ) + export const _getAllPoolsFromApi = async (network: INetworkName, isLiteChain = false): Promise => { const {poolLists} = await _getCachedData(network, isLiteChain); return poolLists diff --git a/src/curve.ts b/src/curve.ts index 8431ee09..a7adbf28 100644 --- a/src/curve.ts +++ b/src/curve.ts @@ -16,7 +16,17 @@ import { getFactoryPoolData } from "./factory/factory.js"; import { getFactoryPoolsDataFromApi } from "./factory/factory-api.js"; import { getCryptoFactoryPoolData } from "./factory/factory-crypto.js"; import { getTricryptoFactoryPoolData } from "./factory/factory-tricrypto.js"; -import {IPoolData, IDict, ICurve, IChainId, IFactoryPoolType, Abi, INetworkConstants} from "./interfaces"; +import { + IPoolData, + IDict, + ICurve, + IChainId, + IFactoryPoolType, + Abi, + INetworkConstants, + IPoolType, + IExtendedPoolDataFromApi, +} from "./interfaces"; import ERC20Abi from './constants/abis/ERC20.json' with { type: 'json' }; import cERC20Abi from './constants/abis/cERC20.json' with { type: 'json' }; import yERC20Abi from './constants/abis/yERC20.json' with { type: 'json' }; @@ -56,6 +66,7 @@ import {_getHiddenPools} from "./external-api.js"; import { L2Networks } from "./constants/L2Networks.js"; import { getTwocryptoFactoryPoolData } from "./factory/factory-twocrypto.js"; import {getNetworkConstants} from "./utils.js"; +import {_setPoolsFromApi} from "./cached"; export const OLD_CHAINS = [1, 10, 56, 100, 137, 250, 1284, 2222, 8453, 42161, 42220, 43114, 1313161554]; // these chains have non-ng pools @@ -147,7 +158,7 @@ class Curve implements ICurve { async init( providerType: 'JsonRpc' | 'Web3' | 'Infura' | 'Alchemy' | 'NoRPC', providerSettings: { url?: string, privateKey?: string, batchMaxCount? : number } | { externalProvider: ethers.Eip1193Provider } | { network?: Networkish, apiKey?: string } | 'NoRPC', - options: { gasPrice?: number, maxFeePerGas?: number, maxPriorityFeePerGas?: number, chainId?: number } = {} // gasPrice in Gwei + options: { gasPrice?: number, maxFeePerGas?: number, maxPriorityFeePerGas?: number, chainId?: number, poolsData?: Record } = {} // gasPrice in Gwei ): Promise { // @ts-ignore this.provider = null; @@ -294,6 +305,10 @@ class Curve implements ICurve { } this.feeData = { gasPrice: options.gasPrice, maxFeePerGas: options.maxFeePerGas, maxPriorityFeePerGas: options.maxPriorityFeePerGas }; + if (options.poolsData) { + _setPoolsFromApi(this.constants.NETWORK_NAME, this.isLiteChain, options.poolsData); + } + await this.updateFeeData(); for (const pool of Object.values({...this.constants.POOLS_DATA, ...this.constants.LLAMMAS_DATA})) { From 9ae23668c8fdfe23386690ea7a9d081c0846b0fc Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Apr 2025 11:00:00 +0200 Subject: [PATCH 2/4] chore: self-review --- src/cached.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cached.ts b/src/cached.ts index c6382c6a..0ab53a72 100644 --- a/src/cached.ts +++ b/src/cached.ts @@ -2,6 +2,13 @@ import {IDict, IExtendedPoolDataFromApi, INetworkName, IPoolType} from "./interf import {uncached_getAllPoolsFromApi, uncached_getCrvApyFromApi, uncached_getUsdPricesFromApi} from './external-api.js' import {curve} from "./curve"; +/** + * Memoizes a function that returns a promise. + * Custom function instead of `memoizee` because we want to be able to set the cache manually based on server data. + * @param fn The function that returns a promise and will be memoized + * @param maxAge The maximum age of the cache in milliseconds + * @returns A memoized `fn` function that includes a `set` method to set the cache manually + */ const memoize = Promise>(fn: TFunc, {maxAge}: { maxAge: number }) => { @@ -37,11 +44,8 @@ function createCache(poolsDict: Record<"main" | "crypto" | "factory" | "factory- return {poolsDict, poolLists, usdPrices, crvApy}; } -const _getCachedData = memoize( - async (network: INetworkName, isLiteChain: boolean) => createCache(await uncached_getAllPoolsFromApi(network, isLiteChain)) - , { - maxAge: 1000 * 60 * 5, // 5 minutes - }) +const _getCachedData = memoize(async (network: INetworkName, isLiteChain: boolean) => + createCache(await uncached_getAllPoolsFromApi(network, isLiteChain)), { maxAge: 1000 * 60 * 5 /* 5 minutes */ }) export const _getPoolsFromApi = async (network: INetworkName, poolType: IPoolType, isLiteChain = false): Promise => { From 0b110a76055a31ceecb4930326426a07a931c17d Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Apr 2025 12:51:57 +0200 Subject: [PATCH 3/4] fix: review comments --- src/cached.ts | 56 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/cached.ts b/src/cached.ts index b924662a..9c1970e5 100644 --- a/src/cached.ts +++ b/src/cached.ts @@ -1,5 +1,5 @@ import {IDict, IExtendedPoolDataFromApi, INetworkName, IPoolType} from "./interfaces.js"; -import {uncached_getAllPoolsFromApi, createCrvApyDict, createUsdPricesDict} from './external-api.js' +import {createCrvApyDict, createUsdPricesDict, uncached_getAllPoolsFromApi} from './external-api.js' import {curve} from "./curve"; /** @@ -7,49 +7,75 @@ import {curve} from "./curve"; * Custom function instead of `memoizee` because we want to be able to set the cache manually based on server data. * @param fn The function that returns a promise and will be memoized * @param maxAge The maximum age of the cache in milliseconds + * @param createKey A function that creates a key for the cache based on the arguments passed to the function * @returns A memoized `fn` function that includes a `set` method to set the cache manually */ -const memoize = Promise>(fn: TFunc, {maxAge}: { - maxAge: number +const memoize = Promise>(fn: TFunc, { + maxAge, + createKey = (list) => list.toString(), +}: { + maxAge: number, + createKey?: (args: TParams) => string }) => { const cache: Record> = {}; + const timeouts: Record> = {}; + + const setCache = (key: string, promise?: Promise) => { + if (promise) { + cache[key] = promise; + } else if (key in cache) { + delete cache[key]; + } + if (key in timeouts) { + clearTimeout(timeouts[key]); + delete timeouts[key] + } + }; + + const scheduleCleanup = (key: string) => timeouts[key] = setTimeout(() => { + delete timeouts[key]; + delete cache[key]; + }, maxAge); + const cachedFn = async (...args: TParams): Promise => { - const key = JSON.stringify(args); + const key = createKey(args); if (key in cache) { return cache[key]; } const promise = fn(...args); - cache[key] = promise; + setCache(key, promise); try { const result = await promise; - setTimeout(() => delete cache[key], maxAge); + scheduleCleanup(key) return result; } catch (e) { delete cache[key]; throw e; } }; + cachedFn.set = (result: TResult, ...args: TParams) => { - const key = JSON.stringify(args); - setTimeout(() => delete cache[key], maxAge); - cache[key] = Promise.resolve(result); + const key = createKey(args); + setCache(key, Promise.resolve(result)); + scheduleCleanup(key); } + return cachedFn as TFunc & { set: (result: TResult, ...args: TParams) => void }; } -function createCache(poolsDict: Record<"main" | "crypto" | "factory" | "factory-crvusd" | "factory-eywa" | "factory-crypto" | "factory-twocrypto" | "factory-tricrypto" | "factory-stable-ng", IExtendedPoolDataFromApi>) { +const createCache = (poolsDict: Record) => { const poolLists = Object.values(poolsDict) const usdPrices = createUsdPricesDict(poolLists); const crvApy = createCrvApyDict(poolLists) return {poolsDict, poolLists, usdPrices, crvApy}; -} +}; /** * This function is used to cache the data fetched from the API and the data derived from it. * Note: do not expose this function to the outside world, instead encapsulate it in a function that returns the data you need. */ const _getCachedData = memoize(async (network: INetworkName, isLiteChain: boolean) => - createCache(await uncached_getAllPoolsFromApi(network, isLiteChain)), { maxAge: 1000 * 60 * 5 /* 5 minutes */ }) + createCache(await uncached_getAllPoolsFromApi(network, isLiteChain)), {maxAge: 1000 * 60 * 5 /* 5 minutes */}) export const _getPoolsFromApi = async (network: INetworkName, poolType: IPoolType, isLiteChain = false): Promise => { @@ -59,11 +85,7 @@ export const _getPoolsFromApi = export const _setPoolsFromApi = (network: INetworkName, isLiteChain: boolean, data: Record): void => - _getCachedData.set( - createCache(data), - network, - isLiteChain - ) + _getCachedData.set(createCache(data), network, isLiteChain) export const _getAllPoolsFromApi = async (network: INetworkName, isLiteChain = false): Promise => { const {poolLists} = await _getCachedData(network, isLiteChain); From 69a731897cfe80080edd3b303c1bdd1b036ba614 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 3 Jun 2025 10:06:34 +0200 Subject: [PATCH 4/4] chore: build v2.66.31 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24e2652a..ab6082b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@curvefi/api", - "version": "2.66.30", + "version": "2.66.31", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@curvefi/api", - "version": "2.66.30", + "version": "2.66.31", "license": "MIT", "dependencies": { "@curvefi/ethcall": "^6.0.13", diff --git a/package.json b/package.json index bd949eec..cc880e24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/api", - "version": "2.66.30", + "version": "2.66.31", "description": "JavaScript library for curve.finance", "main": "lib/index.js", "author": "Macket",