diff --git a/README.md b/README.md index 36483e0a..6af88da9 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,9 @@ import curve from "@curvefi/api"; (async () => { await curve.init('JsonRpc', {}, {gasPrice: 0, maxFeePerGas: 0, maxPriorityFeePerGas: 0}); + + console.log(await curve.getTVL()); + // 19281307454.671753 const aave = new curve.Pool('aave'); diff --git a/package.json b/package.json index 4261ee85..d389f810 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/api", - "version": "1.21.0", + "version": "1.22.0", "description": "JavaScript library for curve.fi", "main": "lib/index.js", "scripts": { diff --git a/src/external-api.ts b/src/external-api.ts new file mode 100644 index 00000000..ebd4c61e --- /dev/null +++ b/src/external-api.ts @@ -0,0 +1,19 @@ +import { IExtendedPoolDataFromApi } from "./interfaces"; +import axios from "axios"; +import memoize from "memoizee"; + +export const _getPoolsFromApi = memoize( + async (network: "ethereum" | "polygon", poolType: "main" | "crypto" | "factory" | "factory-crypto"): Promise => { + const url = `https://api.curve.fi/api/getPools/${network}/${poolType}`; + try { + const response = await axios.get(url); + return response.data.data; + } catch (err) { + return { poolData: [], tvl: 0, tvlAll: 0 }; + } + }, + { + promise: true, + maxAge: 5 * 60 * 1000, // 5m + } +) diff --git a/src/index.ts b/src/index.ts index dc6f26d9..91857f92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,6 +47,7 @@ import { getFactoryPoolList, getCryptoFactoryPoolList, getUsdRate, + getTVL, } from "./utils"; async function init ( @@ -81,6 +82,7 @@ const curve = { getFactoryPoolList, getCryptoFactoryPoolList, getUsdRate, + getTVL, setCustomFeeData, signerAddress: '', chainId: 0, diff --git a/src/interfaces.ts b/src/interfaces.ts index d9c46bf8..425fe83a 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -82,6 +82,7 @@ export interface ICoinFromPoolDataApi { address: string, symbol: string, decimals: string, + usdPrice: number | string, } export interface IPoolDataFromApi { @@ -95,6 +96,13 @@ export interface IPoolDataFromApi { implementation: string, implementationAddress: string, coins: ICoinFromPoolDataApi[], + usdTotal: number, +} + +export interface IExtendedPoolDataFromApi { + poolData: IPoolDataFromApi[], + tvl?: number, + tvlAll: number, } export interface RewardsApyInterface { diff --git a/src/pools.ts b/src/pools.ts index 5048a851..a5e4687e 100644 --- a/src/pools.ts +++ b/src/pools.ts @@ -1,5 +1,7 @@ +import axios from "axios"; import { ethers } from "ethers"; -import BigNumber from 'bignumber.js' +import BigNumber from 'bignumber.js'; +import { _getPoolsFromApi } from './external-api'; import { _getCoinAddresses, _getCoinDecimals, @@ -35,7 +37,6 @@ import { LINK_COINS_LOWER_CASE, COINS, } from "./curve"; -import axios from "axios"; export class Pool { @@ -95,7 +96,7 @@ export class Pool { getParameters: () => Promise<{ virtualPrice: string, fee: string, adminFee: string, A: string, gamma?: string }>, getPoolBalances: () => Promise, getPoolWrappedBalances: () => Promise, - getTotalLiquidity: () => Promise, + getTotalLiquidity: (useApi?: boolean) => Promise, getVolume: () => Promise, getBaseApy: () => Promise<{day: string, week: string, month: string, total: string}>, getTokenApy: () => Promise<[baseApy: string, boostedApy: string]>, @@ -307,7 +308,22 @@ export class Pool { return _wrappedBalances.map((_b, i) => ethers.utils.formatUnits(_b, this.decimals[i])); } - private getTotalLiquidity = async (): Promise => { + private getTotalLiquidity = async (useApi = true): Promise => { + if (useApi) { + const network = curve.chainId === 137 ? "polygon" : "ethereum"; + const poolType = !this.isFactory && !this.isCrypto ? "main" : + !this.isFactory ? "crypto" : + !this.isCryptoFactory ? "factory" : + "factory-crypto"; + const poolsData = (await _getPoolsFromApi(network, poolType)).poolData; + + try { + const totalLiquidity = poolsData.filter((data) => data.address.toLowerCase() === this.swap.toLowerCase())[0].usdTotal; + return String(totalLiquidity); + } catch (err) { + console.log((err as Error).message); + } + } const balances = await this.getPoolBalances(); const promises = []; @@ -342,7 +358,7 @@ export class Pool { private getVolume = async (): Promise => { const volume = (await this._getPoolStats()).volume; - const usdRate = (this.isCrypto || (curve.chainId === 1 && this.isFactory)) ? 1 : await _getUsdRate(this.referenceAsset); + const usdRate = (this.isCrypto || (curve.chainId === 1 && this.isFactory)) ? 1 : await _getUsdRate(this.coinAddresses[0]); return String(volume * usdRate) } diff --git a/src/utils.ts b/src/utils.ts index 74a344b8..feba55b8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,6 +5,7 @@ import BigNumber from 'bignumber.js'; import {DictInterface, IStats } from './interfaces'; import { curve, POOLS_DATA, LP_TOKENS, GAUGES } from "./curve"; import { COINS, DECIMALS_LOWER_CASE } from "./curve"; +import { _getPoolsFromApi } from "./external-api"; const ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; @@ -206,9 +207,33 @@ export const getPoolNameBySwapAddress = (swapAddress: string): string => { return Object.entries(POOLS_DATA).filter(([_, poolData]) => poolData.swap_address.toLowerCase() === swapAddress.toLowerCase())[0][0]; } -const _usdRatesCache: DictInterface<{ rate: number, time: number }> = {} +export const _getUsdPricesFromApi = async (): Promise> => { + const network = curve.chainId === 137 ? "polygon" : "ethereum"; + const promises = [ + _getPoolsFromApi(network, "main"), + _getPoolsFromApi(network, "crypto"), + _getPoolsFromApi(network, "factory"), + _getPoolsFromApi(network, "factory-crypto"), + ]; + const allTypesExtendedPoolData = await Promise.all(promises); + const priceDict: DictInterface = {}; + + for (const extendedPoolData of allTypesExtendedPoolData) { + for (const pool of extendedPoolData.poolData) { + for (const coin of pool.coins) { + if (typeof coin.usdPrice === "number") priceDict[coin.address.toLowerCase()] = coin.usdPrice; + } + } + } + + return priceDict +} +const _usdRatesCache: DictInterface<{ rate: number, time: number }> = {} export const _getUsdRate = async (assetId: string): Promise => { + const pricesFromApi = await _getUsdPricesFromApi(); + if (assetId.toLowerCase() in pricesFromApi) return pricesFromApi[assetId.toLowerCase()]; + if (assetId === 'USD' || (curve.chainId === 137 && (assetId.toLowerCase() === COINS.am3crv.toLowerCase()))) return 1 let chainName = { @@ -352,4 +377,18 @@ export const getCryptoFactoryPoolList = (): string[] => Object.keys(curve.consta export const getUsdRate = async (coin: string): Promise => { const [coinAddress] = _getCoinAddresses(coin); return await _getUsdRate(coinAddress); +} + +export const getTVL = async (chainId = curve.chainId): Promise => { + const network = chainId === 137 ? "polygon" : "ethereum"; + + const promises = [ + _getPoolsFromApi(network, "main"), + _getPoolsFromApi(network, "crypto"), + _getPoolsFromApi(network, "factory"), + _getPoolsFromApi(network, "factory-crypto"), + ]; + const allTypesExtendedPoolData = await Promise.all(promises); + + return allTypesExtendedPoolData.reduce((sum, data) => sum + (data.tvl ?? data.tvlAll), 0) } \ No newline at end of file diff --git a/test/stats.test.ts b/test/stats.test.ts index 962cc71f..c5261b65 100644 --- a/test/stats.test.ts +++ b/test/stats.test.ts @@ -17,92 +17,15 @@ const MAIN_POOLS_ETHEREUM = [ 'eurtusd', 'crveth', 'cvxeth', 'xautusd', 'spelleth', 'teth', ]; -const FACTORY_POOLS_ETHEREUM = [ - 'ibEUR+sEUR-f', 'ibKRW+sKRW-f', 'ibEUR+sEUR-2-f', - 'crvCRVsCRV-f', 'jGBP+TGBP-f', '2CRV-f', - 'crvCRV-f', 'ibbtc/sbtcCRV-f', 'OUSD3CRV-f', - 'aUSDC+aDAI-f', 'FEI3CRV3CRV-f', 'GrapeFUSD3CRV-f', - 'SifuETH3CRV-f', 'RC_INV3CRV-f', 'RC_xRULER3CRV-f', - 'RC_xCOVER3CRV-f', 'nUSD3CRV-f', 'cvxcrv-f', - 'USDM3CRV-f', 'mEUR-f', 'waUSD3CRV-f', - 'waBTC/sbtcCRV-f', 'DOLA3POOL3CRV-f', 'ibJPY+sJPY-f', - 'ibAUD+sAUD-f', 'ibGBP+sGBP-f', 'ibCHF+sCHF-f', - 'OPEN MATIC-f', 'EURN/EURT-f', 'sdCRV-f', - 'BTCpx/sbtcCRV-f', 'PWRD3CRV3CRV-f', 'sansUSDT-f', - 'alETH+ETH-f', '17PctCypt3CRV-f', '17PctCypt3CRV-2-f', - 'tbtc2/sbtcCRV-f', 'kusd3pool3CRV-f', 'tusd3pool3CRV-f', - 'PWRD3CRV-f', 'fUSD3CRV-f', 'TPD3CRV-f', - 'DEI3CRV-f', 'MIM-UST-f', 'ETH/vETH2-f', - 'QBITWELLS3CRV-f', 'QWell13CRV-f', 'bveCVX-CVX-f', - 'UST_whv23CRV-f', 'DSU+3Crv3CRV-f', 'DSU3CRV-f', - 'aETHb-f', 'D3-f', 'aMATICb-f', - 'pax-usdp3CRV-f', 'ibbtc/sbtcCRV-2-f', 'fxEUR_CRV-f', - 'ORK/sbtcCRV-f', 'agEUR/sEUR-f', 'ibZAR+ZARP-f', - '3DYDX3CRV-f', '3EURpool-f', 'tWETH+WETH-f', - 'XSTUSD3CRV-f', 'XIM3CRV3CRV-f', 'XIM3CRV-f', - 'RAMP rUSD3CRV-f', 'bhome3CRV-f', 'JPYC+ibJPY-f', - 'UST-FRAX-f', 'FEIPCV-1-f', 'bentcvx-f', - 'USX3CRV3CRV-f', 'ag+ib-EUR-f', 'tFRAX+FRAX-f', - 'ELONXSWAP3CRV-f', 'BEAN3CRV-f', 'USDV3CRV-f', - 'PARUSDC3CRV-f', 'baoUSD-3CRV-f', 'sUSD3CRV-f', - 'AETHV13CRV-f', -]; -const CRYPTO_FACTORY_POOLS_ETHEREUM = [ - 'FXSETH-fV2', 'FXSETH-2-fV2', - 'FXSETH-3-fV2', 'FXSETH-4-fV2', - 'BADGERWBTC-fV2', 'INVDOLA-fV2', - 'RAIFRAX-fV2', 'RAIETH-fV2', - 'YFIETH-fV2', 'palStkAAVE-fV2', - 'DYDXETH-fV2', 'SDTETH-fV2', - 'CADCUSDC-fV2', 'RAIAGEUR-fV2', - 'rp-eth-fV2', 'PARUSDC-fV2', - 'DUCKETH-fV2', 'BTRFLYETH-fV2', -]; +const FACTORY_POOLS_COUNT_ETHEREUM = 104; +const CRYPTO_FACTORY_POOLS_COUNT_ETHEREUM = 38; const MAIN_POOLS_POLYGON = [ 'aave', 'ren', 'atricrypto3', 'eurtusd' ]; -const FACTORY_POOLS_POLYGON = [ - 'usdtusdt-f', 'busd3CRV-f', 'busd23CRV-f', '2BTCWETH-f', - '3EUR-f', 'MAI3CRV-f', 'maave-f', 'MAI3pool-f', - 'creth-f', 'CWM-f', 'USDN3CRV-f', 'FRAX3CRV-f3CRV-f', - 'ibbtc3CRV-f', 'crvmat200-f', 'rUSD3CRV-f', 'ISusdt-f', - 'jSGD+XSGD-f', 'crvAUR-JRT-f', 'ibbtc3CRV-2-f', 'ibbtc3CRV-3-f', - 'crv2euro-f', 'AUSD3CRV-f', 'jSGD+XSGD-2-f', 'jCAD+CADC-f', - 'PK3CRV-f', 'PK3CRV-2-f', '4MT100-f', '4MT100-2-f', - 'illMATIK-f', 'MEG3CRV-f', 'TEST_POOL-f', 'USDT/FLSH3CRV-f', - 'moUSD3CRV-f', 'TxPUSTSw-f', 'StSwTrPo-f', '4eur-f', - 'aavesarco3CRV-f', '4eur-2-f', 'PopBTCETH-f', 'CJS-f', - 'wCJS-f', 'AMISam3CRV-f', 'AMISamUEM-f', 'AMISamUEB-f', - 'ATETRACRYP-f', 'am3CRVAMIS-f', 'sMVI3CRV-f', 'bMVI3CRV-f', - '3CRVUAMIS-f', 'aTri TONI3CRV-f', 'bBtc TONI3CRV-f', 'bBtc MARCO3CRV-f', - 'bTri MARCO3CRV-f', 'am3CRVItal-f', '2JPY-f', '2JPY-2-f', - 'jEUR-4eur-f', 'jEUR-4eur-2-f', 'AUSDAM3Crv3CRV-f', 'DOGGYUP3CRV-f', - 'DOGGYUP3CRV-2-f', 'aTest3CRV-f', 'PYD3CRV-f', 'renDAI-f', - 'renETH-f', 'renUSDC-f', 'renUSDT-f', 'LAMBO-USDT-f', - 'LAMBO-USDT-2-f', 'PYD3CRV-2-f', 'OOPSPoS-f', 'BRNR-MATIC-f', - 'agEUR+4eur-f', 'xDRINK-f', 'LUK3CRV-f', 'USDC+UST-f', - 'wTEST-f', 'fx5eur-f', '2chf-f', '2aud-f', - '2php-f', '2php-2-f', '2aud-2-f', 'fx5eur-2-f', - '2chf-2-f', '2jpy-f', 'crv3X-f', 'DeiUsdc3CRV-f', - 'DeiUsdc3CRV-2-f', 'NEXP-f', 'wust3CRV-f', 'fxEUR_4eur-f', - 'fxAUD_jAUD-f', 'izumi-f', 'MAI3CRV-2-f', 'MAI3CRV-3-f', - 'MAI3CRV-4-f', 'MAI3CRV-5-f', 'MAI3CRV-6-f', 'MAI33CRV-f', - 'fxEUR_jEUR-f', 'Corinthian3CRV-f', 'cvxfxs-f-f', 'SUSHPOOOL3CRV-f', - 'cOMI-f', 'cOMI-2-f', 'MAI3Pool3CRV-f', 'MAI+3Pool3CRV-f', - 'cvxcrv-f', 'MAI33CRV-2-f', 'Crypl3CRV-f', 'deUSDC-3P3CRV-f', - 'ABC3CRV3CRV-f', 'CRVALRTO-f', 'SoDeDAI3CRV-f', 'SoDeDAI3CRV-2-f', - 'SoDeDAI3CRV-3-f', 'SoHeCRV-f', 'CURVERTO3CRV-f', 'CURVERTO3CRV-2-f', - 'AlertoCRV3-f', 'crv3erto3CRV-f', 'AlertoCrv33CRV-f', 'LertoMatic-f', - 'MATIC/ALRT-f', 'MATIC/ALRT-2-f', 'MATIC/ALRT-3-f', 'Makerto-f', - 'Makerto-2-f', 'Binagon-f', 'BNBMATIC-f', 'BNBMATIC-2-f', - 'Binagon-2-f', 'IDEXUSDT3CRV-f', 'IDEXUSDT3CRV-2-f', '196.967-f', - '107-86-f', '33373430-f', 'SPARK-f', 'Lithereum-f', - 'Lithereum3CRV-f', 'PSOLM3CRV-f', 'PSOLM3CRV-2-f', 'PSOLM-f', - 'PSOLM3CRV-3-f', 'PSOLM3CRV-4-f', -]; +const FACTORY_POOLS_COUNT_POLYGON = 213; const checkNumber = (str: string) => { - const re = /-?\d+(\.\d+)?/g + const re = /-?\d+(\.\d+)?(e-\d+)?/g const match = str.match(re); return match && str === match[0] } @@ -173,19 +96,22 @@ describe('Stats test', async function () { poolStatsTest(poolName); } - for (const poolName of FACTORY_POOLS_ETHEREUM) { - poolStatsTest(poolName); + for (let i = 0; i < FACTORY_POOLS_COUNT_ETHEREUM; i++) { + poolStatsTest("factory-v2-" + i); } - for (const poolName of CRYPTO_FACTORY_POOLS_ETHEREUM) { - poolStatsTest(poolName); + for (let i = 0; i < CRYPTO_FACTORY_POOLS_COUNT_ETHEREUM; i++) { + poolStatsTest("factory-crypto-" + i); } // for (const poolName of MAIN_POOLS_POLYGON) { // poolStatsTest(poolName); // } // - // for (const poolName of FACTORY_POOLS_POLYGON) { - // poolStatsTest(poolName); + // for (let i = 0; i < FACTORY_POOLS_COUNT_POLYGON + 9; i++) { + // const blacklist = [126, 136, 155, 156, 157, 163, 187, 189, 195]; + // if (blacklist.includes(i)) continue; + // + // poolStatsTest("factory-v2-" + i); // } })