Skip to content

Commit ad1c82e

Browse files
creed-victorgrinry
andauthored
SOV-5257 money market list active positions (#18)
* chore: user position list * fix: use aave sdk for user balance calculations * feat: money market reserves info * fix: amount tables * fix: borrow APY data --------- Co-authored-by: Rytis Grincevicius <rytis.grincevicius@gmail.com>
1 parent af7fddb commit ad1c82e

File tree

38 files changed

+1168
-865
lines changed

38 files changed

+1168
-865
lines changed

apps/indexer/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@
138138
}
139139
},
140140
"dependencies": {
141+
"@aave/math-utils": "1.29.1",
142+
"@aave/contract-helpers": "1.29.1",
141143
"@bull-board/api": "6.13.0",
142144
"@bull-board/fastify": "6.13.0",
143145
"@fastify/autoload": "6.0.3",

apps/indexer/src/app/plugins/cache.ts

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,28 @@ import type {
77
import fp from 'fastify-plugin';
88
import { ENV } from '../../env';
99
import { encode } from '../../libs/encode';
10+
import { logger } from '../../libs/logger';
1011
import { createRedisConnection } from '../../libs/utils/redis';
1112

13+
// Custom JSON serialization to handle BigInt values
14+
function serializeWithBigInt(value: unknown): string {
15+
return JSON.stringify(value, (key, val) => {
16+
if (typeof val === 'bigint') {
17+
return { __type: 'bigint', value: val.toString() };
18+
}
19+
return val;
20+
});
21+
}
22+
23+
function deserializeWithBigInt<T>(json: string): T {
24+
return JSON.parse(json, (key, val) => {
25+
if (val && typeof val === 'object' && val.__type === 'bigint') {
26+
return BigInt(val.value);
27+
}
28+
return val;
29+
});
30+
}
31+
1232
const cacheRedisConnection = createRedisConnection(
1333
ENV.REDIS_URL,
1434
ENV.REDIS_CLUSTER_MODE,
@@ -106,7 +126,7 @@ const redisCachePlugin: FastifyPluginAsync<RedisCachePluginOptions> = async (
106126
req: FastifyRequest,
107127
): RouteCacheOptions | null => {
108128
const rawCfg = req.routeOptions.config.cache;
109-
if (!rawCfg) return null;
129+
if (!rawCfg || ENV.NO_CACHE) return null;
110130

111131
if (typeof rawCfg === 'boolean') {
112132
if (!rawCfg) return null;
@@ -142,15 +162,15 @@ const redisCachePlugin: FastifyPluginAsync<RedisCachePluginOptions> = async (
142162
(cfg.key?.(req) ??
143163
encode.sha256(
144164
`${routeUrl}:${req.raw.method}:` +
145-
`${JSON.stringify(req.query ?? {})}:${JSON.stringify(req.body ?? {})}`,
165+
`${JSON.stringify(req.params ?? {})}:${JSON.stringify(req.query ?? {})}:${JSON.stringify(req.body ?? {})}`,
146166
));
147167

148168
req.__cacheKey = key;
149169

150170
const cached = await redis.get(key);
151171
if (!cached) return;
152172

153-
const entry: CacheEntry = JSON.parse(cached);
173+
const entry: CacheEntry = deserializeWithBigInt(cached);
154174
const ageSec = (Date.now() - entry.storedAt) / 1000;
155175

156176
const isFresh = ageSec <= entry.ttlSeconds;
@@ -228,7 +248,7 @@ const redisCachePlugin: FastifyPluginAsync<RedisCachePluginOptions> = async (
228248
const cfg: RouteCacheOptions =
229249
typeof rawCfg === 'boolean' ? { enabled: rawCfg } : rawCfg;
230250

231-
if (cfg.enabled === false) return payload;
251+
if (cfg.enabled === false || ENV.NO_CACHE) return payload;
232252

233253
if (req.__cacheHit) {
234254
return payload;
@@ -260,7 +280,7 @@ const redisCachePlugin: FastifyPluginAsync<RedisCachePluginOptions> = async (
260280

261281
const expireSeconds = ttl + (cfg.staleTtlSeconds ?? defaultStaleTtl);
262282

263-
await redis.setex(key, expireSeconds, JSON.stringify(entry));
283+
await redis.setex(key, expireSeconds, serializeWithBigInt(entry));
264284

265285
// For "real" client requests (not internal revalidation), set MISS header
266286
if (req.headers['x-cache-revalidate'] !== '1') {
@@ -272,6 +292,53 @@ const redisCachePlugin: FastifyPluginAsync<RedisCachePluginOptions> = async (
272292
);
273293
};
274294

295+
export const maybeCache = async <T = unknown>(
296+
key: string,
297+
fn: () => Promise<T>,
298+
opts: Pick<RouteCacheOptions, 'ttlSeconds' | 'enabled'> = {},
299+
): Promise<T> => {
300+
const enabled = opts.enabled ?? true;
301+
302+
if (!enabled || ENV.NO_CACHE) {
303+
return fn();
304+
}
305+
306+
const redis = cacheRedisConnection;
307+
308+
const ttl = opts.ttlSeconds ?? 30; // 30 seconds
309+
310+
const cacheKey = 'maybe-cache:fn:' + encode.sha256(key);
311+
312+
const cached = await redis.get(cacheKey);
313+
314+
if (cached) {
315+
const entry: CacheEntry = deserializeWithBigInt(cached);
316+
const ageSec = (Date.now() - entry.storedAt) / 1000;
317+
318+
const isFresh = ageSec <= entry.ttlSeconds;
319+
320+
logger.info({ key, ageSec, ttl: entry.ttlSeconds }, 'Cache hit');
321+
322+
if (isFresh) {
323+
return entry.payload as T;
324+
}
325+
}
326+
327+
logger.info({ key }, 'Cache miss, invoking function');
328+
329+
const entry = {
330+
payload: await fn(),
331+
headers: {},
332+
statusCode: 200,
333+
storedAt: Date.now(),
334+
ttlSeconds: ttl,
335+
};
336+
337+
await redis.setex(cacheKey, ttl, serializeWithBigInt(entry));
338+
339+
return entry.payload as T;
340+
};
341+
275342
export default fp(redisCachePlugin, {
276343
name: 'cache-plugin',
277344
});

0 commit comments

Comments
 (0)