Skip to content

Commit

Permalink
feat: improve thorchain status polling reliability (#5659)
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre authored Nov 21, 2023
1 parent 1d207ef commit 31326c4
Show file tree
Hide file tree
Showing 12 changed files with 76 additions and 60 deletions.
4 changes: 2 additions & 2 deletions src/context/TransactionsProvider/TransactionsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useSelector } from 'react-redux'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
import { useWallet } from 'hooks/useWallet/useWallet'
import { isSome } from 'lib/utils'
import { waitForThorchainUpdate } from 'lib/utils/thorchain'
import { nftApi } from 'state/apis/nft/nftApi'
import { assets as assetsSlice } from 'state/slices/assetsSlice/assetsSlice'
import { makeNftAssetsFromTxs } from 'state/slices/assetsSlice/utils'
Expand All @@ -18,7 +19,6 @@ import type { IdleStakingSpecificMetadata } from 'state/slices/opportunitiesSlic
import {
isSupportedThorchainSaversAssetId,
isSupportedThorchainSaversChainId,
waitForThorchainUpdate,
} from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import { fetchAllOpportunitiesUserDataByAccountId } from 'state/slices/opportunitiesSlice/thunks'
import { DefiProvider, DefiType } from 'state/slices/opportunitiesSlice/types'
Expand Down Expand Up @@ -129,7 +129,7 @@ export const TransactionsProvider: React.FC<TransactionsProviderProps> = ({ chil
} else if (shouldRefetchSaversOpportunities) {
// Artificial longer completion time, since THORChain Txs take around 15s after confirmation to be picked in the API
// This way, we ensure "View Position" actually routes to the updated position
waitForThorchainUpdate(txid).promise.then(() => {
waitForThorchainUpdate({ txHash: txid }).promise.then(() => {
dispatch(
getOpportunitiesUserData.initiate(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { bn, bnOrZero } from 'lib/bignumber/bignumber'
import { trackOpportunityEvent } from 'lib/mixpanel/helpers'
import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton'
import { MixPanelEvents } from 'lib/mixpanel/types'
import { waitForThorchainUpdate } from 'lib/utils/thorchain'
import { opportunitiesApi } from 'state/slices/opportunitiesSlice/opportunitiesApiSlice'
import { waitForThorchainUpdate } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import {
selectAssetById,
selectAssets,
Expand Down Expand Up @@ -86,7 +86,8 @@ export const Status: React.FC<StatusProps> = ({ accountId }) => {
;(async () => {
// Artificial longer completion time, since THORChain Txs take around 15s after confirmation to be picked in the API
// This way, we ensure "View Position" actually routes to the updated position
await waitForThorchainUpdate(confirmedTransaction.txid).promise
await waitForThorchainUpdate({ txHash: confirmedTransaction.txid, skipOutbound: true })
.promise

if (confirmedTransaction.status === 'Confirmed') {
contextDispatch({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { bn, bnOrZero } from 'lib/bignumber/bignumber'
import { trackOpportunityEvent } from 'lib/mixpanel/helpers'
import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton'
import { MixPanelEvents } from 'lib/mixpanel/types'
import { waitForThorchainUpdate } from 'lib/utils/thorchain'
import { opportunitiesApi } from 'state/slices/opportunitiesSlice/opportunitiesApiSlice'
import { waitForThorchainUpdate } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import {
selectAssetById,
selectAssets,
Expand Down Expand Up @@ -85,7 +85,8 @@ export const Status: React.FC<StatusProps> = ({ accountId }) => {
;(async () => {
// Artificial longer completion time, since THORChain Txs take around 15s after confirmation to be picked in the API
// This way, we ensure "View Position" actually routes to the updated position
await waitForThorchainUpdate(confirmedTransaction.txid).promise
await waitForThorchainUpdate({ txHash: confirmedTransaction.txid, skipOutbound: true })
.promise

if (confirmedTransaction.status === 'Confirmed') {
contextDispatch({
Expand Down
28 changes: 27 additions & 1 deletion src/lib/utils/thorchain/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { AccountId } from '@shapeshiftoss/caip'
import { TxStatus } from '@shapeshiftoss/unchained-client'
import type { QueryClient } from '@tanstack/react-query'
import { queryClient } from 'context/QueryClientProvider/queryClient'
import type { Asset } from 'lib/asset-service'
import { bnOrZero } from 'lib/bignumber/bignumber'
import { fromBaseUnit, toBaseUnit } from 'lib/math'
import { poll } from 'lib/poll/poll'
import {
type EstimatedFeesQueryKey,
queryFn as getEstimatedFeesQueryFn,
Expand All @@ -11,7 +14,10 @@ import type { IsSweepNeededQueryKey } from 'pages/Lending/hooks/useIsSweepNeeded
import { queryFn as isSweepNeededQueryFn } from 'pages/Lending/hooks/useIsSweepNeededQuery'
import { selectPortfolioCryptoBalanceBaseUnitByFilter } from 'state/slices/common-selectors'
import type { ThorchainSaversWithdrawQuoteResponseSuccess } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/types'
import { fromThorBaseUnit } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import {
fromThorBaseUnit,
getThorchainTransactionStatus,
} from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import { isUtxoChainId } from 'state/slices/portfolioSlice/utils'
import { selectMarketDataById } from 'state/slices/selectors'
import { store } from 'state/store'
Expand All @@ -23,6 +29,26 @@ import {
queryFn as getThorchainSaversWithdrawQuoteQueryFn,
} from './hooks/useGetThorchainSaversWithdrawQuoteQuery'

export const waitForThorchainUpdate = ({
txHash,
queryClient,
skipOutbound,
}: {
txHash: string
queryClient?: QueryClient
skipOutbound?: boolean
}) =>
poll({
fn: () => {
// Invalidate some react-queries everytime we poll - since status detection is currently suboptimal
queryClient?.invalidateQueries({ queryKey: ['thorchainLendingPosition'], exact: false })
return getThorchainTransactionStatus(txHash, skipOutbound)
},
validate: status => Boolean(status && status === TxStatus.Confirmed),
interval: 60000,
maxAttempts: 20,
})

// TODO(gomes): this will work for UTXO but is invalid for tokens since they use diff. denoms
// the current workaround is to not do fee deduction for non-UTXO chains,
// but for consistency, we should for native EVM assets, and ensure this is a no-op for tokens
Expand Down
38 changes: 21 additions & 17 deletions src/pages/Lending/Pool/components/Borrow/Borrow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AccountId, AssetId } from '@shapeshiftoss/caip'
import { AnimatePresence } from 'framer-motion'
import { lazy, memo, useCallback, useState } from 'react'
import { lazy, memo, Suspense, 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'
Expand All @@ -22,6 +22,8 @@ const BorrowConfirm = lazy(() =>

const BorrowEntries = [BorrowRoutePaths.Input, BorrowRoutePaths.Confirm]

const suspenseFallback = <div>Loading...</div>

type BorrowProps = {
collateralAccountId: AccountId
borrowAccountId: AccountId
Expand Down Expand Up @@ -161,22 +163,24 @@ const BorrowRoutes = memo(
return (
<AnimatePresence exitBeforeEnter initial={false}>
<Switch location={location}>
<Route
key={BorrowRoutePaths.Input}
path={BorrowRoutePaths.Input}
render={renderBorrowInput}
/>
<Route
key={BorrowRoutePaths.Sweep}
path={BorrowRoutePaths.Sweep}
render={renderBorrowSweep}
/>

<Route
key={BorrowRoutePaths.Confirm}
path={BorrowRoutePaths.Confirm}
render={renderBorrowConfirm}
/>
<Suspense fallback={suspenseFallback}>
<Route
key={BorrowRoutePaths.Input}
path={BorrowRoutePaths.Input}
render={renderBorrowInput}
/>
<Route
key={BorrowRoutePaths.Sweep}
path={BorrowRoutePaths.Sweep}
render={renderBorrowSweep}
/>

<Route
key={BorrowRoutePaths.Confirm}
path={BorrowRoutePaths.Confirm}
render={renderBorrowConfirm}
/>
</Suspense>
</Switch>
</AnimatePresence>
)
Expand Down
9 changes: 3 additions & 6 deletions src/pages/Lending/Pool/components/Borrow/BorrowConfirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,12 @@ 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 { waitForThorchainUpdate } from 'lib/utils/thorchain'
import { useLendingPositionData } from 'pages/Lending/hooks/useLendingPositionData'
import { useLendingQuoteOpenQuery } from 'pages/Lending/hooks/useLendingQuoteQuery'
import { useQuoteEstimatedFeesQuery } from 'pages/Lending/hooks/useQuoteEstimatedFees'
import { getThorchainLendingPosition } from 'state/slices/opportunitiesSlice/resolvers/thorchainLending/utils'
import {
getThorchainFromAddress,
waitForThorchainUpdate,
} from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import { getThorchainFromAddress } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import {
selectAssetById,
selectMarketDataById,
Expand Down Expand Up @@ -91,8 +89,7 @@ export const BorrowConfirm = ({

setIsLoanOpenPending(true)
;(async () => {
// TODO(gomes): we might want to change heuristics here - this takes forever to be truthy, while the loan open itself is reflected way earlier, at least for ETH
await waitForThorchainUpdate(txHash, queryClient).promise
await waitForThorchainUpdate({ txHash, queryClient, skipOutbound: true }).promise
setIsLoanOpenPending(false)
await refetchLendingPositionData()
})()
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Lending/Pool/components/Borrow/BorrowInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ export const BorrowInput = ({
isSweepNeededLoading
}
isDisabled={Boolean(
isLendingQuoteError ||
bnOrZero(depositAmountCryptoPrecision).isZero() ||
isLendingQuoteError ||
isLendingQuoteLoading ||
quoteErrorTranslation ||
isEstimatedFeesDataError ||
Expand Down
5 changes: 2 additions & 3 deletions src/pages/Lending/Pool/components/Repay/RepayConfirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ 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 { waitForThorchainUpdate } from 'lib/utils/thorchain'
import { useLendingQuoteCloseQuery } from 'pages/Lending/hooks/useLendingCloseQuery'
import { useLendingPositionData } from 'pages/Lending/hooks/useLendingPositionData'
import { useQuoteEstimatedFeesQuery } from 'pages/Lending/hooks/useQuoteEstimatedFees'
import { waitForThorchainUpdate } from 'state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils'
import {
selectAssetById,
selectMarketDataById,
Expand Down Expand Up @@ -79,8 +79,7 @@ export const RepayConfirm = ({

setIsLoanClosePending(true)
;(async () => {
// TODO(gomes): we might want to change heuristics here - this takes forever to be truthy, while the loan open itself is reflected way earlier, at least for ETH
await waitForThorchainUpdate(txHash, queryClient).promise
await waitForThorchainUpdate({ txHash, queryClient, skipOutbound: true }).promise
setIsLoanClosePending(false)
await refetchLendingPositionData()
})()
Expand Down
10 changes: 5 additions & 5 deletions src/pages/Lending/hooks/useLendingCloseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ export const useLendingQuoteCloseQuery = ({
return repaymentPercentBn.plus('1').toNumber()
}, [_repaymentPercent])

const { data: lendingPositionData } = useLendingPositionData({
assetId: _collateralAssetId,
accountId: _collateralAccountId,
})

const lendingQuoteCloseQueryKey = useDebounce(
() => [
'lendingQuoteCloseQuery',
Expand Down Expand Up @@ -161,11 +166,6 @@ export const useLendingQuoteCloseQuery = ({
selectMarketDataById(state, collateralAssetId),
)

const { data: lendingPositionData } = useLendingPositionData({
assetId: collateralAssetId,
accountId: collateralAccountId,
})

const userCurrencyToUsdRate = useAppSelector(selectUserCurrencyToUsdRate)

const debtBalanceUserCurrency = useMemo(() => {
Expand Down
7 changes: 5 additions & 2 deletions src/pages/Lending/hooks/useLendingPositionData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const useLendingPositionData = ({ accountId, assetId }: UseLendingPositio
const poolAssetMarketData = useAppSelector(state => selectMarketDataById(state, assetId))

const lendingPositionData = useQuery({
// 2 minutes before the data is considered stale, meaning firing this query will trigger queryFn
staleTime: 120_000,
// The time before the data is considered stale, meaning firing this query after it elapses will trigger queryFn
staleTime: 60_000,
queryKey: lendingPositionQueryKey,
queryFn: async ({ queryKey }) => {
const [, { accountId, assetId }] = queryKey
Expand All @@ -44,6 +44,9 @@ export const useLendingPositionData = ({ accountId, assetId }: UseLendingPositio
}
},
enabled: Boolean(accountId && assetId && poolAssetMarketData.price !== '0'),
refetchOnMount: true,
refetchInterval: 60_000,
refetchIntervalInBackground: true,
})

return lendingPositionData
Expand Down
1 change: 1 addition & 0 deletions src/pages/Lending/hooks/useQuoteEstimatedFees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const useQuoteEstimatedFeesQuery = ({
return { estimatedFees, txFeeFiat, txFeeCryptoBaseUnit: estimatedFees.fast.txFee }
},
enabled: Boolean(asset && (lendingQuoteData || lendingQuoteCloseData)),
retry: false,
})

return useQuoteEstimatedFeesQuery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ import type { HDWallet } from '@shapeshiftoss/hdwallet-core'
import { TxStatus } from '@shapeshiftoss/unchained-client'
import type { Result } from '@sniptt/monads'
import { Err, Ok } from '@sniptt/monads'
import type { QueryClient } from '@tanstack/react-query'
import axios from 'axios'
import { getConfig } from 'config'
import memoize from 'lodash/memoize'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
import type { Asset } from 'lib/asset-service'
import type { BN } from 'lib/bignumber/bignumber'
import { BigNumber, bn, bnOrZero } from 'lib/bignumber/bignumber'
import { poll } from 'lib/poll/poll'
import type {
ThornodePoolResponse,
ThornodeStatusResponse,
Expand Down Expand Up @@ -153,7 +151,7 @@ export const getAllThorchainSaversPositions = async (
return opportunitiesData
}

export const getThorchainTransactionStatus = async (txHash: string) => {
export const getThorchainTransactionStatus = async (txHash: string, skipOutbound?: boolean) => {
const thorTxHash = txHash.replace(/^0x/, '')
const { data: thorTxData, status } = await axios.get<ThornodeStatusResponse>(
`${getConfig().REACT_APP_THORCHAIN_NODE_URL}/lcd/thorchain/tx/status/${thorTxHash}`,
Expand All @@ -167,10 +165,7 @@ export const getThorchainTransactionStatus = async (txHash: string) => {
// Despite the Tx being observed, things may be slow to be picked on the THOR node side of things i.e for swaps to/from BTC
thorTxData.stages.inbound_finalised?.completed === false ||
thorTxData.stages.swap_status?.pending === true ||
// Note, this does not apply to all Txs, e.g savers deposit won't have this property
// the *presence* of outbound_signed?.completed as false *will* indicate a pending outbound Tx, but the opposite is not neccessarily true
// i.e a succesful end-to-end "swap" might be succesful despite the *absence* of outbound_signed property
thorTxData.stages.outbound_signed?.completed === false
(!skipOutbound && thorTxData.stages.outbound_signed?.completed === false)
)
return TxStatus.Pending
if (thorTxData.stages.swap_status?.pending === false) return TxStatus.Confirmed
Expand Down Expand Up @@ -347,18 +342,6 @@ export const isSupportedThorchainSaversAssetId = (assetId: AssetId) =>
export const isSupportedThorchainSaversChainId = (chainId: ChainId) =>
SUPPORTED_THORCHAIN_SAVERS_CHAIN_IDS.includes(chainId)

export const waitForThorchainUpdate = (txHash: string, queryClient?: QueryClient) =>
poll({
fn: () => {
// Invalidate some react-queries everytime we poll - since status detection is currently suboptimal
queryClient?.invalidateQueries({ queryKey: ['thorchainLendingPosition'], exact: false })
return getThorchainTransactionStatus(txHash)
},
validate: status => Boolean(status && status === TxStatus.Confirmed),
interval: 60000,
maxAttempts: 10,
})

export const makeDaysToBreakEven = ({
expectedAmountOutThorBaseUnit,
amountCryptoBaseUnit,
Expand Down

0 comments on commit 31326c4

Please sign in to comment.