Skip to content

Commit af7fddb

Browse files
authored
feat: endpoint for active user (#17)
1 parent 9c37750 commit af7fddb

3 files changed

Lines changed: 162 additions & 1 deletion

File tree

apps/indexer/src/app/routes/_chain/routes.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import { TTokenSelected, tTokensSelectors } from '../../../database/selectors';
99
import {
1010
fetchPoolList,
1111
fetchPoolReserves,
12+
fetchUserReserves,
1213
selectPoolById,
1314
} from '../../../libs/loaders/money-market';
1415
import { paginationResponse, paginationSchema } from '../../../libs/pagination';
16+
import { transformUserReservesData } from '../../../libs/utils/user-reserves';
1517

1618
interface ReserveDataHumanized {
1719
originalId: number;
@@ -262,4 +264,88 @@ export default async function (fastify: FastifyInstance) {
262264
// };
263265
},
264266
);
267+
268+
fastify.withTypeProvider<ZodTypeProvider>().get(
269+
'/money-market/:pool/user/:address/lendings',
270+
{
271+
schema: {
272+
querystring: paginationSchema,
273+
params: z.object({
274+
pool: z.string(),
275+
address: z.string(),
276+
}),
277+
},
278+
config: {
279+
cache: true,
280+
},
281+
},
282+
async (
283+
req: FastifyRequest<{ Params: { pool: string; address: string } }>,
284+
reply,
285+
) => {
286+
const pools = await fetchPoolList(req.chain.chainId);
287+
const pool = selectPoolById(req.params.pool, pools);
288+
289+
if (!pool) return reply.notFound('Pool not found');
290+
291+
const userReservesRaw = await fetchUserReserves(
292+
req.chain.chainId,
293+
pool,
294+
req.params.address,
295+
);
296+
297+
const activePositions = userReservesRaw.filter(
298+
(r) => r.scaledATokenBalance > 0n,
299+
);
300+
301+
return transformUserReservesData({
302+
chainId: req.chain.chainId,
303+
userAddress: req.params.address,
304+
pool,
305+
reserves: activePositions,
306+
});
307+
},
308+
);
309+
310+
fastify.withTypeProvider<ZodTypeProvider>().get(
311+
'/money-market/:pool/user/:address/borrowings',
312+
{
313+
schema: {
314+
querystring: paginationSchema,
315+
params: z.object({
316+
pool: z.string(),
317+
address: z.string(),
318+
}),
319+
},
320+
config: {
321+
cache: true,
322+
},
323+
},
324+
async (
325+
req: FastifyRequest<{ Params: { pool: string; address: string } }>,
326+
reply,
327+
) => {
328+
const pools = await fetchPoolList(req.chain.chainId);
329+
const pool = selectPoolById(req.params.pool, pools);
330+
331+
if (!pool) return reply.notFound('Pool not found');
332+
333+
const userReservesRaw = await fetchUserReserves(
334+
req.chain.chainId,
335+
pool,
336+
req.params.address,
337+
);
338+
339+
const activeBorrows = userReservesRaw.filter(
340+
(r) => r.scaledVariableDebt > 0n || r.principalStableDebt > 0n,
341+
);
342+
343+
return transformUserReservesData({
344+
chainId: req.chain.chainId,
345+
userAddress: req.params.address,
346+
pool,
347+
reserves: activeBorrows,
348+
});
349+
},
350+
);
265351
}

apps/indexer/src/libs/loaders/money-market.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ const uiPoolDataProviderAbi = [
353353
},
354354
] as const;
355355

356-
type PoolDefinition = {
356+
export type PoolDefinition = {
357357
id: string | 'default';
358358
name: string;
359359
logoURI: string;
@@ -407,3 +407,21 @@ export async function fetchPoolReserves(
407407
args: [pool.poolAddressesProvider],
408408
});
409409
}
410+
411+
export async function fetchUserReserves(
412+
chainId: ChainSelector,
413+
pool: PoolDefinition,
414+
user: string,
415+
) {
416+
const chain = chains.get(chainId);
417+
if (!chain) {
418+
throw new Error(`Unsupported chain: ${chainId}`);
419+
}
420+
421+
return chain.rpc.readContract({
422+
address: pool.uiPoolDataProvider,
423+
abi: uiPoolDataProviderAbi,
424+
functionName: 'getUserReservesData',
425+
args: [pool.poolAddressesProvider, user as Address],
426+
});
427+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { areAddressesEqual } from '@sovryn/slayer-shared';
2+
import { and, eq, inArray } from 'drizzle-orm';
3+
import { client } from '../../database/client';
4+
import { tTokens } from '../../database/schema';
5+
import { tTokensSelectors } from '../../database/selectors';
6+
import { PoolDefinition } from '../loaders/money-market';
7+
8+
export async function transformUserReservesData({
9+
chainId,
10+
userAddress,
11+
pool,
12+
reserves,
13+
}: {
14+
chainId: number;
15+
userAddress: string;
16+
pool: PoolDefinition;
17+
reserves: Array<{
18+
underlyingAsset: string;
19+
scaledATokenBalance: bigint;
20+
usageAsCollateralEnabledOnUser: boolean;
21+
stableBorrowRate: bigint;
22+
scaledVariableDebt: bigint;
23+
principalStableDebt: bigint;
24+
stableBorrowLastUpdateTimestamp: bigint;
25+
}>;
26+
}) {
27+
if (!reserves.length) {
28+
return { data: [], count: 0 };
29+
}
30+
31+
const tokens = await client.query.tTokens.findMany({
32+
columns: tTokensSelectors.columns,
33+
where: and(
34+
eq(tTokens.chainId, chainId),
35+
inArray(
36+
tTokens.address,
37+
reserves.map((i) => i.underlyingAsset.toLowerCase()),
38+
),
39+
),
40+
});
41+
42+
const data = reserves.map((pos) => ({
43+
id: `${chainId}-${pos.underlyingAsset}-${pool.address}-${userAddress}`.toLowerCase(),
44+
user: userAddress,
45+
pool,
46+
token: tokens.find((t) =>
47+
areAddressesEqual(t.address, pos.underlyingAsset),
48+
),
49+
scaledVariableDebt: pos?.scaledVariableDebt.toString(),
50+
principalStableDebt: pos?.principalStableDebt.toString(),
51+
stableBorrowRate: pos?.stableBorrowRate.toString(),
52+
stableBorrowLastUpdateTimestamp: pos.stableBorrowLastUpdateTimestamp,
53+
usageAsCollateralEnabledOnUser: pos.usageAsCollateralEnabledOnUser,
54+
}));
55+
56+
return { data, count: data.length };
57+
}

0 commit comments

Comments
 (0)