|
1 | 1 | import got from '../../utils/got' |
2 | 2 | import { Address } from 'viem' |
3 | 3 | import { NetworkId } from '../../types/networkId' |
| 4 | +import { LRUCache } from 'lru-cache' |
4 | 5 |
|
5 | 6 | export type BeefyVault = { |
6 | 7 | id: string |
@@ -40,6 +41,14 @@ export type GovVault = BeefyVault & { |
40 | 41 | earnedTokenAddress: Address[] |
41 | 42 | } |
42 | 43 |
|
| 44 | +type BeefyData = Record<string, number | undefined> |
| 45 | + |
| 46 | +type BeefyPrices = BeefyData |
| 47 | + |
| 48 | +type BeefyTvls = BeefyData |
| 49 | + |
| 50 | +type BeefyApyBreakdown = Record<string, Record<string, number> | undefined> |
| 51 | + |
43 | 52 | export const NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID: Record< |
44 | 53 | NetworkId, |
45 | 54 | string | null |
@@ -75,80 +84,122 @@ const NETWORK_ID_TO_CHAIN_ID: { |
75 | 84 | [NetworkId['base-sepolia']]: 84532, |
76 | 85 | } |
77 | 86 |
|
78 | | -export async function getBeefyVaults( |
79 | | - networkId: NetworkId, |
80 | | -): Promise<{ vaults: BaseBeefyVault[]; govVaults: GovVault[] }> { |
81 | | - const [vaults, govVaults] = await Promise.all([ |
82 | | - got |
83 | | - .get( |
84 | | - `https://api.beefy.finance/harvestable-vaults/${NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID[networkId]}`, |
85 | | - ) |
86 | | - .json<BaseBeefyVault[]>(), |
87 | | - got |
88 | | - .get( |
89 | | - `https://api.beefy.finance/gov-vaults/${NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID[networkId]}`, |
90 | | - ) |
91 | | - .json<GovVault[]>(), |
92 | | - ]) |
93 | | - |
94 | | - return { |
95 | | - vaults, |
96 | | - govVaults, |
| 87 | +const CACHE_CONFIG = { |
| 88 | + max: 20, |
| 89 | + ttl: 5 * 1000, // 5 seconds |
| 90 | + allowStale: true, // allow stale-while-revalidate behavior |
| 91 | +} as const |
| 92 | + |
| 93 | +// Cache used for non-parametrized endpoints |
| 94 | +const urlCache = new LRUCache({ |
| 95 | + ...CACHE_CONFIG, |
| 96 | + fetchMethod: async (url: string) => { |
| 97 | + return got.get(url).json() |
| 98 | + }, |
| 99 | +}) |
| 100 | + |
| 101 | +const vaultsCache = new LRUCache< |
| 102 | + NetworkId, |
| 103 | + { vaults: BaseBeefyVault[]; govVaults: GovVault[] } |
| 104 | +>({ |
| 105 | + ...CACHE_CONFIG, |
| 106 | + fetchMethod: async (networkId: NetworkId) => { |
| 107 | + const [vaults, govVaults] = await Promise.all([ |
| 108 | + got |
| 109 | + .get( |
| 110 | + `https://api.beefy.finance/harvestable-vaults/${NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID[networkId]}`, |
| 111 | + ) |
| 112 | + .json<BaseBeefyVault[]>(), |
| 113 | + got |
| 114 | + .get( |
| 115 | + `https://api.beefy.finance/gov-vaults/${NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID[networkId]}`, |
| 116 | + ) |
| 117 | + .json<GovVault[]>(), |
| 118 | + ]) |
| 119 | + |
| 120 | + return { |
| 121 | + vaults, |
| 122 | + govVaults, |
| 123 | + } |
| 124 | + }, |
| 125 | +}) |
| 126 | + |
| 127 | +const pricesCache = new LRUCache<NetworkId, BeefyData>({ |
| 128 | + ...CACHE_CONFIG, |
| 129 | + fetchMethod: async (networkId: NetworkId): Promise<BeefyData> => { |
| 130 | + const [lpsPrices, tokenPrices, tokens] = await Promise.all([ |
| 131 | + got.get(`https://api.beefy.finance/lps`).json<BeefyData>(), |
| 132 | + got.get(`https://api.beefy.finance/prices`).json<BeefyData>(), |
| 133 | + got |
| 134 | + .get( |
| 135 | + `https://api.beefy.finance/tokens/${NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID[networkId]}`, |
| 136 | + ) |
| 137 | + .json< |
| 138 | + Record< |
| 139 | + string, // oracleId |
| 140 | + { |
| 141 | + // These are the fields we need, but there are more |
| 142 | + address: string |
| 143 | + oracle: string // examples: 'lps', 'tokens' |
| 144 | + oracleId: string |
| 145 | + } |
| 146 | + > |
| 147 | + >(), |
| 148 | + ]) |
| 149 | + |
| 150 | + // Combine lps prices with token prices |
| 151 | + return { |
| 152 | + ...lpsPrices, |
| 153 | + ...Object.fromEntries( |
| 154 | + Object.entries(tokens) |
| 155 | + .filter(([, { oracle }]) => oracle === 'tokens') |
| 156 | + .map(([, { address, oracleId }]) => [ |
| 157 | + address.toLowerCase(), |
| 158 | + tokenPrices[oracleId], |
| 159 | + ]), |
| 160 | + ), |
| 161 | + } |
| 162 | + }, |
| 163 | +}) |
| 164 | + |
| 165 | +export async function getApyBreakdown(): Promise<BeefyApyBreakdown> { |
| 166 | + const apyBreakdown = (await urlCache.fetch( |
| 167 | + 'https://api.beefy.finance/apy/breakdown/', |
| 168 | + )) as BeefyApyBreakdown | undefined |
| 169 | + if (!apyBreakdown) { |
| 170 | + throw new Error('Failed to fetch APY breakdown data') |
97 | 171 | } |
| 172 | + return apyBreakdown |
98 | 173 | } |
99 | 174 |
|
100 | | -export async function getBeefyPrices( |
101 | | - networkId: NetworkId, |
102 | | -): Promise<Record<string, number | undefined>> { |
103 | | - const [lpsPrices, tokenPrices, tokens] = await Promise.all([ |
104 | | - got |
105 | | - .get(`https://api.beefy.finance/lps`) |
106 | | - .json<Record<string, number | undefined>>(), |
107 | | - got |
108 | | - .get(`https://api.beefy.finance/prices`) |
109 | | - .json<Record<string, number | undefined>>(), |
110 | | - got |
111 | | - .get( |
112 | | - `https://api.beefy.finance/tokens/${NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID[networkId]}`, |
113 | | - ) |
114 | | - .json< |
115 | | - Record< |
116 | | - string, // oracleId |
117 | | - { |
118 | | - // These are the fields we need, but there are more |
119 | | - address: string |
120 | | - oracle: string // examples: 'lps', 'tokens' |
121 | | - oracleId: string |
122 | | - } |
123 | | - > |
124 | | - >(), |
125 | | - ]) |
126 | | - |
127 | | - // Combine lps prices with token prices |
128 | | - return { |
129 | | - ...lpsPrices, |
130 | | - ...Object.fromEntries( |
131 | | - Object.entries(tokens) |
132 | | - .filter(([, { oracle }]) => oracle === 'tokens') |
133 | | - .map(([, { address, oracleId }]) => [ |
134 | | - address.toLowerCase(), |
135 | | - tokenPrices[oracleId], |
136 | | - ]), |
137 | | - ), |
| 175 | +export async function getTvls(networkId: NetworkId): Promise<BeefyTvls> { |
| 176 | + const tvlResponse = (await urlCache.fetch( |
| 177 | + 'https://api.beefy.finance/tvl/', |
| 178 | + )) as Record<number, BeefyTvls> | undefined |
| 179 | + if (!tvlResponse) { |
| 180 | + throw new Error('Failed to fetch TVL data') |
138 | 181 | } |
| 182 | + return tvlResponse[NETWORK_ID_TO_CHAIN_ID[networkId]] ?? {} |
139 | 183 | } |
140 | 184 |
|
141 | | -export async function getApyBreakdown() { |
142 | | - return got |
143 | | - .get(`https://api.beefy.finance/apy/breakdown/`) |
144 | | - .json<Record<string, Record<string, number> | undefined>>() |
| 185 | +export async function getBeefyVaults( |
| 186 | + networkId: NetworkId, |
| 187 | +): Promise<{ vaults: BaseBeefyVault[]; govVaults: GovVault[] }> { |
| 188 | + const result = await vaultsCache.fetch(networkId) |
| 189 | + |
| 190 | + if (!result) { |
| 191 | + throw new Error('Failed to fetch vaults data') |
| 192 | + } |
| 193 | + |
| 194 | + return result |
145 | 195 | } |
146 | 196 |
|
147 | | -export async function getTvls( |
| 197 | +export async function getBeefyPrices( |
148 | 198 | networkId: NetworkId, |
149 | | -): Promise<Record<string, number | undefined>> { |
150 | | - const tvlResponse = await got |
151 | | - .get(`https://api.beefy.finance/tvl/`) |
152 | | - .json<Record<number, Record<string, number | undefined>>>() |
153 | | - return tvlResponse[NETWORK_ID_TO_CHAIN_ID[networkId]] ?? {} |
| 199 | +): Promise<BeefyPrices> { |
| 200 | + const prices = await pricesCache.fetch(networkId) |
| 201 | + if (!prices) { |
| 202 | + throw new Error('Failed to fetch prices data') |
| 203 | + } |
| 204 | + return prices |
154 | 205 | } |
0 commit comments