diff --git a/package.json b/package.json index 19eeaad..114c9a7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "main": "index.js", "license": "MIT", "dependencies": { - "@gearbox-protocol/sdk-gov": "^2.32.0", "axios": "^1.7.9", "cors": "^2.8.5", "dotenv": "^16.4.7", diff --git a/src/apy/curveAPY.ts b/src/apy/curveAPY.ts index 2ea420d..07afa0f 100644 --- a/src/apy/curveAPY.ts +++ b/src/apy/curveAPY.ts @@ -1,17 +1,8 @@ import { - curveTokens, - NetworkType, - PartialRecord, - PERCENTAGE_FACTOR, - SupportedToken, - tokenDataByNetwork, - TypedObjectUtils, - CHAINS, + APYResult, ApyDetails, getTokenAPY, NetworkType, NOT_DEPLOYED, -} from "@gearbox-protocol/sdk-gov"; -import { APYResult, ApyDetails, getTokenAPY } from "./type"; -import { CurveLPToken } from "@gearbox-protocol/sdk-gov/lib/tokens/curveLP"; -import { GearboxToken } from "@gearbox-protocol/sdk-gov/lib/tokens/gear"; +} from "./type"; +import { TokenStore } from "./token_store"; import axios from "axios"; import { Address } from "viem"; @@ -86,7 +77,6 @@ interface CurvePoolDataResponse { type PoolRecord = Record; type VolumeRecord = Record; -type CurveAPYTokens = CurveLPToken | GearboxToken; const GEAR_POOL = "0x5Be6C45e2d074fAa20700C49aDA3E88a1cc0025d".toLowerCase(); @@ -94,7 +84,7 @@ const CURVE_CHAINS: Record = { Arbitrum: "arbitrum", Mainnet: "ethereum", Optimism: "optimism", - Base: "base", + // Base: "base", }; // const CRYPTO = "https://api.curve.fi/api/getPools/${CURVE_CHAINS[n]}/crypto"; @@ -116,8 +106,7 @@ const getFactoryStableNgURL = (n: NetworkType) => `https://api.curve.fi/api/getPools/${CURVE_CHAINS[n]}/factory-stable-ng`; -export async function getCurveAPY(network: NetworkType): Promise { - const currentTokens = tokenDataByNetwork[network]; +export async function getCurveAPY(network: NetworkType, store: TokenStore): Promise { const { mainnetVolumes, mainnetFactoryPools, volumes, pools } = await getCurvePools(network); @@ -133,20 +122,30 @@ export async function getCurveAPY(network: NetworkType): Promise { const { poolData = [] } = poolCategory?.data?.data || {}; poolData.forEach(p => { - acc[p.lpTokenAddress.toLowerCase()] = p; + // if (p.lpTokenAddress.toLowerCase() == "0xed4064f376cb8d68f770fb1ff088a3d0f3ff5c4d") { + // console.log(p) + // } + if (!p.symbol || p.usdTotal == 0) { + return; + } + let sym = p.symbol.toLowerCase(); + acc[sym] = p; + if (sym.endsWith("-f")) { + acc[sym.slice(0, -2)] = p; + } }); return acc; }, {}); - let curveAPY = TypedObjectUtils.entries(curveTokens).reduce( - (acc, [curveSymbol]) => { - const address = (currentTokens?.[curveSymbol] || "").toLowerCase(); - if (address == NOT_DEPLOYED) { + let crv = store.getBysymbol(network, "CRV"); + let curveAPY = curveTokens.reduce( + (acc, curveSymbol) => { + + const pool = poolDataByAddress[curveSymbol.toLowerCase()]; + if (!pool) { return acc; } - - const pool = poolDataByAddress[address]; const volume = volumeByAddress[(pool?.address || "").toLowerCase()]; const baseAPY = volume?.latestDailyApyPcent || 0; @@ -163,15 +162,15 @@ export async function getCurveAPY(network: NetworkType): Promise { ); let curveAPYs = [{ - reward: address as Address, + reward: pool.lpTokenAddress, symbol: curveSymbol, value: curveAPYToBn(baseAPY), }, { - reward: currentTokens?.["CRV"], - symbol: "CRV", + reward: crv.address, + symbol: crv.symbol, value: curveAPYToBn(maxCrv), }, ...extraRewards]; - acc[address as Address] = getTokenAPY(curveSymbol, curveAPYs); + acc[pool.lpTokenAddress] = getTokenAPY(curveSymbol, curveAPYs); return acc; }, {} as APYResult, @@ -201,8 +200,8 @@ export async function getCurveAPY(network: NetworkType): Promise { value: curveAPYToBn(gearVolume?.latestDailyApyPcent || 0), }, { - reward: currentTokens?.["CRV"], - symbol: "CRV", + reward: crv.address, + symbol: crv.symbol, value: curveAPYToBn(Math.max(...(gearPool?.gaugeCrvApy || []), 0)), }, ...(gearPool?.gaugeRewards || []).map( @@ -231,6 +230,7 @@ async function getCurvePools(network: NetworkType) { axios.get(getCryptoURL(network)), axios.get(getFactoryTriCryptoURL(network)), axios.get(getFactoryCrvUsdURL(network)), + axios.get(getFactoryStableNgURL(network)), ]); return { mainnetVolumes: volumes, @@ -302,3 +302,49 @@ export async function getCurveGearPool(): Promise { const gearPool = poolDataByAddress[GEAR_POOL]; return gearPool; } + + +const curveTokens = [ + "3Crv", + "steCRV", + "crvPlain3andSUSD", + "crvFRAX", + "crvCRVETH", + "crvCVXETH", + "crvUSDTWBTCWETH", + "LDOETH", + "crvUSDUSDC", + "crvUSDUSDT", + "crvUSDFRAX", + "crvUSDETHCRV", + "rETH-f", + "USDeUSDC", + "FRAXUSDe", + "USDecrvUSD", + "USDeDAI", + "MtEthena", + "GHOUSDe", + "pufETHwstE", + "GHOcrvUSD", + "ezETHWETH", + "ezpzETH", + "LBTCWBTC", + "eBTCWBTC", + "pumpBTCWBTC", + "TriBTC", + "FRAXsDAI", + "DOLAsUSDe", + "DOLAFRAXBP3CRV-f", + "crvUSDDOLA-f", + "crvUsUSDe", + "llamathena", + "2CRV", + "3c-crvUSD", + "crvUSDC", + "crvUSDT", + "crvUSDC-e", + "USDEUSDC", + "3CRV", + "wstETHCRV", + "gusd3CRV", // for mainnet +]; \ No newline at end of file diff --git a/src/apy/defiLamaAPY.ts b/src/apy/defiLamaAPY.ts index 79a370e..a8bcd84 100644 --- a/src/apy/defiLamaAPY.ts +++ b/src/apy/defiLamaAPY.ts @@ -1,15 +1,12 @@ import { NetworkType, - PartialRecord, - PERCENTAGE_FACTOR, - TypedObjectUtils, - tokenDataByNetwork, NOT_DEPLOYED, -} from "@gearbox-protocol/sdk-gov"; +} from "./type"; import axios from "axios"; // import { GearboxBackendApi } from "../core/endpoint"; -import { APYResult, getTokenAPY, TokensWithAPY } from "."; +import { APYResult, getTokenAPY } from "."; +import { TokenStore } from "./token_store"; interface LamaItem { apy: number; @@ -29,7 +26,7 @@ interface LamaResponse { } export async function getDefiLamaAPY( - network: NetworkType, + network: NetworkType, store: TokenStore ): Promise { const currentNormal = NORMAL_TO_LAMA[network]; const idList = Object.values(currentNormal); @@ -46,30 +43,28 @@ export async function getDefiLamaAPY( - const currentTokens = tokenDataByNetwork[network]; - const allAPY = TypedObjectUtils.entries( - currentNormal as Record, - ).reduce((acc, [symbol, pool]) => { - const { apy = 0 } = itemsRecord[pool] || {}; - let address = currentTokens?.[symbol]; - if (address != NOT_DEPLOYED) { - acc[address] = getTokenAPY(symbol, [{ - reward: address, - symbol: symbol, - value: apy, - }]); - } + const allAPY = + Object.entries(currentNormal).reduce((acc, [symbol, pool]) => { + const { apy = 0 } = itemsRecord[pool] || {}; + let token = store.getBysymbol(network, symbol); + if (token.address != NOT_DEPLOYED) { + acc[token.address] = getTokenAPY(token.symbol, [{ + reward: token.address, + symbol: symbol, + value: apy, + }]); + } - return acc; - }, {}); + return acc; + }, {}); return allAPY; } const NORMAL_TO_LAMA: Record< NetworkType, - PartialRecord + Record // symbol to pool > = { Mainnet: { sDAI: "c8a24fee-ec00-4f38-86c0-9f6daebc4225", @@ -107,6 +102,6 @@ const NORMAL_TO_LAMA: Record< sfrxETH: "77020688-e1f9-443c-9388-e51ace15cc32", }, - Base: {}, + // Base: {}, }; diff --git a/src/apy/index.ts b/src/apy/index.ts index 69f07b5..c7a823f 100644 --- a/src/apy/index.ts +++ b/src/apy/index.ts @@ -1,172 +1,3 @@ -import { - isLPToken, - LPTokens, - PartialRecord, - SupportedToken, -} from "@gearbox-protocol/sdk-gov"; - -// all extra tokens -type ExtraFarmTokens = Extract< - SupportedToken, - | "STETH" - | "rETH" - | "osETH" - | "cbETH" - | "wstETH" - | "weETH" - | "ezETH" - | "sfrxETH" - | "USDe" - | "rsETH" - | "rswETH" - | "pufETH" - | "pzETH" - | "rstETH" - | "steakLRT" - | "amphrETH" - | "LBTC" - | "Re7LRT" - | "PT_ezETH_26DEC2024" - | "PT_eETH_26DEC2024" - | "PT_sUSDe_26DEC2024" - | "PT_eBTC_26DEC2024" - | "PT_LBTC_27MAR2025" - | "eBTC" - | "PT_cornLBTC_26DEC2024" - | "PT_corn_eBTC_27MAR2025" - | "PT_corn_pumpBTC_26DEC2024" - | "pumpBTC" - | "PT_sUSDe_27MAR2025" ->; - -// tokens with apy among them -type ExtraTokensWithAPY = Extract< - ExtraFarmTokens, - | "STETH" - | "rETH" - | "osETH" - | "cbETH" - | "wstETH" - | "sfrxETH" - | "pzETH" - | "ezETH" - | "Re7LRT" - | "rsETH" - | "weETH" - | "rswETH" - | "rstETH" - | "steakLRT" - | "amphrETH" - | "pufETH" - | "PT_ezETH_26DEC2024" - | "PT_eETH_26DEC2024" - | "PT_sUSDe_26DEC2024" - | "PT_eBTC_26DEC2024" - | "PT_LBTC_27MAR2025" - | "PT_cornLBTC_26DEC2024" - | "PT_corn_eBTC_27MAR2025" - | "PT_corn_pumpBTC_26DEC2024" - | "PT_sUSDe_27MAR2025" ->; - -// LRT & LST tokens among them -type LRTAndLSTTokens = Exclude; - -const EXTRA_FARM_TOKENS: Record = { - STETH: true, - rETH: true, - osETH: true, - cbETH: true, - wstETH: true, - weETH: true, - ezETH: true, - sfrxETH: true, - USDe: true, - rsETH: true, - rswETH: true, - pufETH: true, - pzETH: true, - rstETH: true, - steakLRT: true, - amphrETH: true, - LBTC: true, - Re7LRT: true, - PT_ezETH_26DEC2024: true, - PT_eETH_26DEC2024: true, - PT_sUSDe_26DEC2024: true, - PT_eBTC_26DEC2024: true, - PT_LBTC_27MAR2025: true, - eBTC: true, - PT_cornLBTC_26DEC2024: true, - PT_corn_eBTC_27MAR2025: true, - PT_corn_pumpBTC_26DEC2024: true, - pumpBTC: true, - PT_sUSDe_27MAR2025: true, -}; - -export const isExtraFarmToken = (t: unknown): t is ExtraFarmTokens => { - if (typeof t !== "string") return false; - return !!EXTRA_FARM_TOKENS[t as ExtraFarmTokens]; -}; - -const EXTRA_TOKENS_WITH_APY: Record = { - STETH: true, - osETH: true, - rETH: true, - wstETH: true, - cbETH: true, - sfrxETH: true, - - pzETH: true, - ezETH: true, - Re7LRT: true, - rsETH: true, - weETH: true, - rswETH: true, - rstETH: true, - steakLRT: true, - amphrETH: true, - pufETH: true, - - PT_ezETH_26DEC2024: true, - PT_eETH_26DEC2024: true, - PT_sUSDe_26DEC2024: true, - PT_eBTC_26DEC2024: true, - PT_LBTC_27MAR2025: true, - - PT_cornLBTC_26DEC2024: true, - PT_corn_eBTC_27MAR2025: true, - - PT_corn_pumpBTC_26DEC2024: true, - PT_sUSDe_27MAR2025: true, -}; - -const isExtraTokenWithAPY = (t: unknown): t is ExtraTokensWithAPY => { - if (typeof t !== "string") return false; - return !!EXTRA_TOKENS_WITH_APY[t as ExtraTokensWithAPY]; -}; - -const { USDe, ...rest } = EXTRA_FARM_TOKENS; -const LRT_LST: Record = rest; - -export const isLRT_LSTToken = (t: unknown): t is LRTAndLSTTokens => { - if (typeof t !== "string") return false; - return !!LRT_LST[t as LRTAndLSTTokens]; -}; - -export type TokensWithAPY = LPTokens | ExtraTokensWithAPY; -export type TokensWithApyRecord = PartialRecord; - -export const isTokenWithAPY = (t: unknown): t is TokensWithAPY => { - if (typeof t !== "string") return false; - return isLPToken(t) || isExtraTokenWithAPY(t); -}; - -export type AllLPTokens = LPTokens | ExtraFarmTokens; - -export const isFarmToken = (t: unknown): t is AllLPTokens => { - return isLPToken(t) || isExtraFarmToken(t); -}; export * from "./curveAPY"; export * from "./defiLamaAPY"; diff --git a/src/apy/lidoAPY.ts b/src/apy/lidoAPY.ts index b62e3e7..875c799 100644 --- a/src/apy/lidoAPY.ts +++ b/src/apy/lidoAPY.ts @@ -1,9 +1,8 @@ -import { PartialRecord, PERCENTAGE_FACTOR, tokenDataByNetwork, NetworkType, NOT_DEPLOYED } from "@gearbox-protocol/sdk-gov"; +import { NetworkType, NOT_DEPLOYED } from "./type"; import axios from "axios"; import { Address } from "viem"; import { APYResult, getTokenAPY } from "./type"; - -import { TokensWithAPY } from "."; +import { TokenStore } from "./token_store"; interface Apy { timeUnix: number; @@ -26,36 +25,22 @@ interface LidoApyResponse { const LIDO_URL = "https://eth-api.lido.fi/v1/protocol/steth/apr/sma"; -export type LidoAPYResult = PartialRecord< - Extract, - number ->; -export async function getLidoAPY(network: NetworkType): Promise { +export async function getLidoAPY(network: NetworkType, store: TokenStore): Promise { const res = await axios.get(LIDO_URL); const { smaApr = 0 } = res?.data?.data || {}; - const r = Math.round(smaApr * Number(PERCENTAGE_FACTOR)); - - const currentTokens = tokenDataByNetwork[network]; - let result: APYResult = {}; - let STETH = currentTokens?.["STETH"]; - if (STETH != NOT_DEPLOYED) { - result[STETH] = getTokenAPY("STETH", [{ - reward: STETH, - symbol: "STETH", - value: smaApr, - }]); - } - let wstETH = currentTokens?.["wstETH"]; - if (wstETH != NOT_DEPLOYED) { - result[wstETH] = getTokenAPY("wstETH", - [{ - reward: wstETH, - symbol: "wstETH", + let STETH = store.getBysymbol(network, "STETH"); + let wstETH = store.getBysymbol(network, "wstETH"); + for (var token of [STETH, wstETH]) { + if (token.address != NOT_DEPLOYED) { + result[token.address] = getTokenAPY(token.symbol, [{ + reward: token.address, + symbol: token.symbol, value: smaApr, }]); + } } return result; } diff --git a/src/apy/pendleAPY.ts b/src/apy/pendleAPY.ts index 571a9a1..b6e9896 100644 --- a/src/apy/pendleAPY.ts +++ b/src/apy/pendleAPY.ts @@ -1,14 +1,9 @@ import { - PartialRecord, - PERCENTAGE_DECIMALS, - PERCENTAGE_FACTOR, - SupportedToken, + APYResult, PERCENTAGE_DECIMALS, NetworkType, - tokenDataByNetwork, -} from "@gearbox-protocol/sdk-gov"; +} from "./type"; import axios from "axios"; -import { APYResult } from "./type"; - +import { TokenStore } from "./token_store"; interface APYResponse { underlyingInterestApy: number; } @@ -17,7 +12,7 @@ const getAPYURL = () => "https://api-v2.pendle.finance/core/v2/1/markets/0xcdd26eb5eb2ce0f203a84553853667ae69ca29ce/data"; -export async function getPendleAPY(network: NetworkType): Promise { +export async function getPendleAPY(network: NetworkType, store: TokenStore): Promise { if (network != "Mainnet") { return {}; } @@ -28,15 +23,14 @@ export async function getPendleAPY(network: NetworkType): Promise { const rate = apyInfo?.underlyingInterestApy || 0; let result: APYResult = {}; - let currentTokens = tokenDataByNetwork[network]; - let pendle = currentTokens?.["PENDLE"]; + let pendle = store.getBysymbol(network, "PENDLE"); - result[pendle] = { - symbol: "PENDLE", + result[pendle.address] = { + symbol: pendle.symbol, apys: [{ value: numberToAPY(Number(rate)), - reward: pendle, - symbol: "PENDLE", + reward: pendle.address, + symbol: pendle.symbol, }] }; // diff --git a/src/apy/skyAPY.ts b/src/apy/skyAPY.ts index fac77c6..d17e4ef 100644 --- a/src/apy/skyAPY.ts +++ b/src/apy/skyAPY.ts @@ -1,11 +1,8 @@ import { NetworkType, - PartialRecord, PERCENTAGE_DECIMALS, - PERCENTAGE_FACTOR, - SupportedToken, - tokenDataByNetwork, -} from "@gearbox-protocol/sdk-gov"; +} from "./type"; +import { TokenStore } from "./token_store"; import { APYResult, getTokenAPY } from "./type"; import axios from "axios"; @@ -21,7 +18,7 @@ const getAPYURL = () => "https://info-sky.blockanalitica.com/api/v1/overall/"; // type SkyTokens = Extract; -export async function getSkyAPY(network: NetworkType): Promise { +export async function getSkyAPY(network: NetworkType, store: TokenStore): Promise { if (network != "Mainnet") { return {}; } @@ -31,19 +28,19 @@ export async function getSkyAPY(network: NetworkType): Promise { const savingsRate = apyInfo?.sky_savings_rate_apy || 0; const farmRate = apyInfo?.sky_farm_apy || 0; - let currentTokens = tokenDataByNetwork[network]; - let sUSDS = currentTokens?.["sUSDS"]; - let stkUSDS = currentTokens?.["stkUSDS"]; + let sUSDS = store.getBysymbol(network, "sUSDS"); + let stkUSDS = store.getBysymbol(network, "stkUSDS"); let response: APYResult = {}; - response[sUSDS] = getTokenAPY("sUSDS", [{ - reward: sUSDS, - symbol: "sUSDS", + // + response[sUSDS.address] = getTokenAPY(sUSDS.symbol, [{ + reward: sUSDS.address, + symbol: sUSDS.symbol, value: numberToAPY(Number(savingsRate)), }]); - - response[stkUSDS] = getTokenAPY("stkUSDS", [{ - reward: stkUSDS, - symbol: "stkUSDS", + // + response[stkUSDS.address] = getTokenAPY(stkUSDS.symbol, [{ + reward: stkUSDS.address, + symbol: stkUSDS.symbol, value: numberToAPY(Number(farmRate)), }]); return response; diff --git a/src/apy/token_store.ts b/src/apy/token_store.ts new file mode 100644 index 0000000..f6f0d17 --- /dev/null +++ b/src/apy/token_store.ts @@ -0,0 +1,81 @@ +import { NetworkType, NOT_DEPLOYED } from "./type"; +import { Address } from "viem"; + +interface Token { + symbol: string, + address: Address, +} +export class TokenStore { + data: Record> + + constructor() { + this.data = { + "Mainnet": { + // for sky + "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD": "sUSDS", + "0xcB5D10A57Aeb622b92784D53F730eE2210ab370E": "stkUSDS", + // for pendle + "0x808507121B80c02388fAd14726482e061B8da827": "PENDLE", + // + '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0': 'wstETH', + '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84': 'STETH', + // for defillama + "0x83F20F44975D03b1b09e64809B757c47f942BEeA": "sDAI", + "0xae78736Cd615f374D3085123A210448E74Fc6393": "rETH", + "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38": "osETH", + "0xDd1fE5AD401D4777cE89959b7fa587e569Bf125D": "auraB_rETH_STABLE_vault", + "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497": "sUSDe", + "0x276187f24D41745513cbE2Bd5dFC33a4d8CDc9ed": "stkcvxcrvFRAX", + "0x0Bf1626d4925F8A872801968be11c052862AC2D3": "stkcvxcrvUSDETHCRV", + "0x7376AD488AB2bd8dF7665d619A4148f0E5094813": "stkcvxcrvUSDFRAX", + "0xEE3EE8373384BBfea3227E527C1B9b4e7821273E": "stkcvxcrvUSDTWBTCWETH", + "0xDb4217B9C8DB788Aa3871d45B4BE6ac5D1FF8C49": "stkcvxcrvUSDUSDC", + "0x5C5e5117E26374870c80a5FA04c3f75a821440D6": "stkcvxcrvUSDUSDT", + "0xEe9085fC268F6727d5D4293dBABccF901ffDCC29": "PT_sUSDe_26DEC2024", + "0xf7906F274c174A52d444175729E3fa98f9bde285": "PT_ezETH_26DEC2024", + "0x6ee2b5E19ECBa773a352E5B21415Dc419A700d1d": "PT_eETH_26DEC2024", + "0xEc5a52C685CC3Ad79a6a347aBACe330d69e0b1eD": "PT_LBTC_27MAR2025", + "0xB997B3418935A1Df0F914Ee901ec83927c1509A0": "PT_eBTC_26DEC2024", + "0x332A8ee60EdFf0a11CF3994b1b846BBC27d3DcD6": "PT_cornLBTC_26DEC2024", + "0x44A7876cA99460ef3218bf08b5f52E2dbE199566": "PT_corn_eBTC_27MAR2025", + "0xa76f0C6e5f286bFF151b891d2A0245077F1Ad74c": "PT_corn_pumpBTC_26DEC2024", + "0xE00bd3Df25fb187d6ABBB620b3dfd19839947b81": "PT_sUSDe_27MAR2025", + // + '0xD533a949740bb3306d119CC777fa900bA034cd52': 'CRV', + + }, + "Optimism": { + "0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb": "wstETH", + // + "0x9Bcef72be871e61ED4fBbc7630889beE758eb81D": "rETH", + // + '0x0994206dfE8De6Ec6920FF4D779B0d950605Fb53': 'CRV', + }, + "Arbitrum": { + "0x5979D7b546E38E414F7E9822514be443A4800529": "wstETH", + // + "0xEC70Dcb4A1EFa46b8F2D97C310C9c4790ba5ffA8": "rETH", + '0x1DEBd73E752bEaF79865Fd6446b0c970EaE7732f': "cbETH", + '0x95aB45875cFFdba1E5f451B950bC2E42c0053f39': "sfrxETH", + // + '0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978': 'CRV', + }, + } + }; + + getBysymbol(network: NetworkType, symNeeded: string): Token { + for (var [addr, symbol] of Object.entries(this.data[network] || {})) { + if (symbol == symNeeded) { + return { + symbol: symbol, + address: addr as Address, + }; + } + } + console.log(`error: token not found for ${network} ${symNeeded}`); + return { + symbol: symNeeded, + address: NOT_DEPLOYED, + }; + } +} \ No newline at end of file diff --git a/src/apy/type.ts b/src/apy/type.ts index 344c33f..4edb9de 100644 --- a/src/apy/type.ts +++ b/src/apy/type.ts @@ -1,7 +1,3 @@ -import { - NetworkType, - PartialRecord, -} from "@gearbox-protocol/sdk-gov"; import { Address } from "viem"; export interface ApyDetails { reward: Address, @@ -10,15 +6,34 @@ export interface ApyDetails { lastUpdated?: string protocol?: string } + +export const supportedChains = [ + "Mainnet", + "Arbitrum", + "Optimism", +] as const; +export type NetworkType = (typeof supportedChains)[number]; +export const CHAINS = { + Mainnet: 1, + Arbitrum: 42161, + Optimism: 10, +}; +export declare const NOT_DEPLOYED = "0xNOT DEPLOYED"; + +export function isSupportedNetwork(chainId: number) { + return Object.values(CHAINS).includes(chainId); +} export interface TokenAPY { symbol: string, apys: ApyDetails[] } +export const PERCENTAGE_DECIMALS = 100; + export function getTokenAPY(sym: string, apys: ApyDetails[]) { return { symbol: sym, apys: apys, } } -export type APYResult = PartialRecord; \ No newline at end of file +export type APYResult = Record; \ No newline at end of file diff --git a/src/apy/yearnAPY.ts b/src/apy/yearnAPY.ts index fad7453..0d4a521 100644 --- a/src/apy/yearnAPY.ts +++ b/src/apy/yearnAPY.ts @@ -1,17 +1,12 @@ import { CHAINS, NetworkType, - PartialRecord, PERCENTAGE_DECIMALS, - PERCENTAGE_FACTOR, - tokenDataByNetwork, - TypedObjectUtils, - YearnLPToken, - yearnTokens, -} from "@gearbox-protocol/sdk-gov"; +} from './type'; + import axios from "axios"; import { Address } from "viem"; -import { APYResult, ApyDetails, getTokenAPY } from "./type"; +import { APYResult, getTokenAPY } from "./type"; interface YearnAPYData { apr: { @@ -28,37 +23,37 @@ type Response = Array; const getUrl = (chainId: number) => `https://ydaemon.yearn.finance/vaults/all?chainids=${chainId}&limit=2500`; -export type YearnAPYResult = PartialRecord; + +const yearnTokens = ["yvDAI", "yvUSDC", "yvUSDC_e", "yvWETH", "yvWBTC", "yvUSDT", "yvOP", "yvCurve-stETH", "yvCurve-FRAX"]; export async function getYearnAPY( network: NetworkType, ): Promise { try { const chainId = CHAINS[network]; - const currentTokens = tokenDataByNetwork[network]; const { data } = await axios.get(getUrl(chainId)); const dataByAddress = data.reduce>( (acc, d) => { - acc[d.address.toLowerCase()] = d; + acc[d.symbol.toLowerCase()] = d; return acc; }, {}, ); - const yearnAPY = TypedObjectUtils.entries( - yearnTokens, - ).reduce((acc, [yearnSymbol]) => { - const address = (currentTokens?.[yearnSymbol] || "").toLowerCase(); + const yearnAPY = yearnTokens.reduce((acc, yearnSymbol) => { - const data = dataByAddress[address]; + const data = dataByAddress[yearnSymbol.toLowerCase()]; + if (!data) { + return acc; + } const { apr: apy } = data || {}; const { netAPR } = apy || {}; const netApy = netAPR || 0; - acc[address as Address] = getTokenAPY(yearnSymbol, [{ - reward: address as Address, + acc[data.address as Address] = getTokenAPY(yearnSymbol, [{ + reward: data.address as Address, symbol: yearnSymbol, value: numberToAPY(netApy), }]); diff --git a/src/endpoints.ts b/src/endpoints.ts index d43a470..47eb779 100644 --- a/src/endpoints.ts +++ b/src/endpoints.ts @@ -1,7 +1,4 @@ -import { ApyDetails } from './apy'; -import { - isSupportedNetwork, -} from "@gearbox-protocol/sdk-gov"; +import { ApyDetails, isSupportedNetwork } from './apy'; import { Address, isAddress } from 'viem'; import { Fetcher } from './fetcher'; diff --git a/src/fetcher.ts b/src/fetcher.ts index 0f28e44..792bd41 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -1,52 +1,63 @@ import { getCurveAPY, getYearnAPY, TokenAPY, getLidoAPY, getSkyAPY, getPendleAPY, getDefiLamaAPY, ApyDetails } from './apy'; +import { TokenStore } from './apy/token_store'; import { NetworkType, supportedChains, CHAINS, -} from "@gearbox-protocol/sdk-gov"; + APYResult, +} from "./apy/type"; import moment from 'moment'; import { Address } from "viem"; +function log(network: NetworkType, protocols: string[], allProtocolAPYs: APYResult[]) { + // logs + + let s = ""; + for (var ind in allProtocolAPYs) { + let t = ""; + Object.entries(allProtocolAPYs[ind]).forEach(([k, v]) => { + t += v?.symbol + ", " + }) + s += ` ${protocols[ind]}:${Object.keys(allProtocolAPYs[ind]).length} ` + if (t != "") { + console.log(protocols[ind] + ": " + t) + } + } + console.log(`Fetched ${s} for ${network}`) + // +} export class Fetcher { public cache: Record> + store: TokenStore constructor() { this.cache = {}; + this.store = new TokenStore(); } async getnetworkTokens(network: NetworkType) { let ans: Record = {}; const [...allProtocolAPYs] = await Promise.all([ - getCurveAPY(network), + getCurveAPY(network, this.store), getYearnAPY(network), - getSkyAPY(network), - getPendleAPY(network), - getLidoAPY(network), - getDefiLamaAPY(network), + getSkyAPY(network, this.store), + getPendleAPY(network, this.store), + getLidoAPY(network, this.store), + getDefiLamaAPY(network, this.store), ]); let protocols = ["Curve", "Yearn", "Sky", "Pendle", "Lido", "Defillama"]; - let s = ""; - for (var ind in protocols) { - let t = ""; - Object.entries(allProtocolAPYs[ind]).forEach(([k, v]) => { - t += v?.symbol + ", " - }) - s += ` ${protocols[ind]}:${Object.keys(allProtocolAPYs[ind]).length} ` - if (t != "") { - console.log(protocols[ind] + ": " + t) - } - } - console.log(`Fetched ${s} for ${network}`) + log(network, protocols, allProtocolAPYs) + let time = moment().utc().format();; allProtocolAPYs.forEach((networkAPY, networkInd) => { Object.entries(networkAPY).forEach(([token, newAPYs]) => { newAPYs?.apys.forEach((entry: ApyDetails) => { entry.lastUpdated = time; entry.protocol = protocols[networkInd] - // entry.symbol = entry.symbol.toLowerCase(); + entry.reward = entry.reward.toLowerCase() as Address; }) // - let tokenAddr = token as Address; + let tokenAddr = token.toLowerCase() as Address; if (tokenAddr in ans) { ans[tokenAddr]?.apys.push(...(newAPYs?.apys!)); } else { @@ -59,10 +70,7 @@ export class Fetcher { async run() { - for (var network of supportedChains) { - if (network == "Base") { - continue; - } + for (var network of Object.values(supportedChains)) { let chainId = CHAINS[network as NetworkType]; let apys = await this.getnetworkTokens(network as NetworkType); this.cache[chainId] = apys; diff --git a/yarn.lock b/yarn.lock index 420fa29..6540370 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@adraffy/ens-normalize@1.10.1": - version "1.10.1" - resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz" - integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== - "@adraffy/ens-normalize@^1.10.1": version "1.11.0" resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz" @@ -17,22 +12,6 @@ resolved "https://registry.npmjs.org/@gearbox-protocol/prettier-config/-/prettier-config-2.0.0.tgz" integrity sha512-9eDnexCnLPFUp0ZakuZVjRbOlBWYvTq8ZdeDryyxzKUC3zaA1xIFQ2PNq5RDNvtCNOVDMzGGZfvp++zvlLyuFQ== -"@gearbox-protocol/sdk-gov@^2.32.0": - version "2.33.1" - resolved "https://registry.npmjs.org/@gearbox-protocol/sdk-gov/-/sdk-gov-2.33.1.tgz" - integrity sha512-FqGrlelRRg9b/uQdhfiqQzv8+4peqMOJgE7cuieSuxPmKes4bribLK21KHp1jwfNed4rBH5I3y09CkwLqztNzw== - dependencies: - ethers "6.12.1" - humanize-duration-ts "^2.1.1" - zod "^3.22.2" - -"@noble/curves@1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz" - integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== - dependencies: - "@noble/hashes" "1.3.2" - "@noble/curves@1.7.0", "@noble/curves@^1.4.0", "@noble/curves@^1.6.0", "@noble/curves@~1.7.0": version "1.7.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz" @@ -40,11 +19,6 @@ dependencies: "@noble/hashes" "1.6.0" -"@noble/hashes@1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" - integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== - "@noble/hashes@1.6.0": version "1.6.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz" @@ -129,7 +103,7 @@ resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== -"@types/node@*", "@types/node@18.15.13": +"@types/node@*": version "18.15.13" resolved "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== @@ -174,11 +148,6 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -aes-js@4.0.0-beta.5: - version "4.0.0-beta.5" - resolved "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz" - integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" @@ -352,19 +321,6 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -ethers@6.12.1: - version "6.12.1" - resolved "https://registry.npmjs.org/ethers/-/ethers-6.12.1.tgz" - integrity sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw== - dependencies: - "@adraffy/ens-normalize" "1.10.1" - "@noble/curves" "1.2.0" - "@noble/hashes" "1.3.2" - "@types/node" "18.15.13" - aes-js "4.0.0-beta.5" - tslib "2.4.0" - ws "8.5.0" - eventemitter3@5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" @@ -493,11 +449,6 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -humanize-duration-ts@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/humanize-duration-ts/-/humanize-duration-ts-2.1.1.tgz" - integrity sha512-TibNF2/fkypjAfHdGpWL/dmWUS0G6Qi+3mKyiB6LDCowbMy+PtzbgPTnFMNTOVAJXDau01jYrJ3tFoz5AJSqhA== - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -746,11 +697,6 @@ toidentifier@1.0.1: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tslib@2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" @@ -801,13 +747,3 @@ ws@8.18.0: version "8.18.0" resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - -ws@8.5.0: - version "8.5.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== - -zod@^3.22.2: - version "3.24.1" - resolved "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz" - integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==