Skip to content

Commit 1467a53

Browse files
authored
chore(beefy): cache beefy API requests (#822)
### Description Cache Beefy API requests using stale-while-revalidate behavior, similar to #820 It will ensure that the cached result is always prioritized, while keeping cache updated every 5 seconds.
1 parent 504666a commit 1467a53

File tree

1 file changed

+118
-67
lines changed

1 file changed

+118
-67
lines changed

src/apps/beefy/api.ts

Lines changed: 118 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import got from '../../utils/got'
22
import { Address } from 'viem'
33
import { NetworkId } from '../../types/networkId'
4+
import { LRUCache } from 'lru-cache'
45

56
export type BeefyVault = {
67
id: string
@@ -40,6 +41,14 @@ export type GovVault = BeefyVault & {
4041
earnedTokenAddress: Address[]
4142
}
4243

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+
4352
export const NETWORK_ID_TO_BEEFY_BLOCKCHAIN_ID: Record<
4453
NetworkId,
4554
string | null
@@ -75,80 +84,122 @@ const NETWORK_ID_TO_CHAIN_ID: {
7584
[NetworkId['base-sepolia']]: 84532,
7685
}
7786

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')
97171
}
172+
return apyBreakdown
98173
}
99174

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')
138181
}
182+
return tvlResponse[NETWORK_ID_TO_CHAIN_ID[networkId]] ?? {}
139183
}
140184

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
145195
}
146196

147-
export async function getTvls(
197+
export async function getBeefyPrices(
148198
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
154205
}

0 commit comments

Comments
 (0)