Skip to content
46 changes: 46 additions & 0 deletions src/cached.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import memoize from "memoizee";
import {IDict, IExtendedPoolDataFromApi, INetworkName, IPoolType} from "./interfaces.js";
import {uncached_getAllPoolsFromApi, createCrvApyDict, createUsdPricesDict} from './external-api.js'
import {curve} from "./curve";

/**
* 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) => {
const poolsDict = await uncached_getAllPoolsFromApi(network, isLiteChain);
const poolLists = Object.values(poolsDict)
const usdPrices = createUsdPricesDict(poolLists);
const crvApy = createCrvApyDict(poolLists)
return { poolsDict, poolLists, usdPrices, crvApy };
},
{
promise: true,
maxAge: 5 * 60 * 1000, // 5m
primitive: true,
}
)

export const _getPoolsFromApi =
async (network: INetworkName, poolType: IPoolType, isLiteChain = false): Promise<IExtendedPoolDataFromApi> => {
const {poolsDict} = await _getCachedData(network, isLiteChain);
return poolsDict[poolType]
}

export const _getAllPoolsFromApi = async (network: INetworkName, isLiteChain = false): Promise<IExtendedPoolDataFromApi[]> => {
const {poolLists} = await _getCachedData(network, isLiteChain);
return poolLists
}

export const _getUsdPricesFromApi = async (): Promise<IDict<number>> => {
const network = curve.constants.NETWORK_NAME;
const {usdPrices} = await _getCachedData(network, false);
return usdPrices
}

export const _getCrvApyFromApi = async (): Promise<IDict<[number, number]>> => {
const network = curve.constants.NETWORK_NAME;
const {crvApy} = await _getCachedData(network, false);
return crvApy
}
128 changes: 99 additions & 29 deletions src/external-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,108 @@ import {
} from "./interfaces";


export const _getPoolsFromApi = memoize(
async (network: INetworkName, poolType: IPoolType, isLiteChain = false): Promise<IExtendedPoolDataFromApi> => {
const api = isLiteChain ? "https://api-core.curve.fi/v1/" : "https://api.curve.fi/api";
const url = `${api}/getPools/${network}/${poolType}`;
return await fetchData(url) ?? { poolData: [], tvl: 0, tvlAll: 0 };
},
{
promise: true,
maxAge: 5 * 60 * 1000, // 5m
const uncached_getPoolsFromApi = async (network: INetworkName, poolType: IPoolType, isLiteChain = false): Promise<IExtendedPoolDataFromApi> => {
const api = isLiteChain ? "https://api-core.curve.fi/v1/" : "https://api.curve.fi/api";
const url = `${api}/getPools/${network}/${poolType}`;
return await fetchData(url) ?? { poolData: [], tvl: 0, tvlAll: 0 };
}

const getPoolTypes = (isLiteChain: boolean) => isLiteChain ? ["factory-twocrypto", "factory-tricrypto", "factory-stable-ng"] as const :
["main", "crypto", "factory", "factory-crvusd", "factory-eywa", "factory-crypto", "factory-twocrypto", "factory-tricrypto", "factory-stable-ng"] as const;

export const uncached_getAllPoolsFromApi = async (network: INetworkName, isLiteChain = false): Promise<Record<IPoolType, IExtendedPoolDataFromApi>> =>
Object.fromEntries(
await Promise.all(getPoolTypes(isLiteChain).map(async (poolType) => {
const data = await uncached_getPoolsFromApi(network, poolType, isLiteChain);
return [poolType, data];
}))
)

export const createUsdPricesDict = (allTypesExtendedPoolData: IExtendedPoolDataFromApi[]): IDict<number> => {
const priceDict: IDict<Record<string, number>[]> = {};
const priceDictByMaxTvl: IDict<number> = {};

for (const extendedPoolData of allTypesExtendedPoolData) {
for (const pool of extendedPoolData.poolData) {
const lpTokenAddress = pool.lpTokenAddress ?? pool.address;
const totalSupply = pool.totalSupply / (10 ** 18);
if(lpTokenAddress.toLowerCase() in priceDict) {
priceDict[lpTokenAddress.toLowerCase()].push({
price: pool.usdTotal && totalSupply ? pool.usdTotal / totalSupply : 0,
tvl: pool.usdTotal,
})
} else {
priceDict[lpTokenAddress.toLowerCase()] = []
priceDict[lpTokenAddress.toLowerCase()].push({
price: pool.usdTotal && totalSupply ? pool.usdTotal / totalSupply : 0,
tvl: pool.usdTotal,
})
}

for (const coin of pool.coins) {
if (typeof coin.usdPrice === "number") {
if(coin.address.toLowerCase() in priceDict) {
priceDict[coin.address.toLowerCase()].push({
price: coin.usdPrice,
tvl: pool.usdTotal,
})
} else {
priceDict[coin.address.toLowerCase()] = []
priceDict[coin.address.toLowerCase()].push({
price: coin.usdPrice,
tvl: pool.usdTotal,
})
}
}
}

for (const coin of pool.gaugeRewards ?? []) {
if (typeof coin.tokenPrice === "number") {
if(coin.tokenAddress.toLowerCase() in priceDict) {
priceDict[coin.tokenAddress.toLowerCase()].push({
price: coin.tokenPrice,
tvl: pool.usdTotal,
});
} else {
priceDict[coin.tokenAddress.toLowerCase()] = []
priceDict[coin.tokenAddress.toLowerCase()].push({
price: coin.tokenPrice,
tvl: pool.usdTotal,
});
}
}
}
}
}

for(const address in priceDict) {
if (priceDict[address].length) {
const maxTvlItem = priceDict[address].reduce((prev, current) => +current.tvl > +prev.tvl ? current : prev);
priceDictByMaxTvl[address] = maxTvlItem.price
} else {
priceDictByMaxTvl[address] = 0
}
}
)

export const _getAllPoolsFromApi = async (network: INetworkName, isLiteChain = false): Promise<IExtendedPoolDataFromApi[]> => {
if(isLiteChain) {
return await Promise.all([
_getPoolsFromApi(network, "factory-twocrypto", isLiteChain),
_getPoolsFromApi(network, "factory-tricrypto", isLiteChain),
_getPoolsFromApi(network, "factory-stable-ng", isLiteChain),
]);
} else {
return await Promise.all([
_getPoolsFromApi(network, "main", isLiteChain),
_getPoolsFromApi(network, "crypto", isLiteChain),
_getPoolsFromApi(network, "factory", isLiteChain),
_getPoolsFromApi(network, "factory-crvusd", isLiteChain),
_getPoolsFromApi(network, "factory-eywa", isLiteChain),
_getPoolsFromApi(network, "factory-crypto", isLiteChain),
_getPoolsFromApi(network, "factory-twocrypto", isLiteChain),
_getPoolsFromApi(network, "factory-tricrypto", isLiteChain),
_getPoolsFromApi(network, "factory-stable-ng", isLiteChain),
]);
return priceDictByMaxTvl
}

export const createCrvApyDict = (allTypesExtendedPoolData: IExtendedPoolDataFromApi[]): IDict<[number, number]> => {
const apyDict: IDict<[number, number]> = {};

for (const extendedPoolData of allTypesExtendedPoolData) {
for (const pool of extendedPoolData.poolData) {
if (pool.gaugeAddress) {
if (!pool.gaugeCrvApy) {
apyDict[pool.gaugeAddress.toLowerCase()] = [0, 0];
} else {
apyDict[pool.gaugeAddress.toLowerCase()] = [pool.gaugeCrvApy[0] ?? 0, pool.gaugeCrvApy[1] ?? 0];
}
}
}
}

return apyDict
}

export const _getSubgraphData = memoize(
Expand Down
2 changes: 1 addition & 1 deletion src/factory/factory-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import twocryptoFactorySwapABI from "../constants/abis/factory-twocrypto/factory
import tricryptoFactorySwapABI from "../constants/abis/factory-tricrypto/factory-tricrypto-pool.json" with { type: 'json' };
import tricryptoFactoryEthDisabledSwapABI from "../constants/abis/factory-tricrypto/factory-tricrypto-pool-eth-disabled.json" with { type: 'json' };
import { getPoolIdByAddress, setFactoryZapContracts } from "./common.js";
import { _getPoolsFromApi } from "../external-api.js";
import { _getPoolsFromApi } from "../cached.js";
import {assetTypeNameHandler, getPoolName, isStableNgPool} from "../utils.js";
import StableNgBasePoolZapABI from "../constants/abis/stable-ng-base-pool-zap.json" with { type: 'json' };
import MetaStableSwapNGABI from "../constants/abis/factory-stable-ng/meta-stableswap-ng.json" with { type: 'json' };
Expand Down
6 changes: 5 additions & 1 deletion src/pools/subClasses/corePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ export class CorePool implements ICorePool {
inApi: boolean;

constructor(id: string) {
const poolData = curve.getPoolsData()[id];
const poolsData = curve.getPoolsData();
if (!poolsData[id]) {
throw new Error(`Pool ${id} not found. Available pools: ${Object.keys(poolsData).join(', ')}`);
}
const poolData = poolsData[id];

this.id = id;
this.name = poolData.name;
Expand Down
3 changes: 1 addition & 2 deletions src/pools/subClasses/statsPool.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { curve } from "../../curve.js";
import {IPoolType, IReward} from '../../interfaces.js';
import {_getPoolsFromApi} from '../../external-api.js';
import {_getPoolsFromApi,_getCrvApyFromApi} from '../../cached.js';
import {
_getUsdRate,
BN,
toBN,
_getCrvApyFromApi,
_getRewardsFromApi,
getVolumeApiController,
} from '../../utils.js';
Expand Down
2 changes: 1 addition & 1 deletion src/pools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getPool } from "./poolConstructor.js";
import { IDict } from "../interfaces";
import { curve } from "../curve.js";
import { _getRewardsFromApi, _getUsdRate, _setContracts, toBN } from "../utils.js";
import { _getAllPoolsFromApi } from "../external-api.js";
import { _getAllPoolsFromApi } from "../cached.js";
import ERC20Abi from "../constants/abis/ERC20.json" with { type: 'json' };

// _userLpBalance: { address: { poolId: { _lpBalance: 0, time: 0 } } }
Expand Down
93 changes: 1 addition & 92 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import {
} from './interfaces';
import {curve} from "./curve.js";
import {
_getAllPoolsFromApi,
_getCurveLiteNetworks,
_getFactoryAPYs,
_getLiteNetworksData,
_getSubgraphData,
_getVolumes,
} from "./external-api.js";
import {_getAllPoolsFromApi, _getUsdPricesFromApi} from "./cached.js";
import ERC20Abi from './constants/abis/ERC20.json' with { type: 'json' };
import {L2Networks} from './constants/L2Networks.js';
import {volumeNetworks} from "./constants/volumeNetworks.js";
Expand Down Expand Up @@ -316,97 +316,6 @@ export const getPoolIdBySwapAddress = (swapAddress: string): string => {
return poolIds[0][0];
}

export const _getUsdPricesFromApi = async (): Promise<IDict<number>> => {
const network = curve.constants.NETWORK_NAME;
const allTypesExtendedPoolData = await _getAllPoolsFromApi(network, curve.isLiteChain);
const priceDict: IDict<Record<string, number>[]> = {};
const priceDictByMaxTvl: IDict<number> = {};

for (const extendedPoolData of allTypesExtendedPoolData) {
for (const pool of extendedPoolData.poolData) {
const lpTokenAddress = pool.lpTokenAddress ?? pool.address;
const totalSupply = pool.totalSupply / (10 ** 18);
if(lpTokenAddress.toLowerCase() in priceDict) {
priceDict[lpTokenAddress.toLowerCase()].push({
price: pool.usdTotal && totalSupply ? pool.usdTotal / totalSupply : 0,
tvl: pool.usdTotal,
})
} else {
priceDict[lpTokenAddress.toLowerCase()] = []
priceDict[lpTokenAddress.toLowerCase()].push({
price: pool.usdTotal && totalSupply ? pool.usdTotal / totalSupply : 0,
tvl: pool.usdTotal,
})
}

for (const coin of pool.coins) {
if (typeof coin.usdPrice === "number") {
if(coin.address.toLowerCase() in priceDict) {
priceDict[coin.address.toLowerCase()].push({
price: coin.usdPrice,
tvl: pool.usdTotal,
})
} else {
priceDict[coin.address.toLowerCase()] = []
priceDict[coin.address.toLowerCase()].push({
price: coin.usdPrice,
tvl: pool.usdTotal,
})
}
}
}

for (const coin of pool.gaugeRewards ?? []) {
if (typeof coin.tokenPrice === "number") {
if(coin.tokenAddress.toLowerCase() in priceDict) {
priceDict[coin.tokenAddress.toLowerCase()].push({
price: coin.tokenPrice,
tvl: pool.usdTotal,
});
} else {
priceDict[coin.tokenAddress.toLowerCase()] = []
priceDict[coin.tokenAddress.toLowerCase()].push({
price: coin.tokenPrice,
tvl: pool.usdTotal,
});
}
}
}
}
}

for(const address in priceDict) {
if (priceDict[address].length) {
const maxTvlItem = priceDict[address].reduce((prev, current) => +current.tvl > +prev.tvl ? current : prev);
priceDictByMaxTvl[address] = maxTvlItem.price
} else {
priceDictByMaxTvl[address] = 0
}
}

return priceDictByMaxTvl
}

export const _getCrvApyFromApi = async (): Promise<IDict<[number, number]>> => {
const network = curve.constants.NETWORK_NAME;
const allTypesExtendedPoolData = await _getAllPoolsFromApi(network, curve.isLiteChain);
const apyDict: IDict<[number, number]> = {};

for (const extendedPoolData of allTypesExtendedPoolData) {
for (const pool of extendedPoolData.poolData) {
if (pool.gaugeAddress) {
if (!pool.gaugeCrvApy) {
apyDict[pool.gaugeAddress.toLowerCase()] = [0, 0];
} else {
apyDict[pool.gaugeAddress.toLowerCase()] = [pool.gaugeCrvApy[0] ?? 0, pool.gaugeCrvApy[1] ?? 0];
}
}
}
}

return apyDict
}

export const _getRewardsFromApi = async (): Promise<IDict<IRewardFromApi[]>> => {
const network = curve.constants.NETWORK_NAME;
const allTypesExtendedPoolData = await _getAllPoolsFromApi(network, curve.isLiteChain);
Expand Down
Loading