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/header/WalletAssetsHeader.tsx b/src/sections/wallet/assets/header/WalletAssetsHeader.tsx index 05a195bbb..40462819f 100644 --- a/src/sections/wallet/assets/header/WalletAssetsHeader.tsx +++ b/src/sections/wallet/assets/header/WalletAssetsHeader.tsx @@ -3,6 +3,7 @@ import { HeaderValues } from "sections/pools/header/PoolsHeader" import { HeaderTotalData } from "sections/pools/header/PoolsHeaderTotal" import { useWalletAssetsTotals } from "sections/wallet/assets/WalletAssets.utils" import BN from "bignumber.js" +import { useUserBorrowSummary } from "api/borrow" type Props = { disconnected?: boolean } @@ -10,9 +11,12 @@ export const WalletAssetsHeader = ({ disconnected }: Props) => { const { t } = useTranslation() const { isLoading, balanceTotal, assetsTotal, farmsTotal, lpTotal } = useWalletAssetsTotals() + + const { data: userSummary } = useUserBorrowSummary() + return ( { content: ( ), @@ -32,7 +36,7 @@ export const WalletAssetsHeader = ({ disconnected }: Props) => { content: ( ), @@ -43,7 +47,7 @@ export const WalletAssetsHeader = ({ disconnected }: Props) => { content: ( ), @@ -54,11 +58,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