From 11e9d12c22b7a6e2e0798158b7ecc19db8bf6e35 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 2 Nov 2023 09:19:44 +0900 Subject: [PATCH] feat: thorchain lending improvements (#5562) * feat: tackle TODOs * feat: move AccountId state management up top in Borrow.tsx * feat: lending improvements * feat: 2mn staleTime for thorchainLendingPosition * feat: remove Infinity staleTime comment * feat: programmatic thor lending assets * feat: more skeletons * feat: more more skeletons * feat: error-handling * feat: cleanup * feat: improve loading state * fix: flash of content * feat: more skeletony * feat: utxo deposits * feat: rm console.log * feat: improve error handling * fix: derp * feat: no default * feat: add unification comment --- src/pages/Lending/Pool/Pool.tsx | 34 ++- .../Lending/Pool/components/Borrow/Borrow.tsx | 50 ++++- .../Pool/components/Borrow/BorrowConfirm.tsx | 121 +++++++--- .../Pool/components/Borrow/BorrowInput.tsx | 207 +++++++++++------- .../Lending/Pool/components/LoanSummary.tsx | 24 +- .../Lending/hooks/useLendingPositionData.tsx | 4 +- .../Lending/hooks/useLendingQuoteQuery.ts | 7 +- .../hooks/useLendingSupportedAssets.ts | 48 ++++ 8 files changed, 355 insertions(+), 140 deletions(-) create mode 100644 src/pages/Lending/hooks/useLendingSupportedAssets.ts diff --git a/src/pages/Lending/Pool/Pool.tsx b/src/pages/Lending/Pool/Pool.tsx index cbc895034a6..3658eb9d7c8 100644 --- a/src/pages/Lending/Pool/Pool.tsx +++ b/src/pages/Lending/Pool/Pool.tsx @@ -17,7 +17,7 @@ import { TabPanels, Tabs, } from '@chakra-ui/react' -import { fromAssetId } from '@shapeshiftoss/caip' +import type { AccountId } from '@shapeshiftoss/caip' import { useQuery } from '@tanstack/react-query' import axios from 'axios' import { getConfig } from 'config' @@ -31,11 +31,7 @@ import { Main } from 'components/Layout/Main' import { RawText, Text } from 'components/Text' import { useRouteAssetId } from 'hooks/useRouteAssetId/useRouteAssetId' import { bnOrZero } from 'lib/bignumber/bignumber' -import { - selectAssetById, - selectFirstAccountIdByChainId, - selectMarketDataById, -} from 'state/slices/selectors' +import { selectAssetById, selectMarketDataById } from 'state/slices/selectors' import { useAppSelector } from 'state/store' import { useLendingPositionData } from '../hooks/useLendingPositionData' @@ -68,6 +64,9 @@ const PoolHeader = () => { const flexDirPool: ResponsiveValue = { base: 'column', lg: 'row' } export const Pool = () => { + const [collateralAccountId, setCollateralAccountId] = useState('') + const [borrowAccountId, setBorrowAccountId] = useState('') + const poolAssetId = useRouteAssetId() const asset = useAppSelector(state => selectAssetById(state, poolAssetId)) @@ -76,24 +75,14 @@ export const Pool = () => { const poolAssetMarketData = useAppSelector(state => selectMarketDataById(state, poolAssetId)) - // TODO(gomes): programmatic - this assumes account 0 for now - const accountId = - useAppSelector(state => - selectFirstAccountIdByChainId(state, fromAssetId(poolAssetId).chainId), - ) ?? '' - const repaymentLockQueryKey = useMemo(() => ['thorchainLendingRepaymentLock'], []) const { data: lendingPositionData, isLoading: isLendingPositionDataLoading } = useLendingPositionData({ assetId: poolAssetId, - accountId, + accountId: collateralAccountId, }) const { data: repaymentLock, isLoading: isRepaymentLockLoading } = useQuery({ - // TODO(gomes): we may or may not want to change this, but this avoids spamming the API for the time being. - // by default, there's a 5mn cache time, but a 0 stale time, meaning queries are considered stale immediately - // Since react-query queries aren't persisted, and until we have an actual need for ensuring the data is fresh, - // this is a good way to avoid spamming the API during develpment staleTime: Infinity, queryKey: repaymentLockQueryKey, queryFn: async () => { @@ -101,6 +90,8 @@ export const Pool = () => { const { data: mimir } = await axios.get>( `${daemonUrl}/lcd/thorchain/mimir`, ) + // TODO(gomes): this is the repayment lock of the pool - not the borrower's + // we will want to make it programmatic in case there's an active position. // https://dev.thorchain.org/thorchain-dev/lending/quick-start-guide if ('LOANREPAYMENTMATURITY' in mimir) return mimir.LOANREPAYMENTMATURITY as number return null @@ -114,7 +105,7 @@ export const Pool = () => { .div(60 * 60 * 24) .toString() }, - enabled: Boolean(accountId && poolAssetId && poolAssetMarketData.price !== '0'), + enabled: Boolean(poolAssetId && poolAssetMarketData.price !== '0'), }) const headerComponent = useMemo(() => , []) @@ -238,7 +229,12 @@ export const Pool = () => { - + diff --git a/src/pages/Lending/Pool/components/Borrow/Borrow.tsx b/src/pages/Lending/Pool/components/Borrow/Borrow.tsx index f4f9809c751..ca2a734e01b 100644 --- a/src/pages/Lending/Pool/components/Borrow/Borrow.tsx +++ b/src/pages/Lending/Pool/components/Borrow/Borrow.tsx @@ -1,8 +1,9 @@ -import type { AssetId } from '@shapeshiftoss/caip' +import type { AccountId, AssetId } from '@shapeshiftoss/caip' import { AnimatePresence } from 'framer-motion' import { memo, useCallback, useState } from 'react' import { MemoryRouter, Route, Switch, useLocation } from 'react-router' import { useRouteAssetId } from 'hooks/useRouteAssetId/useRouteAssetId' +import type { Asset } from 'lib/asset-service' import { BorrowConfirm } from './BorrowConfirm' import { BorrowInput } from './BorrowInput' @@ -10,7 +11,18 @@ import { BorrowRoutePaths } from './types' const BorrowEntries = [BorrowRoutePaths.Input, BorrowRoutePaths.Confirm] -export const Borrow = () => { +type BorrowProps = { + collateralAccountId: AccountId + borrowAccountId: AccountId + onCollateralAccountIdChange: (accountId: AccountId) => void + onBorrowAccountIdChange: (accountId: AccountId) => void +} +export const Borrow = ({ + collateralAccountId, + borrowAccountId, + onCollateralAccountIdChange: handleCollateralAccountIdChange, + onBorrowAccountIdChange: handleBorrowAccountIdChange, +}: BorrowProps) => { const [depositAmount, setDepositAmount] = useState(null) const handleDepositAmountChange = useCallback((value: string) => { @@ -25,6 +37,10 @@ export const Borrow = () => { collateralAssetId={collateralAssetId} depositAmount={depositAmount} onDepositAmountChange={handleDepositAmountChange} + collateralAccountId={collateralAccountId} + borrowAccountId={borrowAccountId} + onCollateralAccountIdChange={handleCollateralAccountIdChange} + onBorrowAccountIdChange={handleBorrowAccountIdChange} /> ) @@ -34,11 +50,25 @@ type BorrowRoutesProps = { collateralAssetId: AssetId depositAmount: string | null onDepositAmountChange: (value: string) => void + collateralAccountId: AccountId + borrowAccountId: AccountId + onCollateralAccountIdChange: (accountId: AccountId) => void + onBorrowAccountIdChange: (accountId: AccountId) => void } const BorrowRoutes = memo( - ({ collateralAssetId, depositAmount, onDepositAmountChange }: BorrowRoutesProps) => { + ({ + collateralAssetId, + depositAmount, + onDepositAmountChange, + collateralAccountId, + borrowAccountId, + onCollateralAccountIdChange: handleCollateralAccountIdChange, + onBorrowAccountIdChange: handleBorrowAccountIdChange, + }: BorrowRoutesProps) => { const location = useLocation() + const [borrowAsset, setBorrowAsset] = useState(null) + return ( @@ -46,11 +76,23 @@ const BorrowRoutes = memo( - + diff --git a/src/pages/Lending/Pool/components/Borrow/BorrowConfirm.tsx b/src/pages/Lending/Pool/components/Borrow/BorrowConfirm.tsx index 337033332a4..c1c99195ea6 100644 --- a/src/pages/Lending/Pool/components/Borrow/BorrowConfirm.tsx +++ b/src/pages/Lending/Pool/components/Borrow/BorrowConfirm.tsx @@ -8,8 +8,8 @@ import { Skeleton, Stack, } from '@chakra-ui/react' -import type { AssetId } from '@shapeshiftoss/caip' -import { btcAssetId, fromAccountId, fromAssetId } from '@shapeshiftoss/caip' +import type { AccountId, AssetId } from '@shapeshiftoss/caip' +import { fromAccountId, fromAssetId } from '@shapeshiftoss/caip' import { FeeDataKey } from '@shapeshiftoss/chain-adapters' import { supportsETH } from '@shapeshiftoss/hdwallet-core' import { TxStatus } from '@shapeshiftoss/unchained-client' @@ -30,14 +30,17 @@ import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingl import { queryClient } from 'context/QueryClientProvider/queryClient' import { getSupportedEvmChainIds } from 'hooks/useEvm/useEvm' import { useWallet } from 'hooks/useWallet/useWallet' +import type { Asset } from 'lib/asset-service' import { bnOrZero } from 'lib/bignumber/bignumber' import { useLendingPositionData } from 'pages/Lending/hooks/useLendingPositionData' import { useLendingQuoteQuery } from 'pages/Lending/hooks/useLendingQuoteQuery' +import { getThorchainLendingPosition } from 'state/slices/opportunitiesSlice/resolvers/thorchainLending/utils' import { waitForThorchainUpdate } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils' +import { isUtxoChainId } from 'state/slices/portfolioSlice/utils' import { selectAssetById, - selectFirstAccountIdByChainId, selectMarketDataById, + selectPortfolioAccountMetadataByAccountId, selectSelectedCurrency, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -48,9 +51,17 @@ import { BorrowRoutePaths } from './types' type BorrowConfirmProps = { collateralAssetId: AssetId depositAmount: string | null + collateralAccountId: AccountId + borrowAccountId: AccountId + borrowAsset: Asset | null } -export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfirmProps) => { +export const BorrowConfirm = ({ + collateralAssetId, + depositAmount, + collateralAccountId, + borrowAsset, +}: BorrowConfirmProps) => { const { state: { wallet }, } = useWallet() @@ -58,7 +69,7 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir const [txHash, setTxHash] = useState(null) const [isLoanOpenPending, setIsLoanOpenPending] = useState(false) - const borrowAssetId = btcAssetId // TODO(gomes): programmatic + const borrowAssetId = borrowAsset?.assetId ?? '' const history = useHistory() const translate = useTranslate() const collateralAsset = useAppSelector(state => selectAssetById(state, collateralAssetId)) @@ -66,15 +77,9 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir const collateralAssetMarketData = useAppSelector(state => selectMarketDataById(state, collateralAssetId), ) - // TODO(gomes): programmatic - const depositAccountId = - useAppSelector(state => - selectFirstAccountIdByChainId(state, fromAssetId(collateralAssetId).chainId), - ) ?? '' - const { refetch: refetchLendingPositionData } = useLendingPositionData({ assetId: collateralAssetId, - accountId: depositAccountId, + accountId: collateralAccountId, }) useEffect(() => { @@ -99,16 +104,64 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir // so we have a safety to not refetch quotes while borrow is pending // perhaps a shared react-query mutation hook would make sense in handleSend(), so we have a way to introspect pending status // from input components and disable inputs as well? - const { data: lendingQuoteData, isLoading: isLendingQuoteLoading } = useLendingQuoteQuery({ + const { + data, + isLoading: isLendingQuoteLoading, + isError: isLendingQuoteError, + } = useLendingQuoteQuery({ collateralAssetId, borrowAssetId, depositAmountCryptoPrecision: depositAmount ?? '0', }) + const lendingQuoteData = isLendingQuoteError ? null : data + const chainAdapter = getChainAdapterManager().get(fromAssetId(collateralAssetId).chainId) const selectedCurrency = useAppSelector(selectSelectedCurrency) + const collateralAccountFilter = useMemo( + () => ({ accountId: collateralAccountId }), + [collateralAccountId], + ) + const collateralAccountMetadata = useAppSelector(state => + selectPortfolioAccountMetadataByAccountId(state, collateralAccountFilter), + ) + const collateralAccountType = collateralAccountMetadata?.accountType + const collateralBip44Params = collateralAccountMetadata?.bip44Params + + const getFromAddress = useCallback(async () => { + if (!(wallet && chainAdapter && collateralBip44Params)) return null + + // TODO(gomes): unify me across savers/lending along with other utils + return isUtxoChainId(fromAccountId(collateralAccountId).chainId) + ? await getThorchainLendingPosition({ + accountId: collateralAccountId, + assetId: collateralAssetId, + }) + .then(position => { + if (!position) throw new Error(`No position found for assetId: ${collateralAssetId}`) + }) + .catch(async () => { + const firstReceiveAddress = await chainAdapter.getAddress({ + wallet, + accountNumber: collateralBip44Params.accountNumber, + accountType: collateralAccountType, + index: 0, + }) + + return firstReceiveAddress + }) + : fromAccountId(collateralAccountId).account + }, [ + wallet, + chainAdapter, + collateralBip44Params, + collateralAccountType, + collateralAccountId, + collateralAssetId, + ]) + // TODO(gomes): handle error (including trading halted) and loading states here const handleDeposit = useCallback(async () => { if ( @@ -122,17 +175,21 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir ) ) return + const from = await getFromAddress() + + if (!from) throw new Error(`Could not get send address for AccountId ${collateralAccountId}`) + const supportedEvmChainIds = getSupportedEvmChainIds() const estimatedFees = await estimateFees({ cryptoAmount: depositAmount, assetId: collateralAssetId, - from: fromAccountId(depositAccountId).account, // TODO(gomes): handle UTXOs + from, memo: supportedEvmChainIds.includes(fromAssetId(collateralAssetId).chainId) ? utils.hexlify(utils.toUtf8Bytes(lendingQuoteData.quoteMemo)) : lendingQuoteData.quoteMemo, to: lendingQuoteData.quoteInboundAddress, sendMax: false, - accountId: depositAccountId, + accountId: collateralAccountId, contractAddress: undefined, }) @@ -141,14 +198,13 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir const maybeTxId = await (() => { // TODO(gomes): isTokenDeposit. This doesn't exist yet but may in the future. - // TODO(gomes): isUtxoChainId as well const sendInput: SendInput = { cryptoAmount: depositAmount ?? '0', assetId: collateralAssetId, to: lendingQuoteData.quoteInboundAddress, - from: fromAccountId(depositAccountId).account, // TODO(gomes): support UTXOs as well, this is just the first naive implementation without UTXO support + from, sendMax: false, - accountId: depositAccountId, + accountId: collateralAccountId, memo: supportedEvmChainIds.includes(fromAssetId(collateralAssetId).chainId) ? utils.hexlify(utils.toUtf8Bytes(lendingQuoteData.quoteMemo)) : lendingQuoteData.quoteMemo, @@ -174,13 +230,14 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir return maybeTxId }, [ - chainAdapter, collateralAssetId, - depositAccountId, depositAmount, + wallet, + chainAdapter, lendingQuoteData, + getFromAddress, + collateralAccountId, selectedCurrency, - wallet, ]) if (!depositAmount) return null @@ -210,16 +267,18 @@ export const BorrowConfirm = ({ collateralAssetId, depositAmount }: BorrowConfir Send - - - - + + + + + + diff --git a/src/pages/Lending/Pool/components/Borrow/BorrowInput.tsx b/src/pages/Lending/Pool/components/Borrow/BorrowInput.tsx index 99dd35d2c70..cee4da1a6e0 100644 --- a/src/pages/Lending/Pool/components/Borrow/BorrowInput.tsx +++ b/src/pages/Lending/Pool/components/Borrow/BorrowInput.tsx @@ -1,8 +1,16 @@ import { ArrowDownIcon } from '@chakra-ui/icons' -import { Button, CardFooter, Collapse, Divider, Flex, IconButton, Stack } from '@chakra-ui/react' +import { + Button, + CardFooter, + Collapse, + Divider, + Flex, + IconButton, + Skeleton, + Stack, +} from '@chakra-ui/react' import type { AccountId, AssetId } from '@shapeshiftoss/caip' -import { btcAssetId, fromAssetId } from '@shapeshiftoss/caip' -import { useCallback, useMemo } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { useTranslate } from 'react-polyglot' import { useHistory } from 'react-router' import { Amount } from 'components/Amount/Amount' @@ -10,12 +18,13 @@ import { TradeAssetSelect } from 'components/MultiHopTrade/components/AssetSelec import { TradeAssetInput } from 'components/MultiHopTrade/components/TradeAssetInput' import { Row } from 'components/Row/Row' import { SlideTransition } from 'components/SlideTransition' +import { useModal } from 'hooks/useModal/useModal' import type { Asset } from 'lib/asset-service' import { bn, bnOrZero } from 'lib/bignumber/bignumber' import { useLendingQuoteQuery } from 'pages/Lending/hooks/useLendingQuoteQuery' +import { useLendingSupportedAssets } from 'pages/Lending/hooks/useLendingSupportedAssets' import { selectAssetById, - selectFirstAccountIdByChainId, selectMarketDataById, selectPortfolioCryptoBalanceBaseUnitByFilter, } from 'state/slices/selectors' @@ -34,16 +43,36 @@ type BorrowInputProps = { collateralAssetId: AssetId depositAmount: string | null onDepositAmountChange: (value: string) => void + collateralAccountId: AccountId + borrowAccountId: AccountId + onCollateralAccountIdChange: (accountId: AccountId) => void + onBorrowAccountIdChange: (accountId: AccountId) => void + borrowAsset: Asset | null + setBorrowAsset: (asset: Asset) => void } export const BorrowInput = ({ collateralAssetId, depositAmount, onDepositAmountChange, + collateralAccountId, + borrowAccountId, + onCollateralAccountIdChange: handleCollateralAccountIdChange, + onBorrowAccountIdChange: handleBorrowAccountIdChange, + borrowAsset, + setBorrowAsset, }: BorrowInputProps) => { const translate = useTranslate() const history = useHistory() + const { data: lendingSupportedAssets } = useLendingSupportedAssets() + + useEffect(() => { + if (!lendingSupportedAssets) return + + setBorrowAsset(lendingSupportedAssets[0]) + }, [lendingSupportedAssets, setBorrowAsset]) + const collateralAsset = useAppSelector(state => selectAssetById(state, collateralAssetId)) const collateralAssetMarketData = useAppSelector(state => selectMarketDataById(state, collateralAssetId), @@ -58,12 +87,17 @@ export const BorrowInput = ({ }, [history]) const handleAccountIdChange = useCallback((accountId: AccountId) => { - console.info(accountId) + console.info({ accountId }) }, []) - const handleAssetClick = useCallback(() => { - console.info('clicked Asset') - }, []) + const buyAssetSearch = useModal('buyAssetSearch') + const handleBorrowAssetClick = useCallback(() => { + buyAssetSearch.open({ + onClick: setBorrowAsset, + title: 'lending.borrow', + assets: lendingSupportedAssets, + }) + }, [buyAssetSearch, lendingSupportedAssets, setBorrowAsset]) const handleAssetChange = useCallback((asset: Asset) => { return console.info(asset) @@ -76,14 +110,9 @@ export const BorrowInput = ({ [onDepositAmountChange], ) - // TODO(gomes): programmatic - const depositAccountId = - useAppSelector(state => - selectFirstAccountIdByChainId(state, fromAssetId(collateralAssetId).chainId), - ) ?? '' const balanceFilter = useMemo( - () => ({ assetId: collateralAssetId, accountId: depositAccountId }), - [collateralAssetId, depositAccountId], + () => ({ assetId: collateralAssetId, accountId: collateralAccountId }), + [collateralAssetId, collateralAccountId], ) const balance = useAppSelector(state => selectPortfolioCryptoBalanceBaseUnitByFilter(state, balanceFilter), @@ -107,42 +136,58 @@ export const BorrowInput = ({ const depositAssetSelectComponent = useMemo(() => { return ( ) - }, [collateralAssetId, handleAccountIdChange, handleAssetChange, handleAssetClick]) + }, [ + collateralAccountId, + collateralAssetId, + handleAccountIdChange, + handleAssetChange, + handleBorrowAssetClick, + ]) - const assetSelectComponent = useMemo(() => { + const borrowAssetSelectComponent = useMemo(() => { return ( ) - }, [handleAccountIdChange, handleAssetChange, handleAssetClick]) + }, [ + borrowAccountId, + borrowAsset?.assetId, + handleAccountIdChange, + handleAssetChange, + handleBorrowAssetClick, + ]) - const { data: lendingQuoteData, isLoading: isLendingQuoteLoading } = useLendingQuoteQuery({ + const { + data, + isLoading: isLendingQuoteLoading, + isError: isLendingQuoteError, + } = useLendingQuoteQuery({ collateralAssetId, - borrowAssetId: btcAssetId, // TODO(gomes): programmatic + borrowAssetId: borrowAsset?.assetId ?? '', depositAmountCryptoPrecision: depositAmount ?? '0', }) - console.log({ lendingQuoteData, isLendingQuoteLoading }) + const lendingQuoteData = isLendingQuoteError ? null : data + + if (!(collateralAsset && borrowAsset)) return null - if (!collateralAsset) return null return ( @@ -160,7 +205,7 @@ export const BorrowInput = ({ showInputSkeleton={false} showFiatSkeleton={false} label={`Deposit ${collateralAsset.symbol}`} - onAccountIdChange={handleAccountIdChange} + onAccountIdChange={handleCollateralAccountIdChange} formControlProps={formControlProps} layout='inline' labelPostFix={depositAssetSelectComponent} @@ -180,9 +225,9 @@ export const BorrowInput = ({ - - - {translate('common.slippage')} - - - - - - {translate('common.gasFee')} - - - - - - {translate('common.fees')} - - - - - - + + {translate('common.slippage')} + + + + + + + + {translate('common.gasFee')} + + + + + + + + {translate('common.fees')} + + + + + + + + + )} ) diff --git a/src/pages/Lending/Pool/components/LoanSummary.tsx b/src/pages/Lending/Pool/components/LoanSummary.tsx index 808106e843c..63d0d56b301 100644 --- a/src/pages/Lending/Pool/components/LoanSummary.tsx +++ b/src/pages/Lending/Pool/components/LoanSummary.tsx @@ -94,13 +94,17 @@ export const LoanSummary: React.FC = ({ enabled: Boolean(accountId && collateralAssetId && collateralAssetMarketData.price !== '0'), }) - const { data: lendingQuoteData, isLoading: isLendingQuoteLoading } = useLendingQuoteQuery({ + const { + data: lendingQuoteData, + isLoading: isLendingQuoteLoading, + isError: isLendingQuoteError, + } = useLendingQuoteQuery({ collateralAssetId, borrowAssetId, depositAmountCryptoPrecision, }) - if (!collateralAsset) return null + if (!collateralAsset || isLendingQuoteError) return null return ( = ({ {translate('lending.debt')} - + = ({ {translate('lending.repaymentLock')} - + 25 days 30 days @@ -177,7 +185,9 @@ export const LoanSummary: React.FC = ({ {translate('lending.collateralizationRatio')} - + = ({ {translate('lending.poolDepth')} - + {translate('lending.healthy')} diff --git a/src/pages/Lending/hooks/useLendingPositionData.tsx b/src/pages/Lending/hooks/useLendingPositionData.tsx index a7b3b9b56e6..43b1f528cab 100644 --- a/src/pages/Lending/hooks/useLendingPositionData.tsx +++ b/src/pages/Lending/hooks/useLendingPositionData.tsx @@ -19,7 +19,8 @@ export const useLendingPositionData = ({ accountId, assetId }: UseLendingPositio const poolAssetMarketData = useAppSelector(state => selectMarketDataById(state, assetId)) const lendingPositionData = useQuery({ - staleTime: Infinity, + // 2 minutes before the data is considered stale, meaning firing this query will trigger queryFn + staleTime: 120_000, queryKey: lendingPositionQueryKey, queryFn: async ({ queryKey }) => { const [, { accountId, assetId }] = queryKey @@ -39,6 +40,7 @@ export const useLendingPositionData = ({ accountId, assetId }: UseLendingPositio collateralBalanceCryptoPrecision, collateralBalanceFiatUserCurrency, debtBalanceFiatUSD, + address: data?.owner, } }, enabled: Boolean(accountId && assetId && poolAssetMarketData.price !== '0'), diff --git a/src/pages/Lending/hooks/useLendingQuoteQuery.ts b/src/pages/Lending/hooks/useLendingQuoteQuery.ts index 5d58e68a7fb..ac1eba728b6 100644 --- a/src/pages/Lending/hooks/useLendingQuoteQuery.ts +++ b/src/pages/Lending/hooks/useLendingQuoteQuery.ts @@ -42,7 +42,9 @@ export const useLendingQuoteQuery = ({ // TODO(gomes): programmatic const destinationAccountId = useAppSelector(state => - selectFirstAccountIdByChainId(state, fromAssetId(borrowAssetId).chainId), + borrowAssetId + ? selectFirstAccountIdByChainId(state, fromAssetId(borrowAssetId).chainId) + : undefined, ) ?? '' const destinationAccountMetadataFilter = useMemo( () => ({ accountId: destinationAccountId }), @@ -182,11 +184,12 @@ export const useLendingQuoteQuery = ({ enabled: Boolean( bnOrZero(depositAmountCryptoPrecision).gt(0) && accountId && + borrowAssetId && destinationAccountMetadata && collateralAsset && borrowAssetReceiveAddress && collateralAssetMarketData.price !== '0' && - depositAmountCryptoPrecision !== '0', + bnOrZero(depositAmountCryptoPrecision).gt(0), ), }) diff --git a/src/pages/Lending/hooks/useLendingSupportedAssets.ts b/src/pages/Lending/hooks/useLendingSupportedAssets.ts new file mode 100644 index 00000000000..2922ef5b759 --- /dev/null +++ b/src/pages/Lending/hooks/useLendingSupportedAssets.ts @@ -0,0 +1,48 @@ +import { useQuery } from '@tanstack/react-query' +import { getConfig } from 'config' +import type { ThornodePoolResponse } from 'lib/swapper/swappers/ThorchainSwapper/types' +import { poolAssetIdToAssetId } from 'lib/swapper/swappers/ThorchainSwapper/utils/poolAssetHelpers/poolAssetHelpers' +import { thorService } from 'lib/swapper/swappers/ThorchainSwapper/utils/thorService' +import { isSome } from 'lib/utils' +import { selectAssetById } from 'state/slices/selectors' +import { store } from 'state/store' + +const queryKey = ['lendingSupportedAssets'] + +export const useLendingSupportedAssets = () => { + const lendingPositionData = useQuery({ + staleTime: Infinity, + queryKey, + queryFn: async () => { + const daemonUrl = getConfig().REACT_APP_THORCHAIN_NODE_URL + const poolResponse = await thorService.get( + `${daemonUrl}/lcd/thorchain/pools`, + ) + if (poolResponse.isOk()) { + const allPools = poolResponse.unwrap().data + + return allPools + } + }, + select: data => { + if (!data) return [] + const availablePools = data.filter( + pool => + pool.status === 'Available' && + // This is weird, but THORChain API is currently returning a loan_cr of 20000 for pools which don't support lending + pool.loan_cr !== '20000' && + pool.loan_cr !== '0', + ) + + return availablePools + .map(pool => { + const assetId = poolAssetIdToAssetId(pool.asset) + const asset = selectAssetById(store.getState(), assetId ?? '') + return asset + }) + .filter(isSome) + }, + }) + + return lendingPositionData +}