From 881fb66a92ab0c71dcce3b7a0af65780a93b42f4 Mon Sep 17 00:00:00 2001 From: Rytis Grincevicius Date: Thu, 4 Dec 2025 14:08:11 +0200 Subject: [PATCH 1/5] chore: user position list --- apps/indexer/src/app/plugins/cache.ts | 2 +- apps/indexer/src/app/routes/_chain/routes.ts | 133 ++++++------ apps/indexer/src/app/routes/root.ts | 4 +- apps/indexer/src/libs/loaders/money-market.ts | 7 + apps/indexer/src/libs/server.ts | 33 +++ apps/indexer/src/libs/utils/user-reserves.ts | 57 ----- .../indexer/src/libs/validators/validators.ts | 11 + apps/indexer/src/main.ts | 11 +- apps/web-app/package.json | 1 + .../BorrowPositionsList.tsx | 4 +- .../AssetsTable/AssetsTable.constants.tsx | 23 --- .../components/AssetsTable/AssetsTable.tsx | 194 +++++++++--------- .../LendPositionsList/LendPositionsList.tsx | 6 +- .../AssetsTable/AssetsTable.constants.tsx | 16 -- .../components/AssetsTable/AssetsTable.tsx | 147 ++++++------- apps/web-app/src/integrations/wagmi/config.ts | 3 + apps/web-app/src/main.tsx | 2 + apps/web-app/src/routes/__root.tsx | 1 + apps/web-app/src/routes/money-market.tsx | 38 +++- .../money-market/money-market.manager.ts | 18 +- packages/sdk/src/types.ts | 18 ++ pnpm-lock.yaml | 45 +++- 22 files changed, 416 insertions(+), 358 deletions(-) create mode 100644 apps/indexer/src/libs/server.ts delete mode 100644 apps/indexer/src/libs/utils/user-reserves.ts create mode 100644 apps/indexer/src/libs/validators/validators.ts delete mode 100644 apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.constants.tsx delete mode 100644 apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.constants.tsx diff --git a/apps/indexer/src/app/plugins/cache.ts b/apps/indexer/src/app/plugins/cache.ts index cf37b30..2c2ac2d 100644 --- a/apps/indexer/src/app/plugins/cache.ts +++ b/apps/indexer/src/app/plugins/cache.ts @@ -142,7 +142,7 @@ const redisCachePlugin: FastifyPluginAsync = async ( (cfg.key?.(req) ?? encode.sha256( `${routeUrl}:${req.raw.method}:` + - `${JSON.stringify(req.query ?? {})}:${JSON.stringify(req.body ?? {})}`, + `${JSON.stringify(req.params ?? {})}:${JSON.stringify(req.query ?? {})}:${JSON.stringify(req.body ?? {})}`, )); req.__cacheKey = key; diff --git a/apps/indexer/src/app/routes/_chain/routes.ts b/apps/indexer/src/app/routes/_chain/routes.ts index e6d32af..2a54121 100644 --- a/apps/indexer/src/app/routes/_chain/routes.ts +++ b/apps/indexer/src/app/routes/_chain/routes.ts @@ -1,7 +1,6 @@ import { areAddressesEqual, Decimal } from '@sovryn/slayer-shared'; import { and, asc, eq, gte, inArray } from 'drizzle-orm'; -import { FastifyInstance, FastifyRequest } from 'fastify'; -import { ZodTypeProvider } from 'fastify-type-provider-zod'; +import { FastifyRequest } from 'fastify'; import z from 'zod'; import { client } from '../../../database/client'; import { tTokens } from '../../../database/schema'; @@ -13,7 +12,8 @@ import { selectPoolById, } from '../../../libs/loaders/money-market'; import { paginationResponse, paginationSchema } from '../../../libs/pagination'; -import { transformUserReservesData } from '../../../libs/utils/user-reserves'; +import { ZodFastifyInstance } from '../../../libs/server'; +import { ze } from '../../../libs/validators/validators'; interface ReserveDataHumanized { originalId: number; @@ -72,12 +72,12 @@ interface PoolBaseCurrencyHumanized { networkBaseTokenPriceDecimals: number; } -export default async function (fastify: FastifyInstance) { +export default async function (fastify: ZodFastifyInstance) { fastify.get('/', async (req) => { return { data: req.chain }; }); - fastify.withTypeProvider().get( + fastify.get( '/tokens', { schema: { @@ -106,7 +106,7 @@ export default async function (fastify: FastifyInstance) { }, ); - fastify.withTypeProvider().get( + fastify.get( '/money-market', { config: { @@ -129,7 +129,7 @@ export default async function (fastify: FastifyInstance) { }, ); - fastify.withTypeProvider().get( + fastify.get( '/money-market/:pool/reserves', { schema: { @@ -265,18 +265,22 @@ export default async function (fastify: FastifyInstance) { }, ); - fastify.withTypeProvider().get( - '/money-market/:pool/user/:address/lendings', + fastify.get( + '/money-market/:pool/user/:address/positions', { schema: { querystring: paginationSchema, params: z.object({ pool: z.string(), - address: z.string(), + address: ze.address, }), }, config: { - cache: true, + cache: { + enabled: true, + ttlSeconds: 10, + staleTtlSeconds: 15, + }, }, }, async ( @@ -288,64 +292,73 @@ export default async function (fastify: FastifyInstance) { if (!pool) return reply.notFound('Pool not found'); - const userReservesRaw = await fetchUserReserves( - req.chain.chainId, - pool, - req.params.address, - ); + const { 0: reservesRaw, 1: poolBaseCurrencyRaw } = + await fetchPoolReserves(req.chain.chainId, pool); - const activePositions = userReservesRaw.filter( - (r) => r.scaledATokenBalance > 0n, - ); + const { 0: userReservesRaw, 1: userEmodeCategoryId } = + await fetchUserReserves(req.chain.chainId, pool, req.params.address); - return transformUserReservesData({ - chainId: req.chain.chainId, - userAddress: req.params.address, - pool, - reserves: activePositions, + const tokens = await client.query.tTokens.findMany({ + columns: tTokensSelectors.columns, + where: and( + eq(tTokens.chainId, req.chain.chainId), + inArray( + tTokens.address, + userReservesRaw.map((i) => i.underlyingAsset.toLowerCase()), + ), + ), }); - }, - ); - fastify.withTypeProvider().get( - '/money-market/:pool/user/:address/borrowings', - { - schema: { - querystring: paginationSchema, - params: z.object({ - pool: z.string(), - address: z.string(), - }), - }, - config: { - cache: true, - }, - }, - async ( - req: FastifyRequest<{ Params: { pool: string; address: string } }>, - reply, - ) => { - const pools = await fetchPoolList(req.chain.chainId); - const pool = selectPoolById(req.params.pool, pools); + const baseCurrencyData: PoolBaseCurrencyHumanized = { + // this is to get the decimals from the unit so 1e18 = string length of 19 - 1 to get the number of 0 + marketReferenceCurrencyDecimals: + poolBaseCurrencyRaw.marketReferenceCurrencyUnit.toString().length - 1, + marketReferenceCurrencyPriceInUsd: + poolBaseCurrencyRaw.marketReferenceCurrencyPriceInUsd.toString(), + networkBaseTokenPriceInUsd: + poolBaseCurrencyRaw.networkBaseTokenPriceInUsd.toString(), + networkBaseTokenPriceDecimals: + poolBaseCurrencyRaw.networkBaseTokenPriceDecimals, + }; - if (!pool) return reply.notFound('Pool not found'); + const userReserves = userReservesRaw.map((userReserveRaw) => { + const token = tokens.find((t) => + areAddressesEqual(t.address, userReserveRaw.underlyingAsset), + ); + const reserve = reservesRaw.find((r) => + areAddressesEqual(r.underlyingAsset, userReserveRaw.underlyingAsset), + ); + return { + id: `${req.chain.chainId}-${req.params.address}-${userReserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), + pool, + token, + reserve, - const userReservesRaw = await fetchUserReserves( - req.chain.chainId, - pool, - req.params.address, - ); + suppliedBalance: Decimal.from( + userReserveRaw.scaledATokenBalance, + token?.decimals ?? 18, + ).toString(), - const activeBorrows = userReservesRaw.filter( - (r) => r.scaledVariableDebt > 0n || r.principalStableDebt > 0n, - ); + borrowedBalance: Decimal.from( + userReserveRaw.scaledVariableDebt, + token?.decimals ?? 18, + ).toString(), - return transformUserReservesData({ - chainId: req.chain.chainId, - userAddress: req.params.address, - pool, - reserves: activeBorrows, + underlyingAsset: userReserveRaw.underlyingAsset.toLowerCase(), + scaledATokenBalance: userReserveRaw.scaledATokenBalance.toString(), + usageAsCollateralEnabledOnUser: + userReserveRaw.usageAsCollateralEnabledOnUser, + stableBorrowRate: userReserveRaw.stableBorrowRate.toString(), + scaledVariableDebt: userReserveRaw.scaledVariableDebt.toString(), + principalStableDebt: userReserveRaw.principalStableDebt.toString(), + stableBorrowLastUpdateTimestamp: + userReserveRaw.stableBorrowLastUpdateTimestamp.toNumber(), + }; }); + + return { + data: { userReserves, userEmodeCategoryId, baseCurrencyData }, + }; }, ); } diff --git a/apps/indexer/src/app/routes/root.ts b/apps/indexer/src/app/routes/root.ts index 76a22a4..4f16a94 100644 --- a/apps/indexer/src/app/routes/root.ts +++ b/apps/indexer/src/app/routes/root.ts @@ -1,7 +1,7 @@ -import { FastifyInstance } from 'fastify'; import { chains } from '../../configs/chains'; +import { ZodFastifyInstance } from '../../libs/server'; -export default async function (fastify: FastifyInstance) { +export default async function (fastify: ZodFastifyInstance) { fastify.get('/', async function () { return { data: chains.list() }; }); diff --git a/apps/indexer/src/libs/loaders/money-market.ts b/apps/indexer/src/libs/loaders/money-market.ts index bbfd2d4..34375df 100644 --- a/apps/indexer/src/libs/loaders/money-market.ts +++ b/apps/indexer/src/libs/loaders/money-market.ts @@ -2,6 +2,8 @@ import { Address } from 'viem'; import { ChainId, chains, ChainSelector } from '../../configs/chains'; import { BASE_DEFINITIONS_URL } from '../../configs/constants'; +// uses contract from @aave/contract-helpers +// @see https://github.com/aave/aave-utilities/blob/%40aave/contract-helpers%401.29.1/packages/contract-helpers/ const uiPoolDataProviderAbi = [ { inputs: [ @@ -321,6 +323,11 @@ const uiPoolDataProviderAbi = [ name: '', type: 'tuple[]', }, + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, ], stateMutability: 'view', type: 'function', diff --git a/apps/indexer/src/libs/server.ts b/apps/indexer/src/libs/server.ts new file mode 100644 index 0000000..c7ca110 --- /dev/null +++ b/apps/indexer/src/libs/server.ts @@ -0,0 +1,33 @@ +import { + fastify, + FastifyBaseLogger, + FastifyInstance, + FastifyServerOptions, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault, +} from 'fastify'; +import { + serializerCompiler, + validatorCompiler, + ZodTypeProvider, +} from 'fastify-type-provider-zod'; + +export type ZodFastifyInstance = FastifyInstance< + RawServerDefault, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + FastifyBaseLogger, + ZodTypeProvider +>; + +export const zodFastify = ( + options: FastifyServerOptions = { logger: true }, +) => { + const server = fastify(options).withTypeProvider(); + + server.setValidatorCompiler(validatorCompiler); + server.setSerializerCompiler(serializerCompiler); + + return server; +}; diff --git a/apps/indexer/src/libs/utils/user-reserves.ts b/apps/indexer/src/libs/utils/user-reserves.ts deleted file mode 100644 index e65376d..0000000 --- a/apps/indexer/src/libs/utils/user-reserves.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { areAddressesEqual } from '@sovryn/slayer-shared'; -import { and, eq, inArray } from 'drizzle-orm'; -import { client } from '../../database/client'; -import { tTokens } from '../../database/schema'; -import { tTokensSelectors } from '../../database/selectors'; -import { PoolDefinition } from '../loaders/money-market'; - -export async function transformUserReservesData({ - chainId, - userAddress, - pool, - reserves, -}: { - chainId: number; - userAddress: string; - pool: PoolDefinition; - reserves: Array<{ - underlyingAsset: string; - scaledATokenBalance: bigint; - usageAsCollateralEnabledOnUser: boolean; - stableBorrowRate: bigint; - scaledVariableDebt: bigint; - principalStableDebt: bigint; - stableBorrowLastUpdateTimestamp: bigint; - }>; -}) { - if (!reserves.length) { - return { data: [], count: 0 }; - } - - const tokens = await client.query.tTokens.findMany({ - columns: tTokensSelectors.columns, - where: and( - eq(tTokens.chainId, chainId), - inArray( - tTokens.address, - reserves.map((i) => i.underlyingAsset.toLowerCase()), - ), - ), - }); - - const data = reserves.map((pos) => ({ - id: `${chainId}-${pos.underlyingAsset}-${pool.address}-${userAddress}`.toLowerCase(), - user: userAddress, - pool, - token: tokens.find((t) => - areAddressesEqual(t.address, pos.underlyingAsset), - ), - scaledVariableDebt: pos?.scaledVariableDebt.toString(), - principalStableDebt: pos?.principalStableDebt.toString(), - stableBorrowRate: pos?.stableBorrowRate.toString(), - stableBorrowLastUpdateTimestamp: pos.stableBorrowLastUpdateTimestamp, - usageAsCollateralEnabledOnUser: pos.usageAsCollateralEnabledOnUser, - })); - - return { data, count: data.length }; -} diff --git a/apps/indexer/src/libs/validators/validators.ts b/apps/indexer/src/libs/validators/validators.ts new file mode 100644 index 0000000..c61a0ea --- /dev/null +++ b/apps/indexer/src/libs/validators/validators.ts @@ -0,0 +1,11 @@ +import { isAddress, type Address } from 'viem'; +import z from 'zod'; + +export const ze = { + address: z + .custom
( + (v) => isAddress(v as string, { strict: false }), + 'invalid address', + ) + .transform((v) => v.toLowerCase() as Address), +} as const; diff --git a/apps/indexer/src/main.ts b/apps/indexer/src/main.ts index 7436e22..9040cbd 100644 --- a/apps/indexer/src/main.ts +++ b/apps/indexer/src/main.ts @@ -4,15 +4,11 @@ import './libs/shims'; // other imports import cors from '@fastify/cors'; import { migrate } from 'drizzle-orm/node-postgres/migrator'; -import Fastify from 'fastify'; -import { - serializerCompiler, - validatorCompiler, -} from 'fastify-type-provider-zod'; import path from 'node:path'; import { app } from './app/app'; import { client } from './database/client'; import { logger } from './libs/logger'; +import { zodFastify } from './libs/server'; import { onShutdown } from './libs/shutdown'; import { notifyReady, onReady } from './libs/startup'; // spawn background jobs, workers, crontab, etc @@ -20,15 +16,12 @@ import './crontab'; import './workers/spawner'; // Instantiate Fastify with some config -const server = Fastify({ +const server = zodFastify({ loggerInstance: logger, disableRequestLogging: true, trustProxy: true, }); -server.setValidatorCompiler(validatorCompiler); -server.setSerializerCompiler(serializerCompiler); - // Enable CORS with the given origins. server.register(cors, { origin: ENV.CORS_ORIGINS, diff --git a/apps/web-app/package.json b/apps/web-app/package.json index abd5855..8d6d43b 100644 --- a/apps/web-app/package.json +++ b/apps/web-app/package.json @@ -29,6 +29,7 @@ "@tanstack/react-router": "1.131.50", "@tanstack/react-router-devtools": "1.131.50", "@tanstack/router-plugin": "1.131.50", + "@wagmi/core": "^3.0.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "debug": "4.4.3", diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx index 2d674f2..22e61c2 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx @@ -1,16 +1,16 @@ import { Accordion } from '@/components/ui/accordion'; +import type { MoneyMarketPoolPosition } from '@sovryn/slayer-sdk'; import { Settings, Zap } from 'lucide-react'; import { useState, type FC } from 'react'; import { AmountRenderer } from '../../../ui/amount-renderer'; import { PoolPositionStat } from '../PoolPositionStat/PoolPositionStat'; -import type { BorrowPosition } from './BorrowPositionsList.types'; import { AssetsTable } from './components/AssetsTable/AssetsTable'; type BorrowPositionsListProps = { supplyBalance: number; supplyWeightedApy: number; borrowPower: number; - borrowPositions: BorrowPosition[]; + borrowPositions: MoneyMarketPoolPosition[]; loading?: boolean; }; diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.constants.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.constants.tsx deleted file mode 100644 index 1463265..0000000 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.constants.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import iconTether from '@/assets/tokens/usdt.png'; -import type { BorrowPosition } from '../../BorrowPositionsList.types'; - -export const BORROW_POSITIONS: BorrowPosition[] = [ - { - symbol: 'USDT', - balance: '1.01234566', - balanceUsd: 159489.7, - apy: '15.34', - apyType: [0, 5.12], - icon: iconTether, - isSortable: true, - }, - { - symbol: 'USDT', - balance: '2.01234566', - balanceUsd: 159489.7, - apy: '4.4', - apyType: [1, 6.12], - icon: iconTether, - isSortable: true, - }, -]; diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx index 3c8f7f1..994667b 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx @@ -6,56 +6,44 @@ import { TableHeader, TableRow, } from '@/components/ui/table/table'; -import { - Fragment, - useCallback, - useEffect, - useMemo, - useState, - type FC, -} from 'react'; +import { Fragment, useCallback, useMemo, useState, type FC } from 'react'; -import iconSort from '@/assets/lend/icon-sort.svg'; import { AmountRenderer } from '@/components/ui/amount-renderer'; import { Button } from '@/components/ui/button'; import { InfoButton } from '@/components/ui/info-button'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - OrderColumn, - OrderType, - type OrderSorting, -} from '@/components/ui/table/table.types'; +import type { MoneyMarketPoolPosition } from '@sovryn/slayer-sdk'; +import { Decimal } from '@sovryn/slayer-shared'; import type { BorrowPosition } from '../../BorrowPositionsList.types'; type AssetsTableProps = { - assets: BorrowPosition[]; + assets: MoneyMarketPoolPosition[]; }; export const AssetsTable: FC = ({ assets }) => { - const [sortDirection, setSortDirection] = useState( - OrderType.ASC, + const items = useMemo( + () => assets.filter((a) => Decimal.from(a.borrowedBalance).gt(0)), + [assets], ); - const [sortedAssets, setSortedAssets] = useState(assets); + + // const [sortDirection, setSortDirection] = useState( + // OrderType.ASC, + // ); + // const [sortedAssets, setSortedAssets] = + // useState(assets); const [selectedApy, setSelectedApy] = useState>({}); - useEffect(() => { - setSortedAssets(assets); - setSelectedApy((prev) => { - const next = { ...prev }; - assets.forEach((a, i) => { - const id = rowKey(a, i); - if (next[id] == null) next[id] = inferDefaultSelected(a); - }); - return next; - }); - }, [assets]); + // useEffect(() => { + // setSortedAssets(assets); + // setSelectedApy((prev) => { + // const next = { ...prev }; + // assets.forEach((a, i) => { + // const id = rowKey(a, i); + // if (next[id] == null) next[id] = inferDefaultSelected(a); + // }); + // return next; + // }); + // }, [assets]); const parsePct = (v: unknown): number => { if (typeof v === 'number') return v; @@ -87,49 +75,49 @@ export const AssetsTable: FC = ({ assets }) => { [selectedApy, rowKey], ); - const sortAssets = useCallback( - (column: OrderColumn) => { - const nextDir = - sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; - setSortDirection(nextDir); + // const sortAssets = useCallback( + // (column: OrderColumn) => { + // const nextDir = + // sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; + // setSortDirection(nextDir); - const sorted = [...sortedAssets].sort((a, b) => { - switch (column) { - case OrderColumn.SYMBOL: { - const cmp = a.symbol.localeCompare(b.symbol); - return nextDir === OrderType.ASC ? cmp : -cmp; - } - case OrderColumn.BALANCE: { - const av = parseFloat(String(a.balance).replace(/,/g, '')) || 0; - const bv = parseFloat(String(b.balance).replace(/,/g, '')) || 0; - return nextDir === OrderType.ASC ? av - bv : bv - av; - } - case OrderColumn.APY: - case OrderColumn.APY_TYPE: { - const ai = assets.indexOf(a); - const bi = assets.indexOf(b); - const av = currentApy(a, ai); - const bv = currentApy(b, bi); - return nextDir === OrderType.ASC ? av - bv : bv - av; - } - default: - return 0; - } - }); + // const sorted = [...sortedAssets].sort((a, b) => { + // switch (column) { + // case OrderColumn.SYMBOL: { + // const cmp = a.symbol.localeCompare(b.symbol); + // return nextDir === OrderType.ASC ? cmp : -cmp; + // } + // case OrderColumn.BALANCE: { + // const av = parseFloat(String(a.balance).replace(/,/g, '')) || 0; + // const bv = parseFloat(String(b.balance).replace(/,/g, '')) || 0; + // return nextDir === OrderType.ASC ? av - bv : bv - av; + // } + // case OrderColumn.APY: + // case OrderColumn.APY_TYPE: { + // const ai = assets.indexOf(a); + // const bi = assets.indexOf(b); + // const av = currentApy(a, ai); + // const bv = currentApy(b, bi); + // return nextDir === OrderType.ASC ? av - bv : bv - av; + // } + // default: + // return 0; + // } + // }); - setSortedAssets(sorted); - }, - [sortDirection, sortedAssets, assets, currentApy], - ); + // setSortedAssets(sorted); + // }, + // [sortDirection, sortedAssets, assets, currentApy], + // ); - const handleApyTypeChange = useCallback( - (asset: BorrowPosition, idx: number, value: string) => { - const id = rowKey(asset, idx); - setSelectedApy((prev) => ({ ...prev, [id]: Number(value) })); - setSortedAssets((prev) => [...prev]); - }, - [rowKey], - ); + // const handleApyTypeChange = useCallback( + // (asset: BorrowPosition, idx: number, value: string) => { + // const id = rowKey(asset, idx); + // setSelectedApy((prev) => ({ ...prev, [id]: Number(value) })); + // setSortedAssets((prev) => [...prev]); + // }, + // [rowKey], + // ); return ( @@ -137,7 +125,7 @@ export const AssetsTable: FC = ({ assets }) => {
Asset - {assets.some((a) => a.isSortable) && ( + {/* {assets.some((a) => a.isSortable) && ( - )} + )} */}
Balance - {assets.some((a) => a.isSortable) && ( + {/* {assets.some((a) => a.isSortable) && ( - )} + )} */}
@@ -170,7 +158,7 @@ export const AssetsTable: FC = ({ assets }) => { APY - {assets.some((a) => a.isSortable) && ( + {/* {assets.some((a) => a.isSortable) && ( - )} + )} */} @@ -188,7 +176,7 @@ export const AssetsTable: FC = ({ assets }) => { APY type - {assets.some((a) => a.isSortable) && ( + {/* {assets.some((a) => a.isSortable) && ( - )} + )} */} @@ -205,44 +193,46 @@ export const AssetsTable: FC = ({ assets }) => {
- {sortedAssets.map((asset, index) => { - const selected = currentApy(asset, index); - const types = (asset.apyType ?? []) - .map(Number) - .filter(Number.isFinite); - const options = [selected, ...types.filter((t) => t !== selected)]; + {items.map((asset, index) => { + // const selected = currentApy(asset, index); + // const types = (asset.apyType ?? []) + // .map(Number) + // .filter(Number.isFinite); + // const options = [selected, ...types.filter((t) => t !== selected)]; return ( - +
{asset.symbol}
-

{asset.symbol}

+

+ {asset.token.symbol} +

- -

+ + {/*

-

+

*/}
- + {/* */}
-
+ {/*
-
+
*/}
@@ -278,7 +268,7 @@ export const AssetsTable: FC = ({ assets }) => {
- {index !== sortedAssets.length - 1 && ( + {index !== items.length - 1 && ( @@ -287,7 +277,7 @@ export const AssetsTable: FC = ({ assets }) => { ); })} - {sortedAssets.length === 0 && ( + {items.length === 0 && ( No assets found. diff --git a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx index bfc8f32..c7d136e 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx @@ -1,15 +1,15 @@ import { Accordion } from '@/components/ui/accordion'; +import type { MoneyMarketPoolPosition } from '@sovryn/slayer-sdk'; import { useState, type FC } from 'react'; import { AmountRenderer } from '../../../ui/amount-renderer'; import { PoolPositionStat } from '../PoolPositionStat/PoolPositionStat'; import { AssetsTable } from './components/AssetsTable/AssetsTable'; -import type { LendPosition } from './LendPositionsList.types'; type LendPositionsListProps = { supplyBalance: number; supplyWeightedApy: number; collateralBalance: number; - lendPositions: LendPosition[]; + lendPositions: MoneyMarketPoolPosition[]; loading?: boolean; }; @@ -19,7 +19,7 @@ export const LendPositionsList: FC = ({ collateralBalance, lendPositions, }) => { - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(true); return ( = ({ assets }) => { - const [sortDirection, setSortDirection] = useState( - OrderType.ASC, + const items = useMemo( + () => assets.filter((asset) => Decimal.from(asset.suppliedBalance).gt(0)), + [assets], ); - const [sortedAssets, setSortedAssets] = useState(assets); - useEffect(() => { - setSortedAssets(assets); - }, [assets]); - const sortAssets = useCallback( - (column: keyof LendPosition) => { - const newSortDirection = - sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; - setSortDirection(newSortDirection); + // const [sortDirection, setSortDirection] = useState( + // OrderType.ASC, + // ); + // const [sortedAssets, setSortedAssets] = + // useState(assets); + // useEffect(() => { + // setSortedAssets(assets); + // }, [assets]); - const sorted = [...sortedAssets].sort((a, b) => { - if (column === OrderColumn.SYMBOL) { - return newSortDirection === OrderType.ASC - ? a[column].localeCompare(b[column]) - : b[column].localeCompare(a[column]); - } else if (column === OrderColumn.BALANCE) { - const balanceA = parseFloat(a.balance.replace(/,/g, '')); - const balanceB = parseFloat(b.balance.replace(/,/g, '')); - return newSortDirection === OrderType.ASC - ? balanceA - balanceB - : balanceB - balanceA; - } else if (column === OrderColumn.APY) { - const apyA = parseFloat(a.apy.replace('%', '')); - const apyB = parseFloat(b.apy.replace('%', '')); - return newSortDirection === OrderType.ASC ? apyA - apyB : apyB - apyA; - } - return 0; - }); + // const sortAssets = useCallback( + // (column: keyof MoneyMarketPoolPosition) => { + // const newSortDirection = + // sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; + // setSortDirection(newSortDirection); - setSortedAssets(sorted); - }, - [sortDirection], - ); + // const sorted = [...sortedAssets].sort((a, b) => { + // if (column === OrderColumn.SYMBOL) { + // return newSortDirection === OrderType.ASC + // ? a[column].localeCompare(b[column]) + // : b[column].localeCompare(a[column]); + // } else if (column === OrderColumn.BALANCE) { + // const balanceA = parseFloat(a.balance.replace(/,/g, '')); + // const balanceB = parseFloat(b.balance.replace(/,/g, '')); + // return newSortDirection === OrderType.ASC + // ? balanceA - balanceB + // : balanceB - balanceA; + // } else if (column === OrderColumn.APY) { + // const apyA = parseFloat(a.apy.replace('%', '')); + // const apyB = parseFloat(b.apy.replace('%', '')); + // return newSortDirection === OrderType.ASC ? apyA - apyB : apyB - apyA; + // } + // return 0; + // }); + + // setSortedAssets(sorted); + // }, + // [sortDirection], + // ); const toggleCollateral = useCallback((symbol: string) => { - setSortedAssets((prevAssets) => - prevAssets.map((asset) => - asset.symbol === symbol - ? { ...asset, collateral: !asset.collateral } - : asset, - ), - ); + // setSortedAssets((prevAssets) => + // prevAssets.map((asset) => + // asset.symbol === symbol + // ? { ...asset, collateral: !asset.collateral } + // : asset, + // ), + // ); }, []); return ( @@ -80,7 +81,7 @@ export const AssetsTable: FC = ({ assets }) => {
Asset - {assets.some((asset) => asset.isSortable) && ( + {/* {assets.some((asset) => asset.isSortable) && ( - )} + )} */}
Balance - {assets.some((asset) => asset.isSortable) && ( + {/* {assets.some((asset) => asset.isSortable) && ( - )} + )} */}
@@ -113,7 +114,7 @@ export const AssetsTable: FC = ({ assets }) => { APY - {assets.some((asset) => asset.isSortable) && ( + {/* {assets.some((asset) => asset.isSortable) && ( - )} + )} */} @@ -134,44 +135,48 @@ export const AssetsTable: FC = ({ assets }) => {
- {sortedAssets.map((asset, index) => ( - + {items.map((asset, index) => ( +
{asset.symbol}
-

{asset.symbol}

+

+ {asset.token.symbol} +

- -

+ + {/*

-

+

*/}
-

{asset.apy}

+

+ {asset.stableBorrowRate} +

toggleCollateral(asset.symbol)} - disabled={!asset.canToggleCollateral} + checked={asset.usageAsCollateralEnabledOnUser} + id={`collateral-${asset.token.address}`} + onClick={() => toggleCollateral(asset.id)} + // disabled={!asset} />
@@ -187,14 +192,14 @@ export const AssetsTable: FC = ({ assets }) => {
- {index !== sortedAssets.length - 1 && ( + {index !== items.length - 1 && ( )}
))} - {sortedAssets.length === 0 && ( + {items.length === 0 && ( No assets found. diff --git a/apps/web-app/src/integrations/wagmi/config.ts b/apps/web-app/src/integrations/wagmi/config.ts index 5909633..4146190 100644 --- a/apps/web-app/src/integrations/wagmi/config.ts +++ b/apps/web-app/src/integrations/wagmi/config.ts @@ -7,6 +7,7 @@ import { rootstockTestnet, sepolia, } from 'viem/chains'; +import { getAccount } from 'wagmi/actions'; export const config = createConfig({ chains: [mainnet, sepolia, bobSepolia, rootstock, rootstockTestnet], @@ -19,6 +20,8 @@ export const config = createConfig({ }, }); +export const getConnection = () => getAccount(config); + declare module 'wagmi' { interface Register { config: typeof config; diff --git a/apps/web-app/src/main.tsx b/apps/web-app/src/main.tsx index fc807ef..418afd4 100644 --- a/apps/web-app/src/main.tsx +++ b/apps/web-app/src/main.tsx @@ -9,6 +9,7 @@ import * as Wagmi from './integrations/wagmi/root-provider.tsx'; // Import the generated route tree import { routeTree } from './routeTree.gen'; +import { getConnection } from './integrations/wagmi/config.ts'; import reportWebVitals from './reportWebVitals.ts'; import './styles.css'; @@ -19,6 +20,7 @@ const router = createRouter({ routeTree, context: { ...TanStackQueryProviderContext, + connection: getConnection, }, defaultPreload: 'intent', scrollRestoration: true, diff --git a/apps/web-app/src/routes/__root.tsx b/apps/web-app/src/routes/__root.tsx index 0eeab2a..4a279d2 100644 --- a/apps/web-app/src/routes/__root.tsx +++ b/apps/web-app/src/routes/__root.tsx @@ -21,6 +21,7 @@ import { useEffect, type PropsWithChildren } from 'react'; interface MyRouterContext { queryClient: QueryClient; + connection: typeof import('@/integrations/wagmi/config').getConnection; } export const Route = createRootRouteWithContext()({ diff --git a/apps/web-app/src/routes/money-market.tsx b/apps/web-app/src/routes/money-market.tsx index 2017e0a..3fd732d 100644 --- a/apps/web-app/src/routes/money-market.tsx +++ b/apps/web-app/src/routes/money-market.tsx @@ -6,20 +6,18 @@ import { TopPanel } from '@/components/MoneyMarket/components/TopPanel/TopPanel' import { BorrowAssetsList } from '@/components/MoneyMarket/components/BorrowAssetsList/BorrowAssetsList'; import { BorrowDialog } from '@/components/MoneyMarket/components/BorrowDialog/BorrowDialog'; import { BorrowPositionsList } from '@/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList'; -import { BORROW_POSITIONS } from '@/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.constants'; import { LendAssetsList } from '@/components/MoneyMarket/components/LendAssetsList/LendAssetsList'; import { LendDialog } from '@/components/MoneyMarket/components/LendDialog/LendDialog'; -import { LEND_POSITIONS } from '@/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.constants'; import { healthFactor, netApy, netWorth, } from '@/components/MoneyMarket/MoneyMarket.constants'; import { Heading } from '@/components/ui/heading/heading'; -import { getContext } from '@/integrations/tanstack-query/root-provider'; import { sdk } from '@/lib/sdk'; import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; +import { useAccount } from 'wagmi'; import z from 'zod'; const STALE_TIME = 1000 * 60 * 60; // 1 hour @@ -40,8 +38,8 @@ export const Route = createFileRoute('/money-market')({ search, pool, }), - loader: ({ deps: { pool } }) => { - const client = getContext().queryClient; + loader: ({ deps: { pool }, context }) => { + const client = context.queryClient; client.prefetchQuery({ queryKey: ['money-market:pools'], queryFn: () => sdk.moneyMarket.listPools(), @@ -53,11 +51,29 @@ export const Route = createFileRoute('/money-market')({ queryFn: () => sdk.moneyMarket.listReserves(pool || 'default'), staleTime: STALE_TIME, }); + + const owner = context.connection().address; + if (owner) { + client.prefetchQuery({ + queryKey: ['money-market:borrows', pool || 'default', owner], + queryFn: () => + sdk.moneyMarket.listUserPositions(pool || 'default', owner), + staleTime: STALE_TIME, + }); + + client.prefetchQuery({ + queryKey: ['money-market:lendings', pool || 'default', owner], + queryFn: () => + sdk.moneyMarket.listUserLendings(pool || 'default', owner), + staleTime: STALE_TIME, + }); + } }, }); function RouteComponent() { const { pool } = Route.useLoaderDeps(); + const { address } = useAccount(); // const { data: pools } = useQuery({ // queryKey: ['money-market:pools'], @@ -71,6 +87,14 @@ function RouteComponent() { staleTime: STALE_TIME, }); + const { data: positions } = useQuery({ + queryKey: ['money-market:positions', pool || 'default', address], + queryFn: () => + sdk.moneyMarket.listUserPositions(pool || 'default', address!), + staleTime: STALE_TIME, + enabled: !!address, + }); + const borrowAssets = useMemo( () => (reserves?.data ?? []).filter((r) => r.borrowingEnabled), [reserves], @@ -95,7 +119,7 @@ function RouteComponent() {
extends BaseClient { }; } + async listUserPositions( + pool: MoneyMarketPool['id'], + user: Address, + opts: SdkRequestOptions = {}, + ) { + const response = await this.ctx.http.request<{ + data: { userReserves: MoneyMarketPoolPosition[] }; + }>(`/${this.ctx.chainId}/money-market/${pool}/user/${user}/positions`, { + ...opts, + query: buildQuery(opts.query), + }); + + return { ...response, data: response.data.userReserves }; + } + async borrow( reserve: MoneyMarketPoolReserve, amount: Decimalish, diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 2440d69..8d036ee 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -77,3 +77,21 @@ export const BORROW_RATE_MODES = { export type BorrowRateMode = (typeof BORROW_RATE_MODES)[keyof typeof BORROW_RATE_MODES]; + +export type MoneyMarketPoolPosition = { + id: string; + pool: MoneyMarketPool; + token: SdkToken; + reserve: any; // todo + + suppliedBalance: string; + borrowedBalance: string; + + underlyingAsset: string; + scaledATokenBalance: string; + usageAsCollateralEnabledOnUser: boolean; + stableBorrowRate: string; + scaledVariableDebt: string; + principalStableDebt: string; + stableBorrowLastUpdateTimestamp: number; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61b625d..1b67eb7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,6 +321,9 @@ importers: '@tanstack/router-plugin': specifier: 1.131.50 version: 1.131.50(@tanstack/react-router@1.131.50(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))(webpack@5.103.0(@swc/core@1.5.29(@swc/helpers@0.5.17))(esbuild@0.25.0)) + '@wagmi/core': + specifier: ^3.0.0 + version: 3.0.0(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.6.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -4932,6 +4935,18 @@ packages: typescript: optional: true + '@wagmi/core@3.0.0': + resolution: {integrity: sha512-wOn8jwB9GNYTdrc4CP/huf1aAhDoQ5GKl5OhxGBZx9X4qE+wReW05dTcurEc+XBl9B/ZVis2JdXVU3ZiYqyS8Q==} + peerDependencies: + '@tanstack/query-core': '>=5.0.0' + typescript: '>=5.7.3' + viem: 2.38.6 + peerDependenciesMeta: + '@tanstack/query-core': + optional: true + typescript: + optional: true + '@wallet-standard/app@1.1.0': resolution: {integrity: sha512-3CijvrO9utx598kjr45hTbbeeykQrQfKmSnxeWOgU25TOEpvcipD/bYDQWIqUv1Oc6KK4YStokSMu/FBNecGUQ==} engines: {node: '>=16'} @@ -16903,7 +16918,7 @@ snapshots: '@metamask/sdk': 0.33.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@safe-global/safe-apps-provider': 0.18.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) - '@wagmi/core': 2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.6.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) + '@wagmi/core': 2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) '@walletconnect/ethereum-provider': 2.21.1(@types/react@19.1.13)(bufferutil@4.0.9)(encoding@0.1.13)(ioredis@5.8.0)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' porto: 0.2.35(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.13)(@wagmi/core@2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)))(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))(wagmi@2.19.2(@tanstack/query-core@5.90.2)(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.13)(bufferutil@4.0.9)(debug@4.4.3)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@10.2.0)(ioredis@5.8.0)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11)) @@ -16949,7 +16964,22 @@ snapshots: - ws - zod - '@wagmi/core@2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.6.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))': + '@wagmi/core@2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))': + dependencies: + eventemitter3: 5.0.1 + mipd: 0.0.7(typescript@5.9.2) + viem: 2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) + zustand: 5.0.0(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(use-sync-external-store@1.4.0(react@19.1.1)) + optionalDependencies: + '@tanstack/query-core': 5.90.2 + typescript: 5.9.2 + transitivePeerDependencies: + - '@types/react' + - immer + - react + - use-sync-external-store + + '@wagmi/core@3.0.0(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.6.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))': dependencies: eventemitter3: 5.0.1 mipd: 0.0.7(typescript@5.9.2) @@ -22349,7 +22379,7 @@ snapshots: porto@0.2.35(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.13)(@wagmi/core@2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)))(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))(wagmi@2.19.2(@tanstack/query-core@5.90.2)(@tanstack/react-query@5.90.2(react@19.1.1))(@types/react@19.1.13)(bufferutil@4.0.9)(debug@4.4.3)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(immer@10.2.0)(ioredis@5.8.0)(react@19.1.1)(typescript@5.9.2)(utf-8-validate@5.0.10)(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11)): dependencies: - '@wagmi/core': 2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.6.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) + '@wagmi/core': 2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) hono: 4.10.6 idb-keyval: 6.2.2 mipd: 0.0.7(typescript@5.9.2) @@ -24448,7 +24478,7 @@ snapshots: dependencies: '@tanstack/react-query': 5.90.2(react@19.1.1) '@wagmi/connectors': 6.1.3(e2df5ec3edd1d01c0c5b6685ed869aa8) - '@wagmi/core': 2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.6.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) + '@wagmi/core': 2.22.1(@tanstack/query-core@5.90.2)(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(typescript@5.9.2)(use-sync-external-store@1.4.0(react@19.1.1))(viem@2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11)) react: 19.1.1 use-sync-external-store: 1.4.0(react@19.1.1) viem: 2.38.6(bufferutil@4.0.9)(typescript@5.9.2)(utf-8-validate@5.0.10)(zod@4.1.11) @@ -24736,6 +24766,13 @@ snapshots: zod@4.1.11: {} + zustand@5.0.0(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(use-sync-external-store@1.4.0(react@19.1.1)): + optionalDependencies: + '@types/react': 19.1.13 + immer: 10.2.0 + react: 19.1.1 + use-sync-external-store: 1.4.0(react@19.1.1) + zustand@5.0.0(@types/react@19.1.13)(immer@10.2.0)(react@19.1.1)(use-sync-external-store@1.6.0(react@19.1.1)): optionalDependencies: '@types/react': 19.1.13 From adda3b484c79ee3be6c65cc458d5df3a4a603ad3 Mon Sep 17 00:00:00 2001 From: Rytis Grincevicius Date: Fri, 5 Dec 2025 14:25:19 +0200 Subject: [PATCH 2/5] fix: use aave sdk for user balance calculations --- apps/indexer/package.json | 1 + apps/indexer/src/app/plugins/cache.ts | 75 ++- apps/indexer/src/app/routes/_chain/routes.ts | 406 ++++++++-------- apps/indexer/src/configs/chains.ts | 5 +- apps/indexer/src/configs/constants.ts | 2 + apps/indexer/src/env.ts | 14 +- apps/indexer/src/libs/aave/index.ts | 1 + apps/indexer/src/libs/loaders/money-market.ts | 436 ++++++++++++++---- .../components/AssetsTable/AssetsTable.tsx | 10 +- .../components/AssetsTable/AssetsTable.tsx | 6 +- packages/sdk/src/constants.ts | 2 + packages/sdk/src/types.ts | 2 + packages/shared/src/lib/decimal.ts | 4 + packages/shared/src/lib/http-client.ts | 1 + pnpm-lock.yaml | 5 +- 15 files changed, 665 insertions(+), 305 deletions(-) create mode 100644 apps/indexer/src/libs/aave/index.ts diff --git a/apps/indexer/package.json b/apps/indexer/package.json index 7924c09..4ba885c 100644 --- a/apps/indexer/package.json +++ b/apps/indexer/package.json @@ -138,6 +138,7 @@ } }, "dependencies": { + "@aave/math-utils": "^1.29.1", "@bull-board/api": "6.13.0", "@bull-board/fastify": "6.13.0", "@fastify/autoload": "6.0.3", diff --git a/apps/indexer/src/app/plugins/cache.ts b/apps/indexer/src/app/plugins/cache.ts index 2c2ac2d..cd6782b 100644 --- a/apps/indexer/src/app/plugins/cache.ts +++ b/apps/indexer/src/app/plugins/cache.ts @@ -7,8 +7,28 @@ import type { import fp from 'fastify-plugin'; import { ENV } from '../../env'; import { encode } from '../../libs/encode'; +import { logger } from '../../libs/logger'; import { createRedisConnection } from '../../libs/utils/redis'; +// Custom JSON serialization to handle BigInt values +function serializeWithBigInt(value: unknown): string { + return JSON.stringify(value, (key, val) => { + if (typeof val === 'bigint') { + return { __type: 'bigint', value: val.toString() }; + } + return val; + }); +} + +function deserializeWithBigInt(json: string): T { + return JSON.parse(json, (key, val) => { + if (val && typeof val === 'object' && val.__type === 'bigint') { + return BigInt(val.value); + } + return val; + }); +} + const cacheRedisConnection = createRedisConnection( ENV.REDIS_URL, ENV.REDIS_CLUSTER_MODE, @@ -106,7 +126,7 @@ const redisCachePlugin: FastifyPluginAsync = async ( req: FastifyRequest, ): RouteCacheOptions | null => { const rawCfg = req.routeOptions.config.cache; - if (!rawCfg) return null; + if (!rawCfg || ENV.NO_CACHE) return null; if (typeof rawCfg === 'boolean') { if (!rawCfg) return null; @@ -150,7 +170,7 @@ const redisCachePlugin: FastifyPluginAsync = async ( const cached = await redis.get(key); if (!cached) return; - const entry: CacheEntry = JSON.parse(cached); + const entry: CacheEntry = deserializeWithBigInt(cached); const ageSec = (Date.now() - entry.storedAt) / 1000; const isFresh = ageSec <= entry.ttlSeconds; @@ -228,7 +248,7 @@ const redisCachePlugin: FastifyPluginAsync = async ( const cfg: RouteCacheOptions = typeof rawCfg === 'boolean' ? { enabled: rawCfg } : rawCfg; - if (cfg.enabled === false) return payload; + if (cfg.enabled === false || ENV.NO_CACHE) return payload; if (req.__cacheHit) { return payload; @@ -260,7 +280,7 @@ const redisCachePlugin: FastifyPluginAsync = async ( const expireSeconds = ttl + (cfg.staleTtlSeconds ?? defaultStaleTtl); - await redis.setex(key, expireSeconds, JSON.stringify(entry)); + await redis.setex(key, expireSeconds, serializeWithBigInt(entry)); // For "real" client requests (not internal revalidation), set MISS header if (req.headers['x-cache-revalidate'] !== '1') { @@ -272,6 +292,53 @@ const redisCachePlugin: FastifyPluginAsync = async ( ); }; +export const maybeCache = async ( + key: string, + fn: () => Promise, + opts: Pick = {}, +): Promise => { + const enabled = opts.enabled ?? true; + + if (!enabled || ENV.NO_CACHE) { + return fn(); + } + + const redis = cacheRedisConnection; + + const ttl = opts.ttlSeconds ?? 30; // 30 seconds + + const cacheKey = 'maybe-cache:fn:' + encode.sha256(key); + + const cached = await redis.get(cacheKey); + + if (cached) { + const entry: CacheEntry = deserializeWithBigInt(cached); + const ageSec = (Date.now() - entry.storedAt) / 1000; + + const isFresh = ageSec <= entry.ttlSeconds; + + logger.info({ key, ageSec, ttl: entry.ttlSeconds }, 'Cache hit'); + + if (isFresh) { + return entry.payload as T; + } + } + + logger.info({ key }, 'Cache miss, invoking function'); + + const entry = { + payload: await fn(), + headers: {}, + statusCode: 200, + storedAt: Date.now(), + ttlSeconds: ttl, + }; + + await redis.setex(cacheKey, ttl, serializeWithBigInt(entry)); + + return entry.payload as T; +}; + export default fp(redisCachePlugin, { name: 'cache-plugin', }); diff --git a/apps/indexer/src/app/routes/_chain/routes.ts b/apps/indexer/src/app/routes/_chain/routes.ts index 2a54121..8830dcd 100644 --- a/apps/indexer/src/app/routes/_chain/routes.ts +++ b/apps/indexer/src/app/routes/_chain/routes.ts @@ -1,10 +1,10 @@ -import { areAddressesEqual, Decimal } from '@sovryn/slayer-shared'; +import { formatReserves, formatUserSummary } from '@aave/math-utils'; import { and, asc, eq, gte, inArray } from 'drizzle-orm'; import { FastifyRequest } from 'fastify'; import z from 'zod'; import { client } from '../../../database/client'; import { tTokens } from '../../../database/schema'; -import { TTokenSelected, tTokensSelectors } from '../../../database/selectors'; +import { tTokensSelectors } from '../../../database/selectors'; import { fetchPoolList, fetchPoolReserves, @@ -15,63 +15,6 @@ import { paginationResponse, paginationSchema } from '../../../libs/pagination'; import { ZodFastifyInstance } from '../../../libs/server'; import { ze } from '../../../libs/validators/validators'; -interface ReserveDataHumanized { - originalId: number; - id: string; - underlyingAsset: string; - - token: TTokenSelected; - - name: string; - symbol: string; - decimals: number; - baseLTVasCollateral: string; - reserveLiquidationThreshold: string; - reserveLiquidationBonus: string; - reserveFactor: string; - usageAsCollateralEnabled: boolean; - borrowingEnabled: boolean; - isActive: boolean; - isFrozen: boolean; - liquidityIndex: string; - variableBorrowIndex: string; - liquidityRate: string; - variableBorrowRate: string; - lastUpdateTimestamp: number; - aTokenAddress: string; - variableDebtTokenAddress: string; - interestRateStrategyAddress: string; - availableLiquidity: string; - totalScaledVariableDebt: string; - priceInMarketReferenceCurrency: string; - priceOracle: string; - variableRateSlope1: string; - variableRateSlope2: string; - baseVariableBorrowRate: string; - optimalUsageRatio: string; - // v3 only - isPaused: boolean; - isSiloedBorrowing: boolean; - accruedToTreasury: string; - unbacked: string; - isolationModeTotalDebt: string; - flashLoanEnabled: boolean; - debtCeiling: string; - debtCeilingDecimals: number; - borrowCap: string; - supplyCap: string; - borrowableInIsolation: boolean; - virtualAccActive: boolean; - virtualUnderlyingBalance: string; -} - -interface PoolBaseCurrencyHumanized { - marketReferenceCurrencyDecimals: number; - marketReferenceCurrencyPriceInUsd: string; - networkBaseTokenPriceInUsd: string; - networkBaseTokenPriceDecimals: number; -} - export default async function (fastify: ZodFastifyInstance) { fastify.get('/', async (req) => { return { data: req.chain }; @@ -139,7 +82,7 @@ export default async function (fastify: ZodFastifyInstance) { }), }, config: { - cache: true, + cache: false, }, }, async (req: FastifyRequest<{ Params: { pool: string } }>, reply) => { @@ -150,107 +93,112 @@ export default async function (fastify: ZodFastifyInstance) { return reply.notFound('Pool not found'); } - const { 0: reservesRaw, 1: poolBaseCurrencyRaw } = - await fetchPoolReserves(req.chain.chainId, pool); + const { reservesData, baseCurrencyData } = await fetchPoolReserves( + req.chain.chainId, + pool, + ); - const tokens = await client.query.tTokens.findMany({ - columns: tTokensSelectors.columns, - where: and( - eq(tTokens.chainId, req.chain.chainId), - inArray( - tTokens.address, - reservesRaw.map((i) => i.underlyingAsset.toLowerCase()), - ), - ), + // const tokens = await client.query.tTokens.findMany({ + // columns: tTokensSelectors.columns, + // where: and( + // eq(tTokens.chainId, req.chain.chainId), + // inArray( + // tTokens.address, + // reservesRaw.map((i) => i.underlyingAsset.toLowerCase()), + // ), + // ), + // }); + + const data = formatReserves({ + reserves: reservesData, + currentTimestamp: Math.floor(Date.now() / 1000), + marketReferencePriceInUsd: + baseCurrencyData.marketReferenceCurrencyPriceInUsd, + marketReferenceCurrencyDecimals: + baseCurrencyData.marketReferenceCurrencyDecimals, }); - const reservesData: Partial[] = reservesRaw.map( - (reserveRaw, index) => { - // const virtualUnderlyingBalance = - // reserveRaw.virtualUnderlyingBalance.toString(); - // const { virtualAccActive } = reserveRaw; - return { - originalId: index, - id: `${req.chain.chainId}-${reserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), - // underlyingAsset: reserveRaw.underlyingAsset.toLowerCase(), - - token: tokens.find((t) => - areAddressesEqual(t.address, reserveRaw.underlyingAsset), - ), - pool, - - // name: reserveRaw.name, - // symbol: ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] - // ? ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] - // : reserveRaw.symbol, - // decimals: reserveRaw.decimals.toNumber(), - baseLTVasCollateral: reserveRaw.baseLTVasCollateral.toString(), - reserveLiquidationThreshold: - reserveRaw.reserveLiquidationThreshold.toString(), - reserveLiquidationBonus: - reserveRaw.reserveLiquidationBonus.toString(), - reserveFactor: reserveRaw.reserveFactor.toString(), - usageAsCollateralEnabled: reserveRaw.usageAsCollateralEnabled, - borrowingEnabled: reserveRaw.borrowingEnabled, - isActive: reserveRaw.isActive, - isFrozen: reserveRaw.isFrozen, - liquidityIndex: reserveRaw.liquidityIndex.toString(), - variableBorrowIndex: reserveRaw.variableBorrowIndex.toString(), - liquidityRate: reserveRaw.liquidityRate.toString(), - variableBorrowRate: reserveRaw.variableBorrowRate.toString(), - lastUpdateTimestamp: reserveRaw.lastUpdateTimestamp, - aTokenAddress: reserveRaw.aTokenAddress.toString(), - variableDebtTokenAddress: - reserveRaw.variableDebtTokenAddress.toString(), - interestRateStrategyAddress: - reserveRaw.interestRateStrategyAddress.toString(), - availableLiquidity: Decimal.from( - reserveRaw.availableLiquidity, - reserveRaw.decimals.toNumber(), - ).toString(), - // availableLiquidity: reserveRaw.availableLiquidity.toString(), - totalScaledVariableDebt: - reserveRaw.totalScaledVariableDebt.toString(), - priceInMarketReferenceCurrency: - reserveRaw.priceInMarketReferenceCurrency.toString(), - // priceOracle: reserveRaw.priceOracle, - variableRateSlope1: reserveRaw.variableRateSlope1.toString(), - variableRateSlope2: reserveRaw.variableRateSlope2.toString(), - // baseVariableBorrowRate: - // reserveRaw.baseVariableBorrowRate.toString(), - // optimalUsageRatio: reserveRaw.optimalUsageRatio.toString(), - // new fields - // isPaused: reserveRaw.isPaused, - // debtCeiling: reserveRaw.debtCeiling.toString(), - // borrowCap: reserveRaw.borrowCap.toString(), - // supplyCap: reserveRaw.supplyCap.toString(), - // borrowableInIsolation: reserveRaw.borrowableInIsolation, - // accruedToTreasury: reserveRaw.accruedToTreasury.toString(), - // unbacked: reserveRaw.unbacked.toString(), - // isolationModeTotalDebt: - // reserveRaw.isolationModeTotalDebt.toString(), - // debtCeilingDecimals: reserveRaw.debtCeilingDecimals.toNumber(), - // isSiloedBorrowing: reserveRaw.isSiloedBorrowing, - // flashLoanEnabled: reserveRaw.flashLoanEnabled, - // virtualAccActive, - // virtualUnderlyingBalance, - }; - }, - ); + // const reservesData: Partial[] = reservesRaw.map( + // (reserveRaw, index) => { + // // const virtualUnderlyingBalance = + // // reserveRaw.virtualUnderlyingBalance.toString(); + // // const { virtualAccActive } = reserveRaw; - const baseCurrencyData: PoolBaseCurrencyHumanized = { - // this is to get the decimals from the unit so 1e18 = string length of 19 - 1 to get the number of 0 - marketReferenceCurrencyDecimals: - poolBaseCurrencyRaw.marketReferenceCurrencyUnit.toString().length - 1, - marketReferenceCurrencyPriceInUsd: - poolBaseCurrencyRaw.marketReferenceCurrencyPriceInUsd.toString(), - networkBaseTokenPriceInUsd: - poolBaseCurrencyRaw.networkBaseTokenPriceInUsd.toString(), - networkBaseTokenPriceDecimals: - poolBaseCurrencyRaw.networkBaseTokenPriceDecimals, - }; + // // const { totalDebt, totalVariableDebt, totalLiquidity } = + // // calculateReserveDebt(reserveRaw, currentTimestamp); + + // // formatRe + + // return { + // originalId: index, + // id: `${req.chain.chainId}-${reserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), + // // underlyingAsset: reserveRaw.underlyingAsset.toLowerCase(), - return { data: { reservesData, baseCurrencyData } }; + // token: tokens.find((t) => + // areAddressesEqual(t.address, reserveRaw.underlyingAsset), + // ), + // pool, + + // // name: reserveRaw.name, + // // symbol: ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] + // // ? ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] + // // : reserveRaw.symbol, + // // decimals: reserveRaw.decimals.toNumber(), + // baseLTVasCollateral: reserveRaw.baseLTVasCollateral.toString(), + // reserveLiquidationThreshold: + // reserveRaw.reserveLiquidationThreshold.toString(), + // reserveLiquidationBonus: + // reserveRaw.reserveLiquidationBonus.toString(), + // reserveFactor: reserveRaw.reserveFactor.toString(), + // usageAsCollateralEnabled: reserveRaw.usageAsCollateralEnabled, + // borrowingEnabled: reserveRaw.borrowingEnabled, + // isActive: reserveRaw.isActive, + // isFrozen: reserveRaw.isFrozen, + // liquidityIndex: reserveRaw.liquidityIndex.toString(), + // variableBorrowIndex: reserveRaw.variableBorrowIndex.toString(), + // liquidityRate: reserveRaw.liquidityRate.toString(), + // variableBorrowRate: reserveRaw.variableBorrowRate.toString(), + // lastUpdateTimestamp: reserveRaw.lastUpdateTimestamp, + // aTokenAddress: reserveRaw.aTokenAddress.toString(), + // variableDebtTokenAddress: + // reserveRaw.variableDebtTokenAddress.toString(), + // interestRateStrategyAddress: + // reserveRaw.interestRateStrategyAddress.toString(), + // availableLiquidity: Decimal.from( + // reserveRaw.availableLiquidity, + // reserveRaw.decimals.toNumber(), + // ).toString(), + // // availableLiquidity: reserveRaw.availableLiquidity.toString(), + // totalScaledVariableDebt: + // reserveRaw.totalScaledVariableDebt.toString(), + // priceInMarketReferenceCurrency: + // reserveRaw.priceInMarketReferenceCurrency.toString(), + // // priceOracle: reserveRaw.priceOracle, + // variableRateSlope1: reserveRaw.variableRateSlope1.toString(), + // variableRateSlope2: reserveRaw.variableRateSlope2.toString(), + // // baseVariableBorrowRate: + // // reserveRaw.baseVariableBorrowRate.toString(), + // // optimalUsageRatio: reserveRaw.optimalUsageRatio.toString(), + // // new fields + // // isPaused: reserveRaw.isPaused, + // // debtCeiling: reserveRaw.debtCeiling.toString(), + // // borrowCap: reserveRaw.borrowCap.toString(), + // // supplyCap: reserveRaw.supplyCap.toString(), + // // borrowableInIsolation: reserveRaw.borrowableInIsolation, + // // accruedToTreasury: reserveRaw.accruedToTreasury.toString(), + // // unbacked: reserveRaw.unbacked.toString(), + // // isolationModeTotalDebt: + // // reserveRaw.isolationModeTotalDebt.toString(), + // // debtCeilingDecimals: reserveRaw.debtCeilingDecimals.toNumber(), + // // isSiloedBorrowing: reserveRaw.isSiloedBorrowing, + // // flashLoanEnabled: reserveRaw.flashLoanEnabled, + // // virtualAccActive, + // // virtualUnderlyingBalance, + // }; + // }, + // ); + + return { data: { reservesData: data, baseCurrencyData } }; // return { // data: items @@ -277,7 +225,7 @@ export default async function (fastify: ZodFastifyInstance) { }, config: { cache: { - enabled: true, + enabled: false, ttlSeconds: 10, staleTtlSeconds: 15, }, @@ -292,72 +240,114 @@ export default async function (fastify: ZodFastifyInstance) { if (!pool) return reply.notFound('Pool not found'); - const { 0: reservesRaw, 1: poolBaseCurrencyRaw } = - await fetchPoolReserves(req.chain.chainId, pool); + const currentTimestamp = Math.floor(Date.now() / 1000); + + const { reservesData, baseCurrencyData } = await fetchPoolReserves( + req.chain.chainId, + pool, + ); - const { 0: userReservesRaw, 1: userEmodeCategoryId } = - await fetchUserReserves(req.chain.chainId, pool, req.params.address); + const { userReserves, userEmodeCategoryId } = await fetchUserReserves( + req.chain.chainId, + pool, + req.params.address, + ); - const tokens = await client.query.tTokens.findMany({ + await client.query.tTokens.findMany({ columns: tTokensSelectors.columns, where: and( eq(tTokens.chainId, req.chain.chainId), inArray( tTokens.address, - userReservesRaw.map((i) => i.underlyingAsset.toLowerCase()), + userReserves.map((i) => i.underlyingAsset.toLowerCase()), ), ), }); - const baseCurrencyData: PoolBaseCurrencyHumanized = { - // this is to get the decimals from the unit so 1e18 = string length of 19 - 1 to get the number of 0 + const summary = formatUserSummary({ + currentTimestamp, + marketReferencePriceInUsd: + baseCurrencyData.marketReferenceCurrencyPriceInUsd, marketReferenceCurrencyDecimals: - poolBaseCurrencyRaw.marketReferenceCurrencyUnit.toString().length - 1, - marketReferenceCurrencyPriceInUsd: - poolBaseCurrencyRaw.marketReferenceCurrencyPriceInUsd.toString(), - networkBaseTokenPriceInUsd: - poolBaseCurrencyRaw.networkBaseTokenPriceInUsd.toString(), - networkBaseTokenPriceDecimals: - poolBaseCurrencyRaw.networkBaseTokenPriceDecimals, - }; - - const userReserves = userReservesRaw.map((userReserveRaw) => { - const token = tokens.find((t) => - areAddressesEqual(t.address, userReserveRaw.underlyingAsset), - ); - const reserve = reservesRaw.find((r) => - areAddressesEqual(r.underlyingAsset, userReserveRaw.underlyingAsset), - ); - return { - id: `${req.chain.chainId}-${req.params.address}-${userReserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), - pool, - token, - reserve, - - suppliedBalance: Decimal.from( - userReserveRaw.scaledATokenBalance, - token?.decimals ?? 18, - ).toString(), - - borrowedBalance: Decimal.from( - userReserveRaw.scaledVariableDebt, - token?.decimals ?? 18, - ).toString(), - - underlyingAsset: userReserveRaw.underlyingAsset.toLowerCase(), - scaledATokenBalance: userReserveRaw.scaledATokenBalance.toString(), - usageAsCollateralEnabledOnUser: - userReserveRaw.usageAsCollateralEnabledOnUser, - stableBorrowRate: userReserveRaw.stableBorrowRate.toString(), - scaledVariableDebt: userReserveRaw.scaledVariableDebt.toString(), - principalStableDebt: userReserveRaw.principalStableDebt.toString(), - stableBorrowLastUpdateTimestamp: - userReserveRaw.stableBorrowLastUpdateTimestamp.toNumber(), - }; + baseCurrencyData.marketReferenceCurrencyDecimals, + userReserves, + userEmodeCategoryId, + formattedReserves: formatReserves({ + reserves: reservesData, + currentTimestamp, + marketReferencePriceInUsd: + baseCurrencyData.marketReferenceCurrencyPriceInUsd, + marketReferenceCurrencyDecimals: + baseCurrencyData.marketReferenceCurrencyDecimals, + }), }); + // const userReserves = userReservesRaw + // .filter( + // (item) => + // item.scaledATokenBalance > 0n || item.scaledVariableDebt > 0n, + // ) + // .map((userReserveRaw) => { + // const token = tokens.find((t) => + // areAddressesEqual(t.address, userReserveRaw.underlyingAsset), + // ); + // const reserve = reserves.find((r) => + // areAddressesEqual( + // r.underlyingAsset, + // userReserveRaw.underlyingAsset, + // ), + // ); + + // const usdPrice = Decimal.from( + // reserve?.priceInMarketReferenceCurrency.toString() || '0', + // ).div(Decimal.pow(baseCurrencyData.marketReferenceCurrencyDecimals)); + + // const suppliedBalance = Decimal.from( + // userReserveRaw.scaledATokenBalance, + // token?.decimals ?? 18, + // ); + + // const borrowedBalance = Decimal.from( + // userReserveRaw.scaledVariableDebt, + // token?.decimals ?? 18, + // ); + + // return { + // id: `${req.chain.chainId}-${req.params.address}-${userReserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), + // // pool, + // // token, + // // reserve, + + // usdPrice: usdPrice.toString(), + // priceInMarketReferenceCurrency: + // reserve?.priceInMarketReferenceCurrency, + // decimalsD: baseCurrencyData.marketReferenceCurrencyDecimals, + // raw: baseCurrencyData, + + // suppliedBalance: suppliedBalance.toString(), + // suppliedBalanceUsd: suppliedBalance + // .mul(usdPrice) + // .toFixed(USD_DECIMALS), + + // borrowedBalance: borrowedBalance.toString(), + // borrowedBalanceUsd: borrowedBalance + // .mul(usdPrice) + // .toFixed(USD_DECIMALS), + + // underlyingAsset: userReserveRaw.underlyingAsset.toLowerCase(), + // scaledATokenBalance: userReserveRaw.scaledATokenBalance.toString(), + // usageAsCollateralEnabledOnUser: + // userReserveRaw.usageAsCollateralEnabledOnUser, + // stableBorrowRate: userReserveRaw.stableBorrowRate.toString(), + // scaledVariableDebt: userReserveRaw.scaledVariableDebt.toString(), + // principalStableDebt: userReserveRaw.principalStableDebt.toString(), + // stableBorrowLastUpdateTimestamp: + // userReserveRaw.stableBorrowLastUpdateTimestamp.toNumber(), + // }; + // }); + return { - data: { userReserves, userEmodeCategoryId, baseCurrencyData }, + data: summary, }; }, ); diff --git a/apps/indexer/src/configs/chains.ts b/apps/indexer/src/configs/chains.ts index b3b76dc..288d82c 100644 --- a/apps/indexer/src/configs/chains.ts +++ b/apps/indexer/src/configs/chains.ts @@ -6,6 +6,7 @@ import { Chain as ViemChain, } from 'viem'; import { bobSepolia, rootstock, rootstockTestnet } from 'viem/chains'; +import { ENV } from '../env'; type ChainConfig = { key: string; chainId: number; @@ -43,7 +44,9 @@ const items = [ name: 'BOB Sepolia', rpc: createPublicClient({ chain: bobSepolia, - transport: http(bobSepolia.rpcUrls.default.http[0]), + transport: http( + ENV.RPC_BOB_TESTNET ?? bobSepolia.rpcUrls.default.http[0], + ), }) as PublicClient, aaveSubgraphUrl: 'https://bob-mm.test.sovryn.app/subgraphs/name/DistributedCollective/sov-protocol-subgraphs', diff --git a/apps/indexer/src/configs/constants.ts b/apps/indexer/src/configs/constants.ts index dd27489..6b93553 100644 --- a/apps/indexer/src/configs/constants.ts +++ b/apps/indexer/src/configs/constants.ts @@ -1,2 +1,4 @@ export const BASE_DEFINITIONS_URL = 'https://raw.githubusercontent.com/DistributedCollective/slayer-data/main'; + +export const USD_DECIMALS = 8; diff --git a/apps/indexer/src/env.ts b/apps/indexer/src/env.ts index ce8445e..b4fdc7c 100644 --- a/apps/indexer/src/env.ts +++ b/apps/indexer/src/env.ts @@ -1,5 +1,13 @@ import 'dotenv/config'; -import { bool, cleanEnv, makeValidator, port, str, testOnly } from 'envalid'; +import { + bool, + cleanEnv, + makeValidator, + port, + str, + testOnly, + url, +} from 'envalid'; export const ENV = cleanEnv(process.env, { PORT: port({ default: 8000 }), @@ -25,4 +33,8 @@ export const ENV = cleanEnv(process.env, { throw new Error('FLAGS must be a comma-separated list of strings'); } })({ default: [] }), + + RPC_BOB_TESTNET: url({ default: undefined }), + + NO_CACHE: bool({ default: false }), }); diff --git a/apps/indexer/src/libs/aave/index.ts b/apps/indexer/src/libs/aave/index.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/indexer/src/libs/aave/index.ts @@ -0,0 +1 @@ + diff --git a/apps/indexer/src/libs/loaders/money-market.ts b/apps/indexer/src/libs/loaders/money-market.ts index 34375df..427a50b 100644 --- a/apps/indexer/src/libs/loaders/money-market.ts +++ b/apps/indexer/src/libs/loaders/money-market.ts @@ -1,43 +1,15 @@ import { Address } from 'viem'; +import { maybeCache } from '../../app/plugins/cache'; import { ChainId, chains, ChainSelector } from '../../configs/chains'; import { BASE_DEFINITIONS_URL } from '../../configs/constants'; -// uses contract from @aave/contract-helpers +// uses contract from @aave/contract-helpers (legacy uiprovider ABI) // @see https://github.com/aave/aave-utilities/blob/%40aave/contract-helpers%401.29.1/packages/contract-helpers/ const uiPoolDataProviderAbi = [ { inputs: [ { - internalType: 'contract IChainlinkAggregator', - name: '_networkBaseTokenPriceInUsdProxyAggregator', - type: 'address', - }, - { - internalType: 'contract IChainlinkAggregator', - name: '_marketReferenceCurrencyPriceInUsdProxyAggregator', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'ETH_CURRENCY_UNIT', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'contract ILendingPoolAddressesProvider', + internalType: 'contract IPoolAddressesProvider', name: 'provider', type: 'address', }, @@ -191,6 +163,11 @@ const uiPoolDataProviderAbi = [ name: 'priceInMarketReferenceCurrency', type: 'uint256', }, + { + internalType: 'address', + name: 'priceOracle', + type: 'address', + }, { internalType: 'uint256', name: 'variableRateSlope1', @@ -211,8 +188,108 @@ const uiPoolDataProviderAbi = [ name: 'stableRateSlope2', type: 'uint256', }, + { + internalType: 'uint256', + name: 'baseStableBorrowRate', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'baseVariableBorrowRate', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'optimalUsageRatio', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'isPaused', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isSiloedBorrowing', + type: 'bool', + }, + { + internalType: 'uint128', + name: 'accruedToTreasury', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'unbacked', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'isolationModeTotalDebt', + type: 'uint128', + }, + { + internalType: 'bool', + name: 'flashLoanEnabled', + type: 'bool', + }, + { + internalType: 'uint256', + name: 'debtCeiling', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'debtCeilingDecimals', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'eModeCategoryId', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'borrowCap', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'supplyCap', + type: 'uint256', + }, + { + internalType: 'uint16', + name: 'eModeLtv', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'eModeLiquidationThreshold', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'eModeLiquidationBonus', + type: 'uint16', + }, + { + internalType: 'address', + name: 'eModePriceSource', + type: 'address', + }, + { + internalType: 'string', + name: 'eModeLabel', + type: 'string', + }, + { + internalType: 'bool', + name: 'borrowableInIsolation', + type: 'bool', + }, ], - internalType: 'struct IUiPoolDataProvider.AggregatedReserveData[]', + internalType: 'struct IUiPoolDataProviderV3.AggregatedReserveData[]', name: '', type: 'tuple[]', }, @@ -239,7 +316,7 @@ const uiPoolDataProviderAbi = [ type: 'uint8', }, ], - internalType: 'struct IUiPoolDataProvider.BaseCurrencyInfo', + internalType: 'struct IUiPoolDataProviderV3.BaseCurrencyInfo', name: '', type: 'tuple', }, @@ -250,7 +327,7 @@ const uiPoolDataProviderAbi = [ { inputs: [ { - internalType: 'contract ILendingPoolAddressesProvider', + internalType: 'contract IPoolAddressesProvider', name: 'provider', type: 'address', }, @@ -269,7 +346,7 @@ const uiPoolDataProviderAbi = [ { inputs: [ { - internalType: 'contract ILendingPoolAddressesProvider', + internalType: 'contract IPoolAddressesProvider', name: 'provider', type: 'address', }, @@ -319,7 +396,7 @@ const uiPoolDataProviderAbi = [ type: 'uint256', }, ], - internalType: 'struct IUiPoolDataProvider.UserReserveData[]', + internalType: 'struct IUiPoolDataProviderV3.UserReserveData[]', name: '', type: 'tuple[]', }, @@ -332,32 +409,6 @@ const uiPoolDataProviderAbi = [ stateMutability: 'view', type: 'function', }, - { - inputs: [], - name: 'marketReferenceCurrencyPriceInUsdProxyAggregator', - outputs: [ - { - internalType: 'contract IChainlinkAggregator', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'networkBaseTokenPriceInUsdProxyAggregator', - outputs: [ - { - internalType: 'contract IChainlinkAggregator', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, ] as const; export type PoolDefinition = { @@ -375,20 +426,105 @@ export type PoolDefinition = { priceFeedURI: string; }; +export interface PoolBaseCurrencyHumanized { + marketReferenceCurrencyDecimals: number; + marketReferenceCurrencyPriceInUsd: string; + networkBaseTokenPriceInUsd: string; + networkBaseTokenPriceDecimals: number; +} + +export interface ReserveDataHumanized { + originalId: number; + id: string; + underlyingAsset: string; + name: string; + symbol: string; + decimals: number; + baseLTVasCollateral: string; + reserveLiquidationThreshold: string; + reserveLiquidationBonus: string; + reserveFactor: string; + usageAsCollateralEnabled: boolean; + borrowingEnabled: boolean; + isActive: boolean; + isFrozen: boolean; + liquidityIndex: string; + variableBorrowIndex: string; + liquidityRate: string; + variableBorrowRate: string; + lastUpdateTimestamp: number; + aTokenAddress: string; + variableDebtTokenAddress: string; + interestRateStrategyAddress: string; + availableLiquidity: string; + totalScaledVariableDebt: string; + priceInMarketReferenceCurrency: string; + priceOracle: string; + variableRateSlope1: string; + variableRateSlope2: string; + baseVariableBorrowRate: string; + optimalUsageRatio: string; + + stableBorrowRateEnabled: boolean; + stableBorrowRate: string; + stableDebtTokenAddress: string; + totalPrincipalStableDebt: string; + averageStableRate: string; + stableDebtLastUpdateTimestamp: number; + + stableRateSlope1: string; + stableRateSlope2: string; + baseStableBorrowRate: string; + + eModeCategoryId: number; + eModeLtv: number; + eModeLiquidationThreshold: number; + eModeLiquidationBonus: number; + eModePriceSource: string; + eModeLabel: string; + // v3 only + isPaused: boolean; + isSiloedBorrowing: boolean; + accruedToTreasury: string; + unbacked: string; + isolationModeTotalDebt: string; + flashLoanEnabled: boolean; + debtCeiling: string; + debtCeilingDecimals: number; + borrowCap: string; + supplyCap: string; + borrowableInIsolation: boolean; + virtualAccActive: boolean; + virtualUnderlyingBalance: string; +} + +export interface ReservesDataHumanized { + reservesData: ReserveDataHumanized[]; + baseCurrencyData: PoolBaseCurrencyHumanized; +} + export async function fetchPoolList(chainId: ChainId) { - const url = `${BASE_DEFINITIONS_URL}/chains/${chainId}/money-market.json`; + return maybeCache( + `pool:list:${chainId}`, + async () => { + const url = `${BASE_DEFINITIONS_URL}/chains/${chainId}/money-market.json`; - const response = await fetch(url); + const response = await fetch(url); - if (!response.ok) { - throw new Error( - `Failed to fetch pools for chainId ${chainId}: ${response.statusText}`, - ); - } + if (!response.ok) { + throw new Error( + `Failed to fetch pools for chainId ${chainId}: ${response.statusText}`, + ); + } - const data = (await response.json()) as { items: PoolDefinition[] }; + const data = (await response.json()) as { items: PoolDefinition[] }; - return data.items; + return data.items; + }, + { + ttlSeconds: 60 * 5, // 5 minutes + }, + ); } export function selectPoolById( @@ -407,28 +543,160 @@ export async function fetchPoolReserves( throw new Error(`Unsupported chain: ${chainId}`); } - return chain.rpc.readContract({ - address: pool.uiPoolDataProvider, - abi: uiPoolDataProviderAbi, - functionName: 'getReservesData', - args: [pool.poolAddressesProvider], - }); + return maybeCache( + `pool:reserves:${chainId}:${pool.address}`, + async () => { + const { 0: reservesRaw, 1: poolBaseCurrencyRaw } = + await chain.rpc.readContract({ + address: pool.uiPoolDataProvider, + abi: uiPoolDataProviderAbi, + functionName: 'getReservesData', + args: [pool.poolAddressesProvider], + }); + + const baseCurrencyData: PoolBaseCurrencyHumanized = { + // this is to get the decimals from the unit so 1e18 = string length of 19 - 1 to get the number of 0 + marketReferenceCurrencyDecimals: + poolBaseCurrencyRaw.marketReferenceCurrencyUnit.toString().length - 1, + marketReferenceCurrencyPriceInUsd: + poolBaseCurrencyRaw.marketReferenceCurrencyPriceInUsd.toString(), + networkBaseTokenPriceInUsd: + poolBaseCurrencyRaw.networkBaseTokenPriceInUsd.toString(), + networkBaseTokenPriceDecimals: + poolBaseCurrencyRaw.networkBaseTokenPriceDecimals, + }; + + const reserves = reservesRaw.map((reserveRaw) => { + return { + originalId: reserveRaw.liquidityIndex.toNumber(), + id: `${chainId}-${reserveRaw.underlyingAsset}-${pool.poolAddressesProvider}`.toLowerCase(), + underlyingAsset: reserveRaw.underlyingAsset.toLowerCase(), + name: reserveRaw.name, + symbol: 'todo', // todo + // symbol: ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] + // ? ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] + // : reserveRaw.symbol, + decimals: reserveRaw.decimals.toNumber(), + baseLTVasCollateral: reserveRaw.baseLTVasCollateral.toString(), + reserveLiquidationThreshold: + reserveRaw.reserveLiquidationThreshold.toString(), + reserveLiquidationBonus: + reserveRaw.reserveLiquidationBonus.toString(), + reserveFactor: reserveRaw.reserveFactor.toString(), + usageAsCollateralEnabled: reserveRaw.usageAsCollateralEnabled, + borrowingEnabled: reserveRaw.borrowingEnabled, + stableBorrowRateEnabled: reserveRaw.stableBorrowRateEnabled, + isActive: reserveRaw.isActive, + isFrozen: reserveRaw.isFrozen, + liquidityIndex: reserveRaw.liquidityIndex.toString(), + variableBorrowIndex: reserveRaw.variableBorrowIndex.toString(), + liquidityRate: reserveRaw.liquidityRate.toString(), + variableBorrowRate: reserveRaw.variableBorrowRate.toString(), + stableBorrowRate: reserveRaw.stableBorrowRate.toString(), + lastUpdateTimestamp: reserveRaw.lastUpdateTimestamp, + aTokenAddress: reserveRaw.aTokenAddress.toString(), + stableDebtTokenAddress: reserveRaw.stableDebtTokenAddress.toString(), + variableDebtTokenAddress: + reserveRaw.variableDebtTokenAddress.toString(), + interestRateStrategyAddress: + reserveRaw.interestRateStrategyAddress.toString(), + availableLiquidity: reserveRaw.availableLiquidity.toString(), + totalPrincipalStableDebt: + reserveRaw.totalPrincipalStableDebt.toString(), + averageStableRate: reserveRaw.averageStableRate.toString(), + stableDebtLastUpdateTimestamp: + reserveRaw.stableDebtLastUpdateTimestamp.toNumber(), + totalScaledVariableDebt: + reserveRaw.totalScaledVariableDebt.toString(), + priceInMarketReferenceCurrency: + reserveRaw.priceInMarketReferenceCurrency.toString(), + priceOracle: reserveRaw.priceOracle, + variableRateSlope1: reserveRaw.variableRateSlope1.toString(), + variableRateSlope2: reserveRaw.variableRateSlope2.toString(), + stableRateSlope1: reserveRaw.stableRateSlope1.toString(), + stableRateSlope2: reserveRaw.stableRateSlope2.toString(), + baseStableBorrowRate: reserveRaw.baseStableBorrowRate.toString(), + baseVariableBorrowRate: reserveRaw.baseVariableBorrowRate.toString(), + optimalUsageRatio: reserveRaw.optimalUsageRatio.toString(), + // new fields + isPaused: reserveRaw.isPaused, + debtCeiling: reserveRaw.debtCeiling.toString(), + eModeCategoryId: reserveRaw.eModeCategoryId, + borrowCap: reserveRaw.borrowCap.toString(), + supplyCap: reserveRaw.supplyCap.toString(), + eModeLtv: reserveRaw.eModeLtv, + eModeLiquidationThreshold: reserveRaw.eModeLiquidationThreshold, + eModeLiquidationBonus: reserveRaw.eModeLiquidationBonus, + eModePriceSource: reserveRaw.eModePriceSource.toString(), + eModeLabel: reserveRaw.eModeLabel.toString(), + borrowableInIsolation: reserveRaw.borrowableInIsolation, + accruedToTreasury: reserveRaw.accruedToTreasury.toString(), + unbacked: reserveRaw.unbacked.toString(), + isolationModeTotalDebt: reserveRaw.isolationModeTotalDebt.toString(), + debtCeilingDecimals: reserveRaw.debtCeilingDecimals.toNumber(), + isSiloedBorrowing: reserveRaw.isSiloedBorrowing, + flashLoanEnabled: reserveRaw.flashLoanEnabled, + virtualAccActive: false, + virtualUnderlyingBalance: '0', + } satisfies ReserveDataHumanized; + }); + + return { + reservesData: reserves, + baseCurrencyData, + } satisfies ReservesDataHumanized; + }, + { + ttlSeconds: 30, + }, + ); +} + +export interface UserReserveDataHumanized { + id: string; + underlyingAsset: string; + scaledATokenBalance: string; + usageAsCollateralEnabledOnUser: boolean; + stableBorrowRate: string; + scaledVariableDebt: string; + principalStableDebt: string; + stableBorrowLastUpdateTimestamp: number; } export async function fetchUserReserves( chainId: ChainSelector, pool: PoolDefinition, user: string, -) { +): Promise<{ + userReserves: UserReserveDataHumanized[]; + userEmodeCategoryId: number; +}> { const chain = chains.get(chainId); if (!chain) { throw new Error(`Unsupported chain: ${chainId}`); } - return chain.rpc.readContract({ - address: pool.uiPoolDataProvider, - abi: uiPoolDataProviderAbi, - functionName: 'getUserReservesData', - args: [pool.poolAddressesProvider, user as Address], - }); + const { 0: userReservesRaw, 1: userEmodeCategoryId } = + await chain.rpc.readContract({ + address: pool.uiPoolDataProvider, + abi: uiPoolDataProviderAbi, + functionName: 'getUserReservesData', + args: [pool.poolAddressesProvider, user as Address], + }); + + return { + userReserves: userReservesRaw.map((userReserveRaw) => ({ + id: `${chainId}-${user}-${userReserveRaw.underlyingAsset}-${pool.poolAddressesProvider}`.toLowerCase(), + underlyingAsset: userReserveRaw.underlyingAsset.toLowerCase(), + scaledATokenBalance: userReserveRaw.scaledATokenBalance.toString(), + usageAsCollateralEnabledOnUser: + userReserveRaw.usageAsCollateralEnabledOnUser, + stableBorrowRate: userReserveRaw.stableBorrowRate.toString(), + scaledVariableDebt: userReserveRaw.scaledVariableDebt.toString(), + principalStableDebt: userReserveRaw.principalStableDebt.toString(), + stableBorrowLastUpdateTimestamp: + userReserveRaw.stableBorrowLastUpdateTimestamp.toNumber(), + })), + userEmodeCategoryId, + }; } diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx index 994667b..e80edc7 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx @@ -220,9 +220,13 @@ export const AssetsTable: FC = ({ assets }) => { - {/*

- -

*/} +

+ +

diff --git a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx index 18c13db..78c588d 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx @@ -154,13 +154,13 @@ export const AssetsTable: FC = ({ assets }) => { - {/*

+

-

*/} +

diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index 60de281..8044bfe 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -8,6 +8,8 @@ export type Mode = (typeof modes)[keyof typeof modes]; export const DEFAULT_PAGE_LIMIT = 100; +export const USD_DECIMALS = 8; + export const INDEXER_URL = { [modes.production]: 'https://slayer-indexer.sovryn.app', [modes.staging]: 'https://slayer-indexer.staging.sovryn.app', diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 8d036ee..cc3b20f 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -85,7 +85,9 @@ export type MoneyMarketPoolPosition = { reserve: any; // todo suppliedBalance: string; + suppliedBalanceUsd: string; borrowedBalance: string; + borrowedBalanceUsd: string; underlyingAsset: string; scaledATokenBalance: string; diff --git a/packages/shared/src/lib/decimal.ts b/packages/shared/src/lib/decimal.ts index 50337f4..7d9569f 100644 --- a/packages/shared/src/lib/decimal.ts +++ b/packages/shared/src/lib/decimal.ts @@ -62,6 +62,10 @@ export class Decimal { throw new Error(`Cannot convert to Decimal: ${value}`); } + static pow(exponent: number): Decimal { + return Decimal.from(1).mul(10 ** exponent); + } + toBigInt(): bigint { return BigInt(this.d.mul(new D(10).pow(this.precision)).toFixed(0)); } diff --git a/packages/shared/src/lib/http-client.ts b/packages/shared/src/lib/http-client.ts index b0d94cb..ee296c0 100644 --- a/packages/shared/src/lib/http-client.ts +++ b/packages/shared/src/lib/http-client.ts @@ -95,6 +95,7 @@ export class HttpClient { if (isTimeout) throw new HTTPTimeoutError(url, reason); throw new HTTPAbortError(url, reason); } + console.error('HTTP request failed', { error, url }); throw new HTTPClientError(500, 'Internal Server Error', '', url); } finally { if (timeout) clearTimeout(timeout); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b67eb7..b9931e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,6 +152,9 @@ importers: apps/indexer: dependencies: + '@aave/math-utils': + specifier: ^1.29.1 + version: 1.36.2(bignumber.js@9.3.1)(tslib@2.8.1) '@bull-board/api': specifier: 6.13.0 version: 6.13.0(@bull-board/ui@6.13.0) @@ -19980,7 +19983,7 @@ snapshots: extension-port-stream@3.0.0: dependencies: - readable-stream: 3.6.2 + readable-stream: 4.7.0 webextension-polyfill: 0.10.0 eyes@0.1.8: {} From ee9d4402016af57785b0ecd233c2290b2cfcc480 Mon Sep 17 00:00:00 2001 From: Rytis Grincevicius Date: Mon, 8 Dec 2025 09:25:00 +0200 Subject: [PATCH 3/5] feat: money market reserves info --- apps/indexer/src/app/routes/_chain/routes.ts | 57 +++++++++--- .../components/AssetsTable/AssetsTable.tsx | 62 +++---------- .../LendAssetsList/LendAssetsList.tsx | 34 +------ .../components/AssetsTable/AssetsTable.tsx | 88 ++++--------------- .../components/LendDialog/LendDialog.tsx | 3 +- apps/web-app/src/components/ui/select.tsx | 2 +- apps/web-app/src/routes/money-market.tsx | 11 +-- packages/sdk/src/types.ts | 20 ++--- 8 files changed, 88 insertions(+), 189 deletions(-) diff --git a/apps/indexer/src/app/routes/_chain/routes.ts b/apps/indexer/src/app/routes/_chain/routes.ts index 8830dcd..03aeb80 100644 --- a/apps/indexer/src/app/routes/_chain/routes.ts +++ b/apps/indexer/src/app/routes/_chain/routes.ts @@ -1,4 +1,9 @@ -import { formatReserves, formatUserSummary } from '@aave/math-utils'; +import { + formatReserves, + formatUserSummary, + USD_DECIMALS, +} from '@aave/math-utils'; +import { areAddressesEqual, Decimal } from '@sovryn/slayer-shared'; import { and, asc, eq, gte, inArray } from 'drizzle-orm'; import { FastifyRequest } from 'fastify'; import z from 'zod'; @@ -98,16 +103,16 @@ export default async function (fastify: ZodFastifyInstance) { pool, ); - // const tokens = await client.query.tTokens.findMany({ - // columns: tTokensSelectors.columns, - // where: and( - // eq(tTokens.chainId, req.chain.chainId), - // inArray( - // tTokens.address, - // reservesRaw.map((i) => i.underlyingAsset.toLowerCase()), - // ), - // ), - // }); + const tokens = await client.query.tTokens.findMany({ + columns: tTokensSelectors.columns, + where: and( + eq(tTokens.chainId, req.chain.chainId), + inArray( + tTokens.address, + reservesData.map((i) => i.underlyingAsset.toLowerCase()), + ), + ), + }); const data = formatReserves({ reserves: reservesData, @@ -198,7 +203,35 @@ export default async function (fastify: ZodFastifyInstance) { // }, // ); - return { data: { reservesData: data, baseCurrencyData } }; + const items = data.map((item) => { + const token = tokens.find((t) => + areAddressesEqual(t.address, item.underlyingAsset), + ); + + return { + id: item.id, + pool, + token, + priceUsd: Decimal.from(item.priceInUSD).toFixed(USD_DECIMALS), + liquidity: Decimal.from(item.totalLiquidity).toString(), + liquidityUsd: Decimal.from(item.totalLiquidity) + .mul(Decimal.from(item.priceInUSD)) + .toFixed(USD_DECIMALS), + borrowApy: Decimal.from(item.variableBorrowAPY) + .mul(100) + .toFixed(USD_DECIMALS), + canBeBorrowed: item.borrowingEnabled, + supplyApy: Decimal.from(item.supplyAPY) + .mul(100) + .toFixed(USD_DECIMALS), + canBeCollateral: item.usageAsCollateralEnabled, + isActive: item.isActive, + isFroze: item.isFrozen, + eModes: item.eModes, + }; + }); + + return { data: { reservesData: items, baseCurrencyData } }; // return { // data: items diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx index 9671b5f..ea08c39 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx @@ -19,12 +19,6 @@ type AssetsTableProps = { }; export const AssetsTable: FC = ({ assets }) => { - // const [sortedAssets, setSortedAssets] = - // useState(assets); - // useEffect(() => { - // setSortedAssets(assets); - // }, [assets]); - const handleBorrow = (reserve: MoneyMarketPoolReserve) => borrowRequestStore.getState().setReserve(reserve); @@ -36,16 +30,6 @@ export const AssetsTable: FC = ({ assets }) => {
Asset - {/* {assets.some((asset) => asset.isSortable) && ( - - )} */}
@@ -54,16 +38,6 @@ export const AssetsTable: FC = ({ assets }) => { Available
- {/* {assets.some((asset) => asset.isSortable) && ( - - )} */}
@@ -72,16 +46,6 @@ export const AssetsTable: FC = ({ assets }) => { APY
- {/* {assets.some((asset) => asset.isSortable) && ( - - )} */}
@@ -106,30 +70,32 @@ export const AssetsTable: FC = ({ assets }) => {
- - - {asset.token.symbol} - -

+

+ -

+
-
-

{0}%

-
+
diff --git a/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/LendAssetsList.tsx b/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/LendAssetsList.tsx index 571e884..97369b4 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/LendAssetsList.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/LendAssetsList.tsx @@ -1,8 +1,6 @@ import { Accordion } from '@/components/ui/accordion'; -import { Checkbox } from '@/components/ui/checkbox'; -import { Label } from '@/components/ui/label'; import type { MoneyMarketPoolReserve } from '@sovryn/slayer-sdk'; -import { useCallback, useMemo, useState, type FC } from 'react'; +import { useState, type FC } from 'react'; import { AssetsTable } from './components/AssetsTable/AssetsTable'; type LendPAssetsListProps = { @@ -12,17 +10,6 @@ type LendPAssetsListProps = { export const LendAssetsList: FC = ({ lendAssets }) => { const [open, setOpen] = useState(false); - const [showZeroBalances, setShowZeroBalances] = useState(false); - - const handleShowBalances = useCallback( - () => setShowZeroBalances((prevState) => !prevState), - [], - ); - - const filteredAssets = useMemo( - () => lendAssets, - [lendAssets, showZeroBalances], - ); return ( = ({ lendAssets }) => { open={open} onClick={setOpen} > -
-
- - -
-
- - +
); }; diff --git a/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx index e4e9f0b..c648645 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx @@ -20,45 +20,6 @@ type AssetsTableProps = { }; export const AssetsTable: FC = ({ assets }) => { - // const [sortDirection, setSortDirection] = useState( - // OrderType.ASC, - // ); - // const [sortedAssets, setSortedAssets] = - // useState(assets); - // useEffect(() => { - // setSortedAssets(assets); - // }, [assets]); - - // const sortAssets = useCallback( - // (column: keyof MoneyMarketPoolReserve) => { - // const newSortDirection = - // sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; - // setSortDirection(newSortDirection); - - // // const sorted = [...sortedAssets].sort((a, b) => { - // // if (column === OrderColumn.SYMBOL) { - // // return newSortDirection === OrderType.ASC - // // ? a[column].localeCompare(b[column]) - // // : b[column].localeCompare(a[column]); - // // } else if (column === OrderColumn.BALANCE) { - // // const balanceA = parseFloat(a.balance.replace(/,/g, '')); - // // const balanceB = parseFloat(b.balance.replace(/,/g, '')); - // // return newSortDirection === OrderType.ASC - // // ? balanceA - balanceB - // // : balanceB - balanceA; - // // } else if (column === OrderColumn.APY) { - // // const apyA = parseFloat(a.apy.replace('%', '')); - // // const apyB = parseFloat(b.apy.replace('%', '')); - // // return newSortDirection === OrderType.ASC ? apyA - apyB : apyB - apyA; - // // } - // // return 0; - // // }); - - // // setSortedAssets(sorted); - // }, - // [sortDirection], - // ); - const handleLending = (reserve: MoneyMarketPoolReserve) => lendRequestStore.getState().setReserve(reserve); @@ -69,31 +30,11 @@ export const AssetsTable: FC = ({ assets }) => {
Asset - {/* {assets.some((asset) => asset.isSortable) && ( - - )} */}
- Wallet balance - {/* {assets.some((asset) => asset.isSortable) && ( - - )} */} + Liquidity
@@ -102,16 +43,6 @@ export const AssetsTable: FC = ({ assets }) => { APY
- {/* {assets.some((asset) => asset.isSortable) && ( - - )} */} @@ -141,16 +72,27 @@ export const AssetsTable: FC = ({ assets }) => {
- +
+ + +
- +
- {asset.usageAsCollateralEnabled ? ( + {asset.canBeCollateral ? ( ) : ( diff --git a/apps/web-app/src/components/MoneyMarket/components/LendDialog/LendDialog.tsx b/apps/web-app/src/components/MoneyMarket/components/LendDialog/LendDialog.tsx index bee5c4c..4641a89 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendDialog/LendDialog.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendDialog/LendDialog.tsx @@ -24,7 +24,6 @@ const LendDialogForm = () => { const { begin } = useSlayerTx({ onClosed: (ok: boolean) => { - console.log('lend tx modal closed, success:', ok); if (ok) { // close lending dialog if tx was successful lendRequestStore.getState().reset(); @@ -99,7 +98,7 @@ const LendDialogForm = () => {

{reserve.token.symbol} can be used as collateral:{' '} - {reserve.usageAsCollateralEnabled ? 'Yes' : 'No'} + {reserve.canBeCollateral ? 'Yes' : 'No'}

diff --git a/apps/web-app/src/components/ui/select.tsx b/apps/web-app/src/components/ui/select.tsx index 47884d1..28552d1 100644 --- a/apps/web-app/src/components/ui/select.tsx +++ b/apps/web-app/src/components/ui/select.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; import * as SelectPrimitive from '@radix-ui/react-select'; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'; +import * as React from 'react'; import { cn } from '@/lib/utils'; diff --git a/apps/web-app/src/routes/money-market.tsx b/apps/web-app/src/routes/money-market.tsx index 3fd732d..6ca6cf4 100644 --- a/apps/web-app/src/routes/money-market.tsx +++ b/apps/web-app/src/routes/money-market.tsx @@ -55,18 +55,11 @@ export const Route = createFileRoute('/money-market')({ const owner = context.connection().address; if (owner) { client.prefetchQuery({ - queryKey: ['money-market:borrows', pool || 'default', owner], + queryKey: ['money-market:positions', pool || 'default', owner], queryFn: () => sdk.moneyMarket.listUserPositions(pool || 'default', owner), staleTime: STALE_TIME, }); - - client.prefetchQuery({ - queryKey: ['money-market:lendings', pool || 'default', owner], - queryFn: () => - sdk.moneyMarket.listUserLendings(pool || 'default', owner), - staleTime: STALE_TIME, - }); } }, }); @@ -96,7 +89,7 @@ function RouteComponent() { }); const borrowAssets = useMemo( - () => (reserves?.data ?? []).filter((r) => r.borrowingEnabled), + () => (reserves?.data ?? []).filter((r) => r.canBeBorrowed), [reserves], ); diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index cc3b20f..f0a7f64 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -34,25 +34,21 @@ export type Token = Pick & export interface MoneyMarketPoolReserve { id: string; - originalId: number; + // originalId: number; token: SdkToken; pool: MoneyMarketPool; - availableLiquidity: string; - baseLTVasCollateral: string; + liquidity: string; + liquidityUsd: string; - borrowingEnabled: boolean; - usageAsCollateralEnabled: boolean; + borrowApy: string; + supplyApy: string; + + canBeBorrowed: boolean; + canBeCollateral: boolean; isActive: boolean; isFrozen: boolean; - - liquidityRate: string; - variableBorrowRate: string; - - reserveFactor: string; - reserveLiquidationBonus: string; - reserveLiquidationThreshold: string; } export interface MoneyMarketPool { From cfe8af216f10acf55224a11300a43a060556b0e7 Mon Sep 17 00:00:00 2001 From: Rytis Grincevicius Date: Mon, 8 Dec 2025 14:23:36 +0200 Subject: [PATCH 4/5] fix: amount tables --- apps/indexer/src/app/routes/_chain/routes.ts | 205 +++++++++++++++++- .../BorrowPositionsList.tsx | 33 ++- .../components/AssetsTable/AssetsTable.tsx | 167 ++++---------- .../LendPositionsList/LendPositionsList.tsx | 6 +- .../components/AssetsTable/AssetsTable.tsx | 89 ++------ apps/web-app/src/routes/money-market.tsx | 22 +- .../money-market/money-market.manager.ts | 8 +- packages/sdk/src/types.ts | 61 ++++-- 8 files changed, 351 insertions(+), 240 deletions(-) diff --git a/apps/indexer/src/app/routes/_chain/routes.ts b/apps/indexer/src/app/routes/_chain/routes.ts index 03aeb80..3931311 100644 --- a/apps/indexer/src/app/routes/_chain/routes.ts +++ b/apps/indexer/src/app/routes/_chain/routes.ts @@ -286,7 +286,7 @@ export default async function (fastify: ZodFastifyInstance) { req.params.address, ); - await client.query.tTokens.findMany({ + const tokens = await client.query.tTokens.findMany({ columns: tTokensSelectors.columns, where: and( eq(tTokens.chainId, req.chain.chainId), @@ -379,8 +379,209 @@ export default async function (fastify: ZodFastifyInstance) { // }; // }); + const netWorth = Decimal.from(summary.netWorthUSD); + const borrowBalance = Decimal.from(summary.totalBorrowsUSD); + const supplyBalance = summary.userReservesData.reduce( + (s, r) => s.add(r.underlyingBalanceUSD), + Decimal.from(0), + ); + + const collateralBalance = summary.userReservesData.reduce( + (s, r) => + r.usageAsCollateralEnabledOnUser ? s.add(r.underlyingBalanceUSD) : s, + Decimal.from(0), + ); + + const computeWeightedSupplyApy = () => { + let totalSuppliedUsd = Decimal.from(0); + let weightedSupplyAPYSum = Decimal.from(0); + + summary.userReservesData.forEach((reserve) => { + const suppliedAmountUsd = Decimal.from(reserve.underlyingBalanceUSD); + const supplyAPY = Decimal.from(reserve.reserve.supplyAPY); + + weightedSupplyAPYSum = weightedSupplyAPYSum.add( + supplyAPY.mul(suppliedAmountUsd), + ); + totalSuppliedUsd = totalSuppliedUsd.add(suppliedAmountUsd); + }); + + if (totalSuppliedUsd.eq(0) || weightedSupplyAPYSum.eq(0)) { + return Decimal.from(0); + } + return weightedSupplyAPYSum.div(totalSuppliedUsd).mul(100); + }; + + const computeWeightedBorrowApy = () => { + let totalBorrowedUsd = Decimal.from(0); + let weightedBorrowAPYSum = Decimal.from(0); + + summary.userReservesData.forEach((reserve) => { + const borrowedAmountUsd = Decimal.from(reserve.totalBorrowsUSD); + const borrowAPY = Decimal.from(reserve.reserve.variableBorrowAPY); + + weightedBorrowAPYSum = weightedBorrowAPYSum.add( + borrowAPY.mul(borrowedAmountUsd), + ); + totalBorrowedUsd = totalBorrowedUsd.add(borrowedAmountUsd); + }); + + if (totalBorrowedUsd.eq(0) || weightedBorrowAPYSum.eq(0)) { + return Decimal.from(0); + } + return weightedBorrowAPYSum.div(totalBorrowedUsd).mul(100); + }; + + const supplyWeightedApy = computeWeightedSupplyApy(); + const borrowWeightedApy = computeWeightedBorrowApy(); + + const netApy = netWorth.eq(0) + ? Decimal.from(0) + : supplyWeightedApy + .mul(supplyBalance) + .div(netWorth) + .sub(borrowWeightedApy.mul(borrowBalance).div(netWorth)); + + const currentLiquidationThreshold = Decimal.from( + summary.currentLiquidationThreshold, + ); + const borrowPower = collateralBalance + .mul(currentLiquidationThreshold) + .div(1.1); + const borrowPowerUsed = borrowPower.eq(0) + ? Decimal.from(100) + : Decimal.from(borrowBalance).div(borrowPower).mul(100); + + const healthFactor = borrowBalance.eq(0) + ? Decimal.INFINITY + : collateralBalance.mul(currentLiquidationThreshold).div(borrowBalance); + + const collateralRatio = borrowBalance.eq(0) + ? Decimal.INFINITY + : Decimal.from(summary.healthFactor); + + const userPositions = summary.userReservesData.map((item) => { + const token = tokens.find((t) => + areAddressesEqual(t.address, item.underlyingAsset), + ); + + const availableLiquidity = Decimal.from( + item.reserve.availableLiquidity, + item.reserve.decimals, + ); + // how much the user can borrow if there is no limit of supply + const canBorrow = Decimal.max( + borrowPower.sub(borrowBalance).div(item.reserve.priceInUSD), + Decimal.ZERO, + ); + + // available to borrow for user including liquidity limitation + const availableToBorrow = availableLiquidity.lt(canBorrow) + ? availableLiquidity + : canBorrow; + const availableToBorrowUsd = availableToBorrow.mul( + item.reserve.priceInUSD, + ); + + const borrowRateMode = Decimal.from(item.variableBorrows).gt(0) ? 2 : 1; + + const canToggleCollateral = + !item.usageAsCollateralEnabledOnUser || + (borrowBalance.eq(0) + ? Decimal.INFINITY + : collateralBalance + .sub(item.underlyingBalanceUSD) + .div(borrowBalance) + ).gt(1.5); + + return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + id: (item as any).id, + pool, + token, + reserve: { + id: item.reserve.id, + priceUsd: Decimal.from(item.reserve.priceInUSD).toFixed( + USD_DECIMALS, + ), + liquidity: Decimal.from(item.reserve.totalLiquidity).toString(), + liquidityUsd: Decimal.from(item.reserve.totalLiquidity) + .mul(Decimal.from(item.reserve.priceInUSD)) + .toFixed(USD_DECIMALS), + borrowApy: Decimal.from(item.reserve.variableBorrowAPY) + .mul(100) + .toFixed(USD_DECIMALS), + canBeBorrowed: item.reserve.borrowingEnabled, + supplyApy: Decimal.from(item.reserve.supplyAPY) + .mul(100) + .toFixed(USD_DECIMALS), + canBeCollateral: item.reserve.usageAsCollateralEnabled, + isActive: item.reserve.isActive, + isFroze: item.reserve.isFrozen, + eModes: item.reserve.eModes, + }, + supplied: item.underlyingBalance, + suppliedUsd: item.underlyingBalanceUSD, + + supplyApy: Decimal.from(item.reserve.supplyAPY).mul(100).toString(), + canToggleCollateral, + + borrowed: item.variableBorrows, + borrowedUsd: item.variableBorrowsUSD, + + collateral: item.usageAsCollateralEnabledOnUser, + + availableToBorrow: availableToBorrow.toString(), + availableToBorrowUsd: availableToBorrowUsd.toFixed(USD_DECIMALS), + + borrowRateMode, + borrowApy: Decimal.from( + borrowRateMode === 1 + ? // @ts-expect-error stableBorrowAPY exists + (item.reserve.stableBorrowAPY ?? 0) + : item.reserve.variableBorrowAPY, + ) + .mul(100) + .toString(), + // @ts-expect-error stableBorrowAPY exists + stableApy: Decimal.from(item.reserve.stableBorrowAPY ?? 0) + .mul(100) + .toString(), + variableApy: Decimal.from(item.reserve.variableBorrowAPY ?? 0) + .mul(100) + .toString(), + }; + }); + return { - data: summary, + data: { + positions: userPositions, + summary: { + netApy: netApy.toFixed(USD_DECIMALS), + healthFactor: healthFactor.toFixed(USD_DECIMALS), + collateralRatio: collateralRatio.toFixed(USD_DECIMALS), + borrowPower: borrowPower.toFixed(USD_DECIMALS), + borrowPowerUsed: borrowPowerUsed.toFixed(USD_DECIMALS), + + borrowWeightedApy: borrowWeightedApy.toFixed(USD_DECIMALS), + supplyWeightedApy: supplyWeightedApy.toFixed(USD_DECIMALS), + + totalLiquidityUsd: supplyBalance.toFixed(USD_DECIMALS), + totalCollateralUsd: collateralBalance.toFixed(USD_DECIMALS), + totalBorrowsUsd: borrowBalance.toFixed(USD_DECIMALS), + availableBorrowsUsd: summary.availableBorrowsUSD, + + currentLoanToValue: summary.currentLoanToValue, + currentLiquidationThreshold: summary.currentLiquidationThreshold, + + supplyBalanceUsd: supplyBalance.toFixed(USD_DECIMALS), + collateralBalanceUsd: collateralBalance.toFixed(USD_DECIMALS), + + netWorthUsd: summary.netWorthUSD, + userEmodeCategoryId: summary.userEmodeCategoryId, + isInIsolationMode: summary.isInIsolationMode, + }, + }, }; }, ); diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx index 22e61c2..816c49d 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList.tsx @@ -7,16 +7,16 @@ import { PoolPositionStat } from '../PoolPositionStat/PoolPositionStat'; import { AssetsTable } from './components/AssetsTable/AssetsTable'; type BorrowPositionsListProps = { - supplyBalance: number; - supplyWeightedApy: number; - borrowPower: number; + borrowBalance: string; + borrowWeightedApy: string; + borrowPower: string; borrowPositions: MoneyMarketPoolPosition[]; loading?: boolean; }; export const BorrowPositionsList: FC = ({ - supplyBalance, - supplyWeightedApy, + borrowBalance, + borrowWeightedApy, borrowPower, borrowPositions, }) => { @@ -48,20 +48,37 @@ export const BorrowPositionsList: FC = ({ + } /> + } /> } + value={ + + } />
diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx index e80edc7..369cd19 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx @@ -11,6 +11,13 @@ import { Fragment, useCallback, useMemo, useState, type FC } from 'react'; import { AmountRenderer } from '@/components/ui/amount-renderer'; import { Button } from '@/components/ui/button'; import { InfoButton } from '@/components/ui/info-button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import type { MoneyMarketPoolPosition } from '@sovryn/slayer-sdk'; import { Decimal } from '@sovryn/slayer-shared'; import type { BorrowPosition } from '../../BorrowPositionsList.types'; @@ -21,30 +28,12 @@ type AssetsTableProps = { export const AssetsTable: FC = ({ assets }) => { const items = useMemo( - () => assets.filter((a) => Decimal.from(a.borrowedBalance).gt(0)), + () => assets.filter((a) => Decimal.from(a.borrowed).gt(0)), [assets], ); - // const [sortDirection, setSortDirection] = useState( - // OrderType.ASC, - // ); - // const [sortedAssets, setSortedAssets] = - // useState(assets); - const [selectedApy, setSelectedApy] = useState>({}); - // useEffect(() => { - // setSortedAssets(assets); - // setSelectedApy((prev) => { - // const next = { ...prev }; - // assets.forEach((a, i) => { - // const id = rowKey(a, i); - // if (next[id] == null) next[id] = inferDefaultSelected(a); - // }); - // return next; - // }); - // }, [assets]); - const parsePct = (v: unknown): number => { if (typeof v === 'number') return v; if (typeof v === 'string') { @@ -75,49 +64,6 @@ export const AssetsTable: FC = ({ assets }) => { [selectedApy, rowKey], ); - // const sortAssets = useCallback( - // (column: OrderColumn) => { - // const nextDir = - // sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; - // setSortDirection(nextDir); - - // const sorted = [...sortedAssets].sort((a, b) => { - // switch (column) { - // case OrderColumn.SYMBOL: { - // const cmp = a.symbol.localeCompare(b.symbol); - // return nextDir === OrderType.ASC ? cmp : -cmp; - // } - // case OrderColumn.BALANCE: { - // const av = parseFloat(String(a.balance).replace(/,/g, '')) || 0; - // const bv = parseFloat(String(b.balance).replace(/,/g, '')) || 0; - // return nextDir === OrderType.ASC ? av - bv : bv - av; - // } - // case OrderColumn.APY: - // case OrderColumn.APY_TYPE: { - // const ai = assets.indexOf(a); - // const bi = assets.indexOf(b); - // const av = currentApy(a, ai); - // const bv = currentApy(b, bi); - // return nextDir === OrderType.ASC ? av - bv : bv - av; - // } - // default: - // return 0; - // } - // }); - - // setSortedAssets(sorted); - // }, - // [sortDirection, sortedAssets, assets, currentApy], - // ); - - // const handleApyTypeChange = useCallback( - // (asset: BorrowPosition, idx: number, value: string) => { - // const id = rowKey(asset, idx); - // setSelectedApy((prev) => ({ ...prev, [id]: Number(value) })); - // setSortedAssets((prev) => [...prev]); - // }, - // [rowKey], - // ); return (
@@ -125,31 +71,11 @@ export const AssetsTable: FC = ({ assets }) => {
Asset - {/* {assets.some((a) => a.isSortable) && ( - - )} */}
Balance - {/* {assets.some((a) => a.isSortable) && ( - - )} */}
@@ -158,16 +84,6 @@ export const AssetsTable: FC = ({ assets }) => { APY - {/* {assets.some((a) => a.isSortable) && ( - - )} */} @@ -176,16 +92,6 @@ export const AssetsTable: FC = ({ assets }) => { APY type - {/* {assets.some((a) => a.isSortable) && ( - - )} */} @@ -194,12 +100,6 @@ export const AssetsTable: FC = ({ assets }) => { {items.map((asset, index) => { - // const selected = currentApy(asset, index); - // const types = (asset.apyType ?? []) - // .map(Number) - // .filter(Number.isFinite); - // const options = [selected, ...types.filter((t) => t !== selected)]; - return ( @@ -219,45 +119,58 @@ export const AssetsTable: FC = ({ assets }) => { - -

+

+ -

+
- {/* */} +
- {/*
+
-
*/} +
diff --git a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx index c7d136e..a75df59 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/LendPositionsList.tsx @@ -6,9 +6,9 @@ import { PoolPositionStat } from '../PoolPositionStat/PoolPositionStat'; import { AssetsTable } from './components/AssetsTable/AssetsTable'; type LendPositionsListProps = { - supplyBalance: number; - supplyWeightedApy: number; - collateralBalance: number; + supplyBalance: string; + supplyWeightedApy: string; + collateralBalance: string; lendPositions: MoneyMarketPoolPosition[]; loading?: boolean; }; diff --git a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx index 78c588d..f1ebd67 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendPositionsList/components/AssetsTable/AssetsTable.tsx @@ -21,49 +21,10 @@ type AssetsTableProps = { export const AssetsTable: FC = ({ assets }) => { const items = useMemo( - () => assets.filter((asset) => Decimal.from(asset.suppliedBalance).gt(0)), + () => assets.filter((asset) => Decimal.from(asset.supplied).gt(0)), [assets], ); - // const [sortDirection, setSortDirection] = useState( - // OrderType.ASC, - // ); - // const [sortedAssets, setSortedAssets] = - // useState(assets); - // useEffect(() => { - // setSortedAssets(assets); - // }, [assets]); - - // const sortAssets = useCallback( - // (column: keyof MoneyMarketPoolPosition) => { - // const newSortDirection = - // sortDirection === OrderType.ASC ? OrderType.DESC : OrderType.ASC; - // setSortDirection(newSortDirection); - - // const sorted = [...sortedAssets].sort((a, b) => { - // if (column === OrderColumn.SYMBOL) { - // return newSortDirection === OrderType.ASC - // ? a[column].localeCompare(b[column]) - // : b[column].localeCompare(a[column]); - // } else if (column === OrderColumn.BALANCE) { - // const balanceA = parseFloat(a.balance.replace(/,/g, '')); - // const balanceB = parseFloat(b.balance.replace(/,/g, '')); - // return newSortDirection === OrderType.ASC - // ? balanceA - balanceB - // : balanceB - balanceA; - // } else if (column === OrderColumn.APY) { - // const apyA = parseFloat(a.apy.replace('%', '')); - // const apyB = parseFloat(b.apy.replace('%', '')); - // return newSortDirection === OrderType.ASC ? apyA - apyB : apyB - apyA; - // } - // return 0; - // }); - - // setSortedAssets(sorted); - // }, - // [sortDirection], - // ); - const toggleCollateral = useCallback((symbol: string) => { // setSortedAssets((prevAssets) => // prevAssets.map((asset) => @@ -81,31 +42,11 @@ export const AssetsTable: FC = ({ assets }) => {
Asset - {/* {assets.some((asset) => asset.isSortable) && ( - - )} */}
Balance - {/* {assets.some((asset) => asset.isSortable) && ( - - )} */}
@@ -114,16 +55,6 @@ export const AssetsTable: FC = ({ assets }) => { APY - {/* {assets.some((asset) => asset.isSortable) && ( - - )} */} @@ -153,10 +84,13 @@ export const AssetsTable: FC = ({ assets }) => {
- +

@@ -164,16 +98,19 @@ export const AssetsTable: FC = ({ assets }) => {

-

- {asset.stableBorrowRate} -

+
toggleCollateral(asset.id)} // disabled={!asset} diff --git a/apps/web-app/src/routes/money-market.tsx b/apps/web-app/src/routes/money-market.tsx index 6ca6cf4..7c462e4 100644 --- a/apps/web-app/src/routes/money-market.tsx +++ b/apps/web-app/src/routes/money-market.tsx @@ -112,19 +112,25 @@ function RouteComponent() {
diff --git a/packages/sdk/src/managers/money-market/money-market.manager.ts b/packages/sdk/src/managers/money-market/money-market.manager.ts index f2028fd..9d4434a 100644 --- a/packages/sdk/src/managers/money-market/money-market.manager.ts +++ b/packages/sdk/src/managers/money-market/money-market.manager.ts @@ -11,6 +11,7 @@ import { MoneyMarketPool, MoneyMarketPoolPosition, MoneyMarketPoolReserve, + MoneyMarketUserSummary, SdkPaginatedResponse, TransactionOpts, } from '../../types.js'; @@ -132,13 +133,16 @@ export class MoneyMarketManager extends BaseClient { opts: SdkRequestOptions = {}, ) { const response = await this.ctx.http.request<{ - data: { userReserves: MoneyMarketPoolPosition[] }; + data: { + positions: MoneyMarketPoolPosition[]; + summary: MoneyMarketUserSummary; + }; }>(`/${this.ctx.chainId}/money-market/${pool}/user/${user}/positions`, { ...opts, query: buildQuery(opts.query), }); - return { ...response, data: response.data.userReserves }; + return response; } async borrow( diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index f0a7f64..a4b2579 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -78,18 +78,51 @@ export type MoneyMarketPoolPosition = { id: string; pool: MoneyMarketPool; token: SdkToken; - reserve: any; // todo - - suppliedBalance: string; - suppliedBalanceUsd: string; - borrowedBalance: string; - borrowedBalanceUsd: string; - - underlyingAsset: string; - scaledATokenBalance: string; - usageAsCollateralEnabledOnUser: boolean; - stableBorrowRate: string; - scaledVariableDebt: string; - principalStableDebt: string; - stableBorrowLastUpdateTimestamp: number; + reserve: Omit; + + supplied: string; + suppliedUsd: string; + + supplyApy: string; + canToggleCollateral: boolean; + + borrowed: string; + borrowedUsd: string; + + borrowApy: string; + stableApy: string; + variableApy: string; + + collateral: boolean; + + availableToBorrow: string; + availableToBorrowUsd: string; + + borrowRateMode: BorrowRateMode; +}; + +export type MoneyMarketUserSummary = { + netApy: string; + healthFactor: string; + collateralRatio: string; + borrowPower: string; + borrowPowerUsed: string; + + totalLiquidityUsd: string; + totalCollateralUsd: string; + totalBorrowsUsd: string; + availableBorrowsUsd: string; + + currentLoanToValue: string; + currentLiquidationThreshold: string; + + supplyBalanceUsd: string; + collateralBalanceUsd: string; + + netWorthUsd: string; + userEmodeCategoryId: number | null; + isInIsolationMode: boolean; + + borrowWeightedApy: string; + supplyWeightedApy: string; }; From bd49c9d35c489583ae4cc64dc01ee1b98fd0b65f Mon Sep 17 00:00:00 2001 From: Rytis Grincevicius Date: Mon, 8 Dec 2025 15:30:25 +0200 Subject: [PATCH 5/5] fix: borrow APY data --- apps/indexer/package.json | 3 +- apps/indexer/src/app/routes/_chain/routes.ts | 172 ++---------------- .../MoneyMarket/MoneyMarket.constants.ts | 3 - .../components/AssetsTable/AssetsTable.tsx | 2 +- .../components/AssetsTable/AssetsTable.tsx | 8 +- .../components/AssetsTable/AssetsTable.tsx | 2 +- .../components/TopPanel/TopPanel.tsx | 27 ++- apps/web-app/src/routes/money-market.tsx | 14 +- packages/sdk/package.json | 4 +- packages/sdk/src/types.ts | 7 +- pnpm-lock.yaml | 27 +-- 11 files changed, 72 insertions(+), 197 deletions(-) delete mode 100644 apps/web-app/src/components/MoneyMarket/MoneyMarket.constants.ts diff --git a/apps/indexer/package.json b/apps/indexer/package.json index 4ba885c..8cd23b6 100644 --- a/apps/indexer/package.json +++ b/apps/indexer/package.json @@ -138,7 +138,8 @@ } }, "dependencies": { - "@aave/math-utils": "^1.29.1", + "@aave/math-utils": "1.29.1", + "@aave/contract-helpers": "1.29.1", "@bull-board/api": "6.13.0", "@bull-board/fastify": "6.13.0", "@fastify/autoload": "6.0.3", diff --git a/apps/indexer/src/app/routes/_chain/routes.ts b/apps/indexer/src/app/routes/_chain/routes.ts index 3931311..aa8d2c9 100644 --- a/apps/indexer/src/app/routes/_chain/routes.ts +++ b/apps/indexer/src/app/routes/_chain/routes.ts @@ -123,86 +123,6 @@ export default async function (fastify: ZodFastifyInstance) { baseCurrencyData.marketReferenceCurrencyDecimals, }); - // const reservesData: Partial[] = reservesRaw.map( - // (reserveRaw, index) => { - // // const virtualUnderlyingBalance = - // // reserveRaw.virtualUnderlyingBalance.toString(); - // // const { virtualAccActive } = reserveRaw; - - // // const { totalDebt, totalVariableDebt, totalLiquidity } = - // // calculateReserveDebt(reserveRaw, currentTimestamp); - - // // formatRe - - // return { - // originalId: index, - // id: `${req.chain.chainId}-${reserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), - // // underlyingAsset: reserveRaw.underlyingAsset.toLowerCase(), - - // token: tokens.find((t) => - // areAddressesEqual(t.address, reserveRaw.underlyingAsset), - // ), - // pool, - - // // name: reserveRaw.name, - // // symbol: ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] - // // ? ammSymbolMap[reserveRaw.underlyingAsset.toLowerCase()] - // // : reserveRaw.symbol, - // // decimals: reserveRaw.decimals.toNumber(), - // baseLTVasCollateral: reserveRaw.baseLTVasCollateral.toString(), - // reserveLiquidationThreshold: - // reserveRaw.reserveLiquidationThreshold.toString(), - // reserveLiquidationBonus: - // reserveRaw.reserveLiquidationBonus.toString(), - // reserveFactor: reserveRaw.reserveFactor.toString(), - // usageAsCollateralEnabled: reserveRaw.usageAsCollateralEnabled, - // borrowingEnabled: reserveRaw.borrowingEnabled, - // isActive: reserveRaw.isActive, - // isFrozen: reserveRaw.isFrozen, - // liquidityIndex: reserveRaw.liquidityIndex.toString(), - // variableBorrowIndex: reserveRaw.variableBorrowIndex.toString(), - // liquidityRate: reserveRaw.liquidityRate.toString(), - // variableBorrowRate: reserveRaw.variableBorrowRate.toString(), - // lastUpdateTimestamp: reserveRaw.lastUpdateTimestamp, - // aTokenAddress: reserveRaw.aTokenAddress.toString(), - // variableDebtTokenAddress: - // reserveRaw.variableDebtTokenAddress.toString(), - // interestRateStrategyAddress: - // reserveRaw.interestRateStrategyAddress.toString(), - // availableLiquidity: Decimal.from( - // reserveRaw.availableLiquidity, - // reserveRaw.decimals.toNumber(), - // ).toString(), - // // availableLiquidity: reserveRaw.availableLiquidity.toString(), - // totalScaledVariableDebt: - // reserveRaw.totalScaledVariableDebt.toString(), - // priceInMarketReferenceCurrency: - // reserveRaw.priceInMarketReferenceCurrency.toString(), - // // priceOracle: reserveRaw.priceOracle, - // variableRateSlope1: reserveRaw.variableRateSlope1.toString(), - // variableRateSlope2: reserveRaw.variableRateSlope2.toString(), - // // baseVariableBorrowRate: - // // reserveRaw.baseVariableBorrowRate.toString(), - // // optimalUsageRatio: reserveRaw.optimalUsageRatio.toString(), - // // new fields - // // isPaused: reserveRaw.isPaused, - // // debtCeiling: reserveRaw.debtCeiling.toString(), - // // borrowCap: reserveRaw.borrowCap.toString(), - // // supplyCap: reserveRaw.supplyCap.toString(), - // // borrowableInIsolation: reserveRaw.borrowableInIsolation, - // // accruedToTreasury: reserveRaw.accruedToTreasury.toString(), - // // unbacked: reserveRaw.unbacked.toString(), - // // isolationModeTotalDebt: - // // reserveRaw.isolationModeTotalDebt.toString(), - // // debtCeilingDecimals: reserveRaw.debtCeilingDecimals.toNumber(), - // // isSiloedBorrowing: reserveRaw.isSiloedBorrowing, - // // flashLoanEnabled: reserveRaw.flashLoanEnabled, - // // virtualAccActive, - // // virtualUnderlyingBalance, - // }; - // }, - // ); - const items = data.map((item) => { const token = tokens.find((t) => areAddressesEqual(t.address, item.underlyingAsset), @@ -217,7 +137,10 @@ export default async function (fastify: ZodFastifyInstance) { liquidityUsd: Decimal.from(item.totalLiquidity) .mul(Decimal.from(item.priceInUSD)) .toFixed(USD_DECIMALS), - borrowApy: Decimal.from(item.variableBorrowAPY) + stableBorrowApy: Decimal.from(item.stableBorrowAPY ?? 0) + .mul(100) + .toFixed(USD_DECIMALS), + variableBorrowApy: Decimal.from(item.variableBorrowAPY ?? 0) .mul(100) .toFixed(USD_DECIMALS), canBeBorrowed: item.borrowingEnabled, @@ -227,7 +150,8 @@ export default async function (fastify: ZodFastifyInstance) { canBeCollateral: item.usageAsCollateralEnabled, isActive: item.isActive, isFroze: item.isFrozen, - eModes: item.eModes, + // eModes: item.eModes, + i: item, }; }); @@ -315,70 +239,6 @@ export default async function (fastify: ZodFastifyInstance) { }), }); - // const userReserves = userReservesRaw - // .filter( - // (item) => - // item.scaledATokenBalance > 0n || item.scaledVariableDebt > 0n, - // ) - // .map((userReserveRaw) => { - // const token = tokens.find((t) => - // areAddressesEqual(t.address, userReserveRaw.underlyingAsset), - // ); - // const reserve = reserves.find((r) => - // areAddressesEqual( - // r.underlyingAsset, - // userReserveRaw.underlyingAsset, - // ), - // ); - - // const usdPrice = Decimal.from( - // reserve?.priceInMarketReferenceCurrency.toString() || '0', - // ).div(Decimal.pow(baseCurrencyData.marketReferenceCurrencyDecimals)); - - // const suppliedBalance = Decimal.from( - // userReserveRaw.scaledATokenBalance, - // token?.decimals ?? 18, - // ); - - // const borrowedBalance = Decimal.from( - // userReserveRaw.scaledVariableDebt, - // token?.decimals ?? 18, - // ); - - // return { - // id: `${req.chain.chainId}-${req.params.address}-${userReserveRaw.underlyingAsset}-${pool.address}`.toLowerCase(), - // // pool, - // // token, - // // reserve, - - // usdPrice: usdPrice.toString(), - // priceInMarketReferenceCurrency: - // reserve?.priceInMarketReferenceCurrency, - // decimalsD: baseCurrencyData.marketReferenceCurrencyDecimals, - // raw: baseCurrencyData, - - // suppliedBalance: suppliedBalance.toString(), - // suppliedBalanceUsd: suppliedBalance - // .mul(usdPrice) - // .toFixed(USD_DECIMALS), - - // borrowedBalance: borrowedBalance.toString(), - // borrowedBalanceUsd: borrowedBalance - // .mul(usdPrice) - // .toFixed(USD_DECIMALS), - - // underlyingAsset: userReserveRaw.underlyingAsset.toLowerCase(), - // scaledATokenBalance: userReserveRaw.scaledATokenBalance.toString(), - // usageAsCollateralEnabledOnUser: - // userReserveRaw.usageAsCollateralEnabledOnUser, - // stableBorrowRate: userReserveRaw.stableBorrowRate.toString(), - // scaledVariableDebt: userReserveRaw.scaledVariableDebt.toString(), - // principalStableDebt: userReserveRaw.principalStableDebt.toString(), - // stableBorrowLastUpdateTimestamp: - // userReserveRaw.stableBorrowLastUpdateTimestamp.toNumber(), - // }; - // }); - const netWorth = Decimal.from(summary.netWorthUSD); const borrowBalance = Decimal.from(summary.totalBorrowsUSD); const supplyBalance = summary.userReservesData.reduce( @@ -508,17 +368,20 @@ export default async function (fastify: ZodFastifyInstance) { liquidityUsd: Decimal.from(item.reserve.totalLiquidity) .mul(Decimal.from(item.reserve.priceInUSD)) .toFixed(USD_DECIMALS), - borrowApy: Decimal.from(item.reserve.variableBorrowAPY) - .mul(100) - .toFixed(USD_DECIMALS), canBeBorrowed: item.reserve.borrowingEnabled, supplyApy: Decimal.from(item.reserve.supplyAPY) .mul(100) .toFixed(USD_DECIMALS), + stableBorrowApy: Decimal.from(item.reserve.stableBorrowAPY) + .mul(100) + .toFixed(USD_DECIMALS), + variableBorrowApy: Decimal.from(item.reserve.variableBorrowAPY) + .mul(100) + .toFixed(USD_DECIMALS), canBeCollateral: item.reserve.usageAsCollateralEnabled, isActive: item.reserve.isActive, isFroze: item.reserve.isFrozen, - eModes: item.reserve.eModes, + // eModes: item.reserve.eModes, }, supplied: item.underlyingBalance, suppliedUsd: item.underlyingBalanceUSD, @@ -537,17 +400,15 @@ export default async function (fastify: ZodFastifyInstance) { borrowRateMode, borrowApy: Decimal.from( borrowRateMode === 1 - ? // @ts-expect-error stableBorrowAPY exists - (item.reserve.stableBorrowAPY ?? 0) + ? item.reserve.stableBorrowAPY : item.reserve.variableBorrowAPY, ) .mul(100) .toString(), - // @ts-expect-error stableBorrowAPY exists - stableApy: Decimal.from(item.reserve.stableBorrowAPY ?? 0) + stableBorrowApy: Decimal.from(item.reserve.stableBorrowAPY ?? 0) .mul(100) .toString(), - variableApy: Decimal.from(item.reserve.variableBorrowAPY ?? 0) + variableBorrowApy: Decimal.from(item.reserve.variableBorrowAPY ?? 0) .mul(100) .toString(), }; @@ -581,6 +442,7 @@ export default async function (fastify: ZodFastifyInstance) { userEmodeCategoryId: summary.userEmodeCategoryId, isInIsolationMode: summary.isInIsolationMode, }, + reservesData, }, }; }, diff --git a/apps/web-app/src/components/MoneyMarket/MoneyMarket.constants.ts b/apps/web-app/src/components/MoneyMarket/MoneyMarket.constants.ts deleted file mode 100644 index 8d80b13..0000000 --- a/apps/web-app/src/components/MoneyMarket/MoneyMarket.constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const healthFactor = 1.25; -export const netApy = 12.34; -export const netWorth = 5678.9; diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx index ea08c39..26fdb56 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowAssetsList/components/AssetsTable/AssetsTable.tsx @@ -85,7 +85,7 @@ export const AssetsTable: FC = ({ assets }) => { diff --git a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx index 369cd19..4b11f9e 100644 --- a/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/BorrowPositionsList/components/AssetsTable/AssetsTable.tsx @@ -152,18 +152,18 @@ export const AssetsTable: FC = ({ assets }) => { - + APY, variable{' '} - + APY, stable{' '} diff --git a/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx b/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx index c648645..63b0f51 100644 --- a/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/LendAssetsList/components/AssetsTable/AssetsTable.tsx @@ -87,7 +87,7 @@ export const AssetsTable: FC = ({ assets }) => {
- +
diff --git a/apps/web-app/src/components/MoneyMarket/components/TopPanel/TopPanel.tsx b/apps/web-app/src/components/MoneyMarket/components/TopPanel/TopPanel.tsx index a11de77..a9db8ad 100644 --- a/apps/web-app/src/components/MoneyMarket/components/TopPanel/TopPanel.tsx +++ b/apps/web-app/src/components/MoneyMarket/components/TopPanel/TopPanel.tsx @@ -1,17 +1,20 @@ +import { Skeleton } from '@/components/ui/skeleton'; import type { FC } from 'react'; import { AmountRenderer } from '../../../ui/amount-renderer'; import { StatisticsCard } from '../StatisticsCard/StatisticsCard'; type TopPanelProps = { - netWorth: number; - netApy: number; - healthFactor: number; + netWorth: string; + netApy: string; + healthFactor: string; + isPending?: boolean; }; export const TopPanel: FC = ({ netApy, netWorth, healthFactor, + isPending, }) => (
@@ -20,7 +23,11 @@ export const TopPanel: FC = ({ label="Net Worth" value={ - + {isPending ? ( + + ) : ( + + )} } /> @@ -29,7 +36,11 @@ export const TopPanel: FC = ({ label="Net APY" value={ - + {isPending ? ( + + ) : ( + + )} } help="Net APY is the combined effect of all supply and borrow positions on net worth, including incentives. It is possible to have a negative net APY if debt APY is higher than supply APY." @@ -38,7 +49,11 @@ export const TopPanel: FC = ({ label="Health Factor" value={ - + {isPending ? ( + + ) : ( + + )} } /> diff --git a/apps/web-app/src/routes/money-market.tsx b/apps/web-app/src/routes/money-market.tsx index 7c462e4..64f3454 100644 --- a/apps/web-app/src/routes/money-market.tsx +++ b/apps/web-app/src/routes/money-market.tsx @@ -8,11 +8,6 @@ import { BorrowDialog } from '@/components/MoneyMarket/components/BorrowDialog/B import { BorrowPositionsList } from '@/components/MoneyMarket/components/BorrowPositionsList/BorrowPositionsList'; import { LendAssetsList } from '@/components/MoneyMarket/components/LendAssetsList/LendAssetsList'; import { LendDialog } from '@/components/MoneyMarket/components/LendDialog/LendDialog'; -import { - healthFactor, - netApy, - netWorth, -} from '@/components/MoneyMarket/MoneyMarket.constants'; import { Heading } from '@/components/ui/heading/heading'; import { sdk } from '@/lib/sdk'; import { useQuery } from '@tanstack/react-query'; @@ -80,7 +75,7 @@ function RouteComponent() { staleTime: STALE_TIME, }); - const { data: positions } = useQuery({ + const { data: positions, isPending } = useQuery({ queryKey: ['money-market:positions', pool || 'default', address], queryFn: () => sdk.moneyMarket.listUserPositions(pool || 'default', address!), @@ -104,9 +99,10 @@ function RouteComponent() {
diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 0dda667..e78fa1b 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -16,8 +16,8 @@ } }, "dependencies": { - "@aave/contract-helpers": "^1.36.2", - "@aave/math-utils": "^1.36.2", + "@aave/contract-helpers": "1.29.1", + "@aave/math-utils": "1.29.1", "@sovryn/slayer-shared": "workspace:*", "debug": "4.4.3", "mobx": "^6.15.0", diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index a4b2579..4b87fd2 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -41,7 +41,8 @@ export interface MoneyMarketPoolReserve { liquidity: string; liquidityUsd: string; - borrowApy: string; + stableBorrowApy: string; + variableBorrowApy: string; supplyApy: string; canBeBorrowed: boolean; @@ -90,8 +91,8 @@ export type MoneyMarketPoolPosition = { borrowedUsd: string; borrowApy: string; - stableApy: string; - variableApy: string; + stableBorrowApy: string; + variableBorrowApy: string; collateral: boolean; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9931e7..46cf34d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,9 +152,12 @@ importers: apps/indexer: dependencies: + '@aave/contract-helpers': + specifier: 1.29.1 + version: 1.29.1(bignumber.js@9.3.1)(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(reflect-metadata@0.1.14)(tslib@2.8.1) '@aave/math-utils': - specifier: ^1.29.1 - version: 1.36.2(bignumber.js@9.3.1)(tslib@2.8.1) + specifier: 1.29.1 + version: 1.29.1(bignumber.js@9.3.1)(tslib@2.8.1) '@bull-board/api': specifier: 6.13.0 version: 6.13.0(@bull-board/ui@6.13.0) @@ -413,11 +416,11 @@ importers: packages/sdk: dependencies: '@aave/contract-helpers': - specifier: ^1.36.2 - version: 1.36.2(bignumber.js@9.3.1)(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(reflect-metadata@0.1.14)(tslib@2.8.1) + specifier: 1.29.1 + version: 1.29.1(bignumber.js@9.3.1)(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(reflect-metadata@0.1.14)(tslib@2.8.1) '@aave/math-utils': - specifier: ^1.36.2 - version: 1.36.2(bignumber.js@9.3.1)(tslib@2.8.1) + specifier: 1.29.1 + version: 1.29.1(bignumber.js@9.3.1)(tslib@2.8.1) '@sovryn/slayer-shared': specifier: workspace:* version: link:../shared @@ -449,16 +452,16 @@ importers: packages: - '@aave/contract-helpers@1.36.2': - resolution: {integrity: sha512-g0z9QbppGHDXSu13OPmyMkHAw1UjCAD2xoUWO+8Vk1xT9ZEwZaT7Pn2sojwUp97FyQiiKbjmJ4KupNS4jp33GQ==} + '@aave/contract-helpers@1.29.1': + resolution: {integrity: sha512-34z5CKpNdEx26G+DSezovdR3PyAf0v0Typbf2udaKG4GrOBgqbKqxr4zqS/dpFO5aAQJJ+nuEM19lKRK4sMcpg==} peerDependencies: bignumber.js: ^9.x ethers: ^5.x reflect-metadata: ^0.1.x tslib: ^2.4.x - '@aave/math-utils@1.36.2': - resolution: {integrity: sha512-r2SnPyK7MdhlfwTTj9Wq1idRhS1dyErJKeP98MexohRhzhWzsB6Uh3/gD6iK5mZc28V1mxhnUPTi1kD+mO6+/Q==} + '@aave/math-utils@1.29.1': + resolution: {integrity: sha512-a+L2+vjza/nH4BxUTzwDcoJH/Qr6UP+g+9YSwG7YKUgoVfdy8gJs5VyXjfDDEVe9lMeZuPJq7CqrgV4P2M6Nkg==} peerDependencies: bignumber.js: ^9.x tslib: ^2.4.x @@ -11150,7 +11153,7 @@ packages: snapshots: - '@aave/contract-helpers@1.36.2(bignumber.js@9.3.1)(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(reflect-metadata@0.1.14)(tslib@2.8.1)': + '@aave/contract-helpers@1.29.1(bignumber.js@9.3.1)(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(reflect-metadata@0.1.14)(tslib@2.8.1)': dependencies: bignumber.js: 9.3.1 ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -11160,7 +11163,7 @@ snapshots: transitivePeerDependencies: - encoding - '@aave/math-utils@1.36.2(bignumber.js@9.3.1)(tslib@2.8.1)': + '@aave/math-utils@1.29.1(bignumber.js@9.3.1)(tslib@2.8.1)': dependencies: bignumber.js: 9.3.1 tslib: 2.8.1