From db1b73e5ecdee6af60ad32d545d2a56944fc410f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Von=C3=A1=C5=A1ek?= Date: Wed, 15 Jan 2025 15:01:10 +0100 Subject: [PATCH 1/2] Money Market user data on wallet page --- src/api/borrow.ts | 74 +++++++++++++++++++ src/api/provider.ts | 4 + src/providers/rpcProvider.tsx | 5 ++ .../wallet/assets/WalletAssets.utils.ts | 9 ++- .../assets/header/WalletAssetsHeader.tsx | 32 ++++++-- src/utils/provider.ts | 28 +++++++ src/utils/queryKeys.ts | 1 + 7 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 src/api/borrow.ts create mode 100644 src/utils/provider.ts diff --git a/src/api/borrow.ts b/src/api/borrow.ts new file mode 100644 index 000000000..11afe753d --- /dev/null +++ b/src/api/borrow.ts @@ -0,0 +1,74 @@ +import { UiPoolDataProvider } from "@aave/contract-helpers" +import { formatReserves, formatUserSummary } from "@aave/math-utils" +import { useQuery } from "@tanstack/react-query" +import { isTestnetRpcUrl } from "api/provider" +import { useRpcProvider } from "providers/rpcProvider" +import { + AaveV3HydrationMainnet, + AaveV3HydrationTestnet, +} from "sections/lending/ui-config/addresses" +import { useEvmAccount } from "sections/web3-connect/Web3Connect.utils" +import { QUERY_KEYS } from "utils/queryKeys" + +export const useUserBorrowSummary = () => { + const { api, evm, isLoaded } = useRpcProvider() + const { account } = useEvmAccount() + + const address = account?.address ?? "" + + return useQuery( + QUERY_KEYS.borrowUserSummary(address), + async () => { + const isTestnet = isTestnetRpcUrl(evm.connection.url) + + const addresses = isTestnet + ? AaveV3HydrationTestnet + : AaveV3HydrationMainnet + + const poolDataContract = new UiPoolDataProvider({ + uiPoolDataProviderAddress: addresses.UI_POOL_DATA_PROVIDER, + provider: evm, + chainId: parseFloat(import.meta.env.VITE_EVM_CHAIN_ID), + }) + + const [reserves, user, timestamp] = await Promise.all([ + poolDataContract.getReservesHumanized({ + lendingPoolAddressProvider: addresses.POOL_ADDRESSES_PROVIDER, + }), + poolDataContract.getUserReservesHumanized({ + lendingPoolAddressProvider: addresses.POOL_ADDRESSES_PROVIDER, + user: address, + }), + api.query.timestamp.now(), + ]) + + const { baseCurrencyData, reservesData } = reserves + const { userEmodeCategoryId, userReserves } = user + + const currentTimestamp = timestamp.toNumber() / 1000 + + const formattedReserves = formatReserves({ + currentTimestamp, + reserves: reservesData, + marketReferencePriceInUsd: + baseCurrencyData.marketReferenceCurrencyPriceInUsd, + marketReferenceCurrencyDecimals: + baseCurrencyData.marketReferenceCurrencyDecimals, + }) + + return formatUserSummary({ + currentTimestamp, + marketReferencePriceInUsd: + baseCurrencyData.marketReferenceCurrencyPriceInUsd, + marketReferenceCurrencyDecimals: + baseCurrencyData.marketReferenceCurrencyDecimals, + userReserves, + formattedReserves, + userEmodeCategoryId, + }) + }, + { + enabled: isLoaded && !!address, + }, + ) +} diff --git a/src/api/provider.ts b/src/api/provider.ts index a3596b215..3d314903e 100644 --- a/src/api/provider.ts +++ b/src/api/provider.ts @@ -21,6 +21,7 @@ import { identity, undefinedNoop } from "utils/helpers" import { ExternalAssetCursor } from "@galacticcouncil/apps" import { getExternalId } from "utils/externalAssets" import { pingRpc } from "utils/rpc" +import { PolkadotEvmRpcProvider } from "utils/provider" export type TEnv = "testnet" | "mainnet" export type ProviderProps = { @@ -273,8 +274,11 @@ export const useProviderData = () => { const balanceClient = new BalanceClient(api) + const evm = new PolkadotEvmRpcProvider(api) + return { api, + evm, tradeRouter, poolService, balanceClient, diff --git a/src/providers/rpcProvider.tsx b/src/providers/rpcProvider.tsx index 049e9f5f1..04029012a 100644 --- a/src/providers/rpcProvider.tsx +++ b/src/providers/rpcProvider.tsx @@ -18,9 +18,11 @@ import { useDisplayAssetStore } from "utils/displayAsset" import { useShareTokens } from "api/xyk" import { AssetsProvider } from "./assets" import { differenceInHours } from "date-fns" +import { PolkadotEvmRpcProvider } from "utils/provider" type TProviderContext = { api: ApiPromise + evm: PolkadotEvmRpcProvider tradeRouter: TradeRouter poolService: PoolService balanceClient: BalanceClient @@ -30,6 +32,7 @@ type TProviderContext = { const ProviderContext = createContext({ isLoaded: false, api: {} as TProviderContext["api"], + evm: {} as TProviderContext["evm"], tradeRouter: {} as TradeRouter, featureFlags: {} as TProviderContext["featureFlags"], poolService: {} as TProviderContext["poolService"], @@ -103,6 +106,7 @@ export const RpcProvider = ({ children }: { children: ReactNode }) => { return { poolService: providerData.poolService, api: providerData.api, + evm: providerData.evm, tradeRouter: providerData.tradeRouter, balanceClient: providerData.balanceClient, featureFlags: providerData.featureFlags, @@ -113,6 +117,7 @@ export const RpcProvider = ({ children }: { children: ReactNode }) => { return { isLoaded: false, api: {} as TProviderContext["api"], + evm: {} as TProviderContext["evm"], tradeRouter: {} as TradeRouter, balanceClient: {} as BalanceClient, featureFlags: { diff --git a/src/sections/wallet/assets/WalletAssets.utils.ts b/src/sections/wallet/assets/WalletAssets.utils.ts index 8ce9807f4..82798e05e 100644 --- a/src/sections/wallet/assets/WalletAssets.utils.ts +++ b/src/sections/wallet/assets/WalletAssets.utils.ts @@ -8,6 +8,7 @@ import { useDisplayShareTokenPrice } from "utils/displayAsset" import { useAssetsData } from "./table/data/WalletAssetsTableData.utils" import { useAccountAssets } from "api/deposits" import BigNumber from "bignumber.js" +import { useUserBorrowSummary } from "api/borrow" type AssetCategory = "all" | "assets" | "liquidity" | "farming" @@ -46,6 +47,7 @@ export const useWalletAssetsTotals = ({ }: { address?: string } = {}) => { + const borrows = useUserBorrowSummary() const assets = useAssetsData({ isAllAssets: false, address }) const lpPositions = useOmnipoolPositionsData({ address }) const farmsTotal = useFarmDepositsTotal(address) @@ -103,17 +105,21 @@ export const useWalletAssetsTotals = ({ }, "0") }, [shareTokenBalances, spotPrices.data]) + const borrowsTotal = borrows.data?.totalBorrowsUSD ?? "0" + const balanceTotal = useMemo( () => BigNumber(assetsTotal) .plus(farmsTotal.value) .plus(lpTotal) .plus(xykTotal) + .plus(borrowsTotal) .toString(), - [assetsTotal, farmsTotal.value, lpTotal, xykTotal], + [assetsTotal, farmsTotal.value, lpTotal, xykTotal, borrowsTotal], ) const isLoading = + borrows.isLoading || assets.isLoading || lpPositions.isLoading || farmsTotal.isLoading || @@ -125,6 +131,7 @@ export const useWalletAssetsTotals = ({ farmsTotal: farmsTotal.value, lpTotal: BigNumber(lpTotal).plus(xykTotal).toString(), balanceTotal, + borrowsTotal: borrows.data?.totalBorrowsUSD ?? "0", isLoading, } } diff --git a/src/sections/wallet/assets/header/WalletAssetsHeader.tsx b/src/sections/wallet/assets/header/WalletAssetsHeader.tsx index 05a195bbb..7e9b9f72d 100644 --- a/src/sections/wallet/assets/header/WalletAssetsHeader.tsx +++ b/src/sections/wallet/assets/header/WalletAssetsHeader.tsx @@ -8,11 +8,18 @@ type Props = { disconnected?: boolean } export const WalletAssetsHeader = ({ disconnected }: Props) => { const { t } = useTranslation() - const { isLoading, balanceTotal, assetsTotal, farmsTotal, lpTotal } = - useWalletAssetsTotals() + const { + isLoading, + balanceTotal, + assetsTotal, + farmsTotal, + borrowsTotal, + lpTotal, + } = useWalletAssetsTotals() + return ( { content: ( ), @@ -32,7 +39,7 @@ export const WalletAssetsHeader = ({ disconnected }: Props) => { content: ( ), @@ -43,7 +50,7 @@ export const WalletAssetsHeader = ({ disconnected }: Props) => { content: ( ), @@ -54,11 +61,22 @@ export const WalletAssetsHeader = ({ disconnected }: Props) => { content: ( ), }, + { + label: "Borrows", + disconnected: disconnected, + content: ( + + ), + }, ]} /> ) diff --git a/src/utils/provider.ts b/src/utils/provider.ts new file mode 100644 index 000000000..09d39cbb2 --- /dev/null +++ b/src/utils/provider.ts @@ -0,0 +1,28 @@ +import { getNetwork, JsonRpcProvider, Network } from "@ethersproject/providers" +import { ApiPromise, WsProvider } from "@polkadot/api" + +export class PolkadotEvmRpcProvider extends JsonRpcProvider { + provider: WsProvider + + constructor(api: ApiPromise) { + const provider = PolkadotEvmRpcProvider.getProviderInstance(api) + const path = provider.endpoint + super(path) + this.provider = provider + } + + async _uncachedDetectNetwork(): Promise { + const chainId = await this.send("eth_chainId", []) + return getNetwork(parseInt(chainId, 16)) + } + + static getProviderInstance(api: ApiPromise) { + // @ts-expect-error Property '_options' is protected + const options = api?._options + return options?.provider as WsProvider + } + + send(method: string, params: Array = []): Promise { + return this.provider.send(method, params) + } +} diff --git a/src/utils/queryKeys.ts b/src/utils/queryKeys.ts index 543292c84..35729c7ce 100644 --- a/src/utils/queryKeys.ts +++ b/src/utils/queryKeys.ts @@ -331,4 +331,5 @@ export const QUERY_KEYS = { ) => ["xcmTransfer", asset, srcAddr, srcChain, dstAddr, dstChain], externalApi: (chain: string) => ["externalApi", chain], bifrostVDotApy: ["bifrostVDotApy"], + borrowUserSummary: (address: string) => ["borrowUserSummary", address], } as const From f8428e04baa46152dd1b53e41280761eedc75ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Von=C3=A1=C5=A1ek?= Date: Wed, 15 Jan 2025 16:06:30 +0100 Subject: [PATCH 2/2] add optional address param to user borrow info --- src/api/borrow.ts | 33 ++++++++++++------- src/api/external/bifrost.ts | 23 +++++++++---- .../ui/header/DashboardHeaderValues.tsx | 4 ++- .../wallet/assets/WalletAssets.utils.ts | 4 ++- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/api/borrow.ts b/src/api/borrow.ts index 11afe753d..92543a6b3 100644 --- a/src/api/borrow.ts +++ b/src/api/borrow.ts @@ -3,41 +3,51 @@ import { formatReserves, formatUserSummary } from "@aave/math-utils" import { useQuery } from "@tanstack/react-query" import { isTestnetRpcUrl } from "api/provider" import { useRpcProvider } from "providers/rpcProvider" +import { useMemo } from "react" import { AaveV3HydrationMainnet, AaveV3HydrationTestnet, } from "sections/lending/ui-config/addresses" -import { useEvmAccount } from "sections/web3-connect/Web3Connect.utils" +import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import { H160, isEvmAccount } from "utils/evm" import { QUERY_KEYS } from "utils/queryKeys" -export const useUserBorrowSummary = () => { +export const useUserBorrowSummary = (givenAddress?: string) => { + const { account } = useAccount() const { api, evm, isLoaded } = useRpcProvider() - const { account } = useEvmAccount() - const address = account?.address ?? "" + const address = givenAddress || account?.address + + const isEvm = isEvmAccount(address) + + const evmAddress = useMemo(() => { + if (!address) return "" + if (isEvm) return H160.fromAccount(address) + return H160.fromSS58(address) + }, [isEvm, address]) return useQuery( - QUERY_KEYS.borrowUserSummary(address), + QUERY_KEYS.borrowUserSummary(evmAddress), async () => { const isTestnet = isTestnetRpcUrl(evm.connection.url) - const addresses = isTestnet + const contracts = isTestnet ? AaveV3HydrationTestnet : AaveV3HydrationMainnet const poolDataContract = new UiPoolDataProvider({ - uiPoolDataProviderAddress: addresses.UI_POOL_DATA_PROVIDER, + uiPoolDataProviderAddress: contracts.UI_POOL_DATA_PROVIDER, provider: evm, chainId: parseFloat(import.meta.env.VITE_EVM_CHAIN_ID), }) const [reserves, user, timestamp] = await Promise.all([ poolDataContract.getReservesHumanized({ - lendingPoolAddressProvider: addresses.POOL_ADDRESSES_PROVIDER, + lendingPoolAddressProvider: contracts.POOL_ADDRESSES_PROVIDER, }), poolDataContract.getUserReservesHumanized({ - lendingPoolAddressProvider: addresses.POOL_ADDRESSES_PROVIDER, - user: address, + lendingPoolAddressProvider: contracts.POOL_ADDRESSES_PROVIDER, + user: evmAddress, }), api.query.timestamp.now(), ]) @@ -68,7 +78,8 @@ export const useUserBorrowSummary = () => { }) }, { - enabled: isLoaded && !!address, + retry: false, + enabled: isLoaded && !!evmAddress, }, ) } diff --git a/src/api/external/bifrost.ts b/src/api/external/bifrost.ts index eee0d263e..33a1e38f1 100644 --- a/src/api/external/bifrost.ts +++ b/src/api/external/bifrost.ts @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query" +import { useQuery, UseQueryOptions } from "@tanstack/react-query" import { QUERY_KEYS } from "utils/queryKeys" type BifrostAPY = { @@ -7,10 +7,19 @@ type BifrostAPY = { apyReward: string } -export const useBifrostVDotApy = () => { - return useQuery(QUERY_KEYS.bifrostVDotApy, async () => { - const res = await fetch("https://dapi.bifrost.io/api/site") - const data = await res.json() - return data["vDOT"] as BifrostAPY - }) +export const useBifrostVDotApy = ( + options: UseQueryOptions = {}, +) => { + return useQuery( + QUERY_KEYS.bifrostVDotApy, + async () => { + const res = await fetch("https://dapi.bifrost.io/api/site") + const data = await res.json() + return data["vDOT"] as BifrostAPY + }, + { + refetchOnWindowFocus: false, + ...options, + }, + ) } diff --git a/src/sections/lending/ui/header/DashboardHeaderValues.tsx b/src/sections/lending/ui/header/DashboardHeaderValues.tsx index c17f2e093..e3bc6e2bc 100644 --- a/src/sections/lending/ui/header/DashboardHeaderValues.tsx +++ b/src/sections/lending/ui/header/DashboardHeaderValues.tsx @@ -82,7 +82,9 @@ export const DashboardHeaderValues: FC<{ (BN(underlyingBalance).gt(0) || BN(totalBorrows).gt(0)), ) - const { data: vDotApy, isLoading: isVDotApyLoading } = useBifrostVDotApy() + const { data: vDotApy, isLoading: isVDotApyLoading } = useBifrostVDotApy({ + enabled: vDotSuppliedOrBorrowed, + }) return ( <> diff --git a/src/sections/wallet/assets/WalletAssets.utils.ts b/src/sections/wallet/assets/WalletAssets.utils.ts index 82798e05e..2d1ed4099 100644 --- a/src/sections/wallet/assets/WalletAssets.utils.ts +++ b/src/sections/wallet/assets/WalletAssets.utils.ts @@ -47,7 +47,7 @@ export const useWalletAssetsTotals = ({ }: { address?: string } = {}) => { - const borrows = useUserBorrowSummary() + const borrows = useUserBorrowSummary(address) const assets = useAssetsData({ isAllAssets: false, address }) const lpPositions = useOmnipoolPositionsData({ address }) const farmsTotal = useFarmDepositsTotal(address) @@ -63,6 +63,8 @@ export const useWalletAssetsTotals = ({ shareTokenBalances.map((token) => token.asset.id), ) + console.log(borrows.error) + const assetsTotal = useMemo( () => assets.data.reduce((acc, cur) => {