diff --git a/src/App.tsx b/src/App.tsx index bdb0eb3127..4c4500c381 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import './i18n'; -import { FaucetClient, SecurityStatusCode } from '@interlay/interbtc-api'; +import { FaucetClient } from '@interlay/interbtc-api'; import { Keyring } from '@polkadot/keyring'; import * as React from 'react'; import { useErrorHandler, withErrorBoundary } from 'react-error-boundary'; @@ -8,9 +8,8 @@ import { useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { Redirect, Route, Switch } from 'react-router-dom'; -import { initGeneralDataAction, isFaucetLoaded, isVaultClientLoaded } from '@/common/actions/general.actions'; -import { ParachainStatus, StoreType } from '@/common/types/util.types'; -import { GOVERNANCE_TOKEN, RELAY_CHAIN_NATIVE_TOKEN, WRAPPED_TOKEN } from '@/config/relay-chains'; +import { isFaucetLoaded, isVaultClientLoaded } from '@/common/actions/general.actions'; +import { StoreType } from '@/common/types/util.types'; import ErrorFallback from '@/legacy-components/ErrorFallback'; import FullLoadingSpinner from '@/legacy-components/FullLoadingSpinner'; import { useSubstrate, useSubstrateSecureState } from '@/lib/substrate'; @@ -91,58 +90,6 @@ const App = (): JSX.Element => { ); useErrorHandler(vaultsError); - // Initializes data on app bootstrap - React.useEffect(() => { - if (!dispatch) return; - if (!bridgeLoaded) return; - - (async () => { - try { - const [ - totalWrappedTokenAmount, - totalLockedCollateralTokenAmount, - totalGovernanceTokenAmount, - btcRelayHeight, - bitcoinHeight, - state - ] = await Promise.all([ - window.bridge.tokens.total(WRAPPED_TOKEN), - window.bridge.tokens.total(RELAY_CHAIN_NATIVE_TOKEN), - window.bridge.tokens.total(GOVERNANCE_TOKEN), - window.bridge.btcRelay.getLatestBlockHeight(), - window.bridge.electrsAPI.getLatestBlockHeight(), - window.bridge.system.getStatusCode() - ]); - - const parachainStatus = (state: SecurityStatusCode) => { - if (state.isError) { - return ParachainStatus.Error; - } else if (state.isRunning) { - return ParachainStatus.Running; - } else if (state.isShutdown) { - return ParachainStatus.Shutdown; - } else { - return ParachainStatus.Loading; - } - }; - - dispatch( - initGeneralDataAction( - totalWrappedTokenAmount, - totalLockedCollateralTokenAmount, - totalGovernanceTokenAmount, - btcRelayHeight, - bitcoinHeight, - parachainStatus(state) - ) - ); - } catch (error) { - // TODO: should add error handling - console.log('[App React.useEffect 2] error.message => ', error.message); - } - })(); - }, [dispatch, bridgeLoaded]); - React.useEffect(() => { if (!setSelectedAccount) return; diff --git a/src/common/actions/general.actions.ts b/src/common/actions/general.actions.ts index 880e1da1ce..c97652ee03 100644 --- a/src/common/actions/general.actions.ts +++ b/src/common/actions/general.actions.ts @@ -1,13 +1,6 @@ -import { CollateralCurrencyExt } from '@interlay/interbtc-api'; -import { BitcoinAmount, MonetaryAmount } from '@interlay/monetary-js'; - -import { GovernanceTokenMonetaryAmount } from '@/config/relay-chains'; - import { ADD_NOTIFICATION, AddNotification, - INIT_GENERAL_DATA_ACTION, - InitGeneralDataAction, IS_BRIDGE_LOADED, IS_FAUCET_LOADED, IS_VAULT_CLIENT_LOADED, @@ -20,14 +13,10 @@ import { ShowAccountModal, ShowBuyModal, ShowSignTermsModal, - UPDATE_HEIGHTS, - UPDATE_TOTALS, UPDATE_TRANSACTION_MODAL_STATUS, - UpdateHeights, - UpdateTotals, UpdateTransactionModal } from '../types/actions.types'; -import { Notification, ParachainStatus, TransactionModalData } from '../types/util.types'; +import { Notification, TransactionModalData } from '../types/util.types'; export const isBridgeLoaded = (isLoaded = false): IsBridgeLoaded => ({ type: IS_BRIDGE_LOADED, @@ -44,23 +33,6 @@ export const isVaultClientLoaded = (isLoaded = false): IsVaultClientLoaded => ({ isLoaded }); -export const initGeneralDataAction = ( - totalWrappedTokenAmount: BitcoinAmount, - totalLockedCollateralTokenAmount: MonetaryAmount, - totalGovernanceTokenAmount: GovernanceTokenMonetaryAmount, - btcRelayHeight: number, - bitcoinHeight: number, - parachainStatus: ParachainStatus -): InitGeneralDataAction => ({ - type: INIT_GENERAL_DATA_ACTION, - btcRelayHeight, - bitcoinHeight, - totalWrappedTokenAmount, - totalLockedCollateralTokenAmount, - totalGovernanceTokenAmount, - parachainStatus -}); - export const showAccountModalAction = (showAccountModal: boolean): ShowAccountModal => ({ type: SHOW_ACCOUNT_MODAL, showAccountModal @@ -76,21 +48,6 @@ export const showBuyModal = (isBuyModalOpen: boolean): ShowBuyModal => ({ isBuyModalOpen }); -export const updateHeightsAction = (btcRelayHeight: number, bitcoinHeight: number): UpdateHeights => ({ - type: UPDATE_HEIGHTS, - btcRelayHeight, - bitcoinHeight -}); - -export const updateTotalsAction = ( - totalLockedCollateralTokenAmount: MonetaryAmount, - totalWrappedTokenAmount: BitcoinAmount -): UpdateTotals => ({ - type: UPDATE_TOTALS, - totalLockedCollateralTokenAmount, - totalWrappedTokenAmount -}); - export const addNotification = (accountAddress: string, notification: Notification): AddNotification => ({ type: ADD_NOTIFICATION, accountAddress, diff --git a/src/common/live-data/block-height-watcher.ts b/src/common/live-data/block-height-watcher.ts deleted file mode 100644 index efbd5528d3..0000000000 --- a/src/common/live-data/block-height-watcher.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Dispatch } from 'redux'; - -import { updateHeightsAction } from '../actions/general.actions'; -import { StoreState } from '../types/util.types'; - -export default async function fetchBtcRelayAndBitcoinHeight(dispatch: Dispatch, store: StoreState): Promise { - const state = store.getState(); - const { btcRelayHeight, bitcoinHeight, bridgeLoaded } = state.general; - if (!bridgeLoaded) return; - - try { - const latestBtcRelayHeight = Number(await window.bridge.btcRelay.getLatestBlockHeight()); - const latestBitcoinHeight = await window.bridge.electrsAPI.getLatestBlockHeight(); - - // update store only if there is a difference between the latest heights and current heights - if (btcRelayHeight !== latestBtcRelayHeight || bitcoinHeight !== latestBitcoinHeight) { - dispatch(updateHeightsAction(latestBtcRelayHeight, latestBitcoinHeight)); - } - } catch (error) { - console.log(error); - } -} diff --git a/src/common/live-data/live-data.ts b/src/common/live-data/live-data.ts deleted file mode 100644 index 94f9e3e2ef..0000000000 --- a/src/common/live-data/live-data.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Dispatch } from 'redux'; - -import { StoreState } from '@/common/types/util.types'; - -import fetchBtcRelayAndBitcoinHeight from './block-height-watcher'; -import fetchTotals from './totals-watcher'; - -// TODO: should use web sockets instead of infinite times of fetch -function startFetchingLiveData(dispatch: Dispatch, store: StoreState): void { - if (window.isFetchingActive) return; - window.isFetchingActive = true; - - // Fetch btc-relay height and bitcoin height - fetchBtcRelayAndBitcoinHeight(dispatch, store); - window.setInterval(() => fetchBtcRelayAndBitcoinHeight(dispatch, store), 60000); - - // Fetch totals - fetchTotals(dispatch, store); - window.setInterval(() => fetchTotals(dispatch, store), 60000); -} - -export default startFetchingLiveData; diff --git a/src/common/live-data/totals-watcher.ts b/src/common/live-data/totals-watcher.ts deleted file mode 100644 index fb46e83950..0000000000 --- a/src/common/live-data/totals-watcher.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Dispatch } from 'redux'; - -import { RELAY_CHAIN_NATIVE_TOKEN, WRAPPED_TOKEN } from '@/config/relay-chains'; - -import { updateTotalsAction } from '../actions/general.actions'; -import { StoreState } from '../types/util.types'; - -export default async function fetchTotals(dispatch: Dispatch, store: StoreState): Promise { - const state = store.getState(); - const { totalLockedCollateralTokenAmount, totalWrappedTokenAmount, bridgeLoaded } = state.general; - if (!bridgeLoaded) return; - - try { - const [latestTotalWrappedTokenAmount, latestTotalLockedCollateralTokenAmount] = await Promise.all([ - window.bridge.tokens.total(WRAPPED_TOKEN), - window.bridge.tokens.total(RELAY_CHAIN_NATIVE_TOKEN) - ]); - - // update store only if there is a difference between the latest totals and current totals - if ( - !totalWrappedTokenAmount.eq(latestTotalWrappedTokenAmount) || - !totalLockedCollateralTokenAmount.eq(latestTotalLockedCollateralTokenAmount) - ) { - dispatch(updateTotalsAction(latestTotalLockedCollateralTokenAmount, latestTotalWrappedTokenAmount)); - } - } catch (error) { - console.log(error); - } -} diff --git a/src/common/reducers/general.reducer.ts b/src/common/reducers/general.reducer.ts index a2ecfde3dd..0fc40771aa 100644 --- a/src/common/reducers/general.reducer.ts +++ b/src/common/reducers/general.reducer.ts @@ -1,23 +1,16 @@ -import { newMonetaryAmount } from '@interlay/interbtc-api'; -import { BitcoinAmount } from '@interlay/monetary-js'; - -import { RELAY_CHAIN_NATIVE_TOKEN } from '@/config/relay-chains'; import { TransactionStatus } from '@/hooks/transaction/types'; import { ADD_NOTIFICATION, GeneralActions, - INIT_GENERAL_DATA_ACTION, IS_BRIDGE_LOADED, IS_VAULT_CLIENT_LOADED, SHOW_ACCOUNT_MODAL, SHOW_BUY_MODAL, SHOW_SIGN_TERMS_MODAL, - UPDATE_HEIGHTS, - UPDATE_TOTALS, UPDATE_TRANSACTION_MODAL_STATUS } from '../types/actions.types'; -import { GeneralState, ParachainStatus } from '../types/util.types'; +import { GeneralState } from '../types/util.types'; const initialState = { bridgeLoaded: false, @@ -26,11 +19,6 @@ const initialState = { showAccountModal: false, isBuyModalOpen: false, isSignTermsModalOpen: false, - totalWrappedTokenAmount: BitcoinAmount.zero(), - totalLockedCollateralTokenAmount: newMonetaryAmount(0, RELAY_CHAIN_NATIVE_TOKEN), - btcRelayHeight: 0, - bitcoinHeight: 0, - parachainStatus: ParachainStatus.Loading, prices: { bitcoin: { usd: 0 }, relayChainNativeToken: { usd: 0 }, @@ -46,25 +34,8 @@ const initialState = { export const generalReducer = (state: GeneralState = initialState, action: GeneralActions): GeneralState => { switch (action.type) { - case UPDATE_TOTALS: - return { - ...state, - totalWrappedTokenAmount: action.totalWrappedTokenAmount, - totalLockedCollateralTokenAmount: action.totalLockedCollateralTokenAmount - }; - case UPDATE_HEIGHTS: - return { ...state, btcRelayHeight: action.btcRelayHeight, bitcoinHeight: action.bitcoinHeight }; case IS_BRIDGE_LOADED: return { ...state, bridgeLoaded: action.isLoaded }; - case INIT_GENERAL_DATA_ACTION: - return { - ...state, - totalLockedCollateralTokenAmount: action.totalLockedCollateralTokenAmount, - totalWrappedTokenAmount: action.totalWrappedTokenAmount, - btcRelayHeight: action.btcRelayHeight, - bitcoinHeight: action.bitcoinHeight, - parachainStatus: action.parachainStatus - }; case IS_VAULT_CLIENT_LOADED: return { ...state, vaultClientLoaded: action.isLoaded }; case SHOW_ACCOUNT_MODAL: diff --git a/src/common/types/actions.types.ts b/src/common/types/actions.types.ts index f4744b03ac..c5e2d32d13 100644 --- a/src/common/types/actions.types.ts +++ b/src/common/types/actions.types.ts @@ -1,41 +1,24 @@ import { CollateralCurrencyExt } from '@interlay/interbtc-api'; import { BitcoinAmount, MonetaryAmount } from '@interlay/monetary-js'; -import { GovernanceTokenMonetaryAmount } from '@/config/relay-chains'; - -import { Notification, ParachainStatus, StoreType, TransactionModalData } from './util.types'; +import { Notification, StoreType, TransactionModalData } from './util.types'; // GENERAL ACTIONS export const IS_BRIDGE_LOADED = 'IS_BRIDGE_LOADED'; export const IS_FAUCET_LOADED = 'IS_FAUCET_LOADED'; export const IS_VAULT_CLIENT_LOADED = 'IS_VAULT_CLIENT_LOADED'; export const INIT_STATE = 'INIT_STATE'; -export const INIT_GENERAL_DATA_ACTION = 'INIT_GENERAL_DATA_ACTION'; export const UPDATE_BALANCE_POLKA_BTC = 'UPDATE_BALANCE_POLKA_BTC'; export const UPDATE_WRAPPED_TOKEN_TRANSFERABLE_BALANCE = 'UPDATE_WRAPPED_TOKEN_TRANSFERABLE_BALANCE'; export const UPDATE_COLLATERAL_TOKEN_BALANCE = 'UPDATE_COLLATERAL_TOKEN_BALANCE'; export const UPDATE_COLLATERAL_TOKEN_TRANSFERABLE_BALANCE = 'UPDATE_COLLATERAL_TOKEN_TRANSFERABLE_BALANCE'; export const SHOW_ACCOUNT_MODAL = 'SHOW_ACCOUNT_MODAL'; export const SHOW_SIGN_TERMS_MODAL = 'SHOW_SIGN_TERMS_MODAL'; -export const UPDATE_HEIGHTS = 'UPDATE_HEIGHTS'; -export const UPDATE_TOTALS = 'UPDATE_TOTALS'; export const SHOW_BUY_MODAL = 'SHOW_BUY_MODAL'; export const ADD_NOTIFICATION = 'ADD_NOTIFICATION'; export const SHOW_TRANSACTION_MODAL = 'SHOW_TRANSACTION_MODAL'; export const UPDATE_TRANSACTION_MODAL_STATUS = 'UPDATE_TRANSACTION_MODAL_STATUS'; -export interface UpdateTotals { - type: typeof UPDATE_TOTALS; - totalLockedCollateralTokenAmount: MonetaryAmount; - totalWrappedTokenAmount: BitcoinAmount; -} - -export interface UpdateHeights { - type: typeof UPDATE_HEIGHTS; - btcRelayHeight: number; - bitcoinHeight: number; -} - export interface IsBridgeLoaded { type: typeof IS_BRIDGE_LOADED; isLoaded: boolean; @@ -56,16 +39,6 @@ export interface InitState { state: StoreType; } -export interface InitGeneralDataAction { - type: typeof INIT_GENERAL_DATA_ACTION; - totalWrappedTokenAmount: BitcoinAmount; - totalLockedCollateralTokenAmount: MonetaryAmount; - totalGovernanceTokenAmount: GovernanceTokenMonetaryAmount; - btcRelayHeight: number; - bitcoinHeight: number; - parachainStatus: ParachainStatus; -} - export interface UpdateBalancePolkaBTC { type: typeof UPDATE_BALANCE_POLKA_BTC; wrappedTokenBalance: BitcoinAmount; @@ -115,15 +88,12 @@ export interface UpdateTransactionModal { export type GeneralActions = | IsBridgeLoaded - | InitGeneralDataAction | IsVaultClientLoaded | UpdateBalancePolkaBTC | UpdateWrappedTokenTransferableBalance | UpdateCollateralTokenBalance | UpdateCollateralTokenTransferableBalance | ShowAccountModal - | UpdateHeights - | UpdateTotals | ShowBuyModal | ShowSignTermsModal | AddNotification diff --git a/src/common/types/util.types.ts b/src/common/types/util.types.ts index 2d5859ee13..3a75dc0511 100644 --- a/src/common/types/util.types.ts +++ b/src/common/types/util.types.ts @@ -1,5 +1,3 @@ -import { CollateralCurrencyExt } from '@interlay/interbtc-api'; -import { BitcoinAmount, MonetaryAmount } from '@interlay/monetary-js'; import { u256 } from '@polkadot/types/primitive'; import { CombinedState, Store } from 'redux'; @@ -40,13 +38,6 @@ export interface DashboardStatusUpdateInfo { forced: boolean; } -export enum ParachainStatus { - Loading, - Error, - Running, - Shutdown -} - export type Notification = { status: TransactionStatus; description: string; @@ -68,11 +59,6 @@ export type GeneralState = { showAccountModal: boolean; isSignTermsModalOpen: boolean; isBuyModalOpen: boolean; - totalWrappedTokenAmount: BitcoinAmount; - totalLockedCollateralTokenAmount: MonetaryAmount; - btcRelayHeight: number; - bitcoinHeight: number; - parachainStatus: ParachainStatus; notifications: Record; transactionModal: { isOpen: boolean; diff --git a/src/components/AuthCTA/AuthCTA.tsx b/src/components/AuthCTA/AuthCTA.tsx index a8a9dc53b6..f799fce007 100644 --- a/src/components/AuthCTA/AuthCTA.tsx +++ b/src/components/AuthCTA/AuthCTA.tsx @@ -1,4 +1,4 @@ -import { forwardRef } from 'react'; +import { forwardRef, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; @@ -7,24 +7,33 @@ import { CTA, CTAProps } from '@/component-library'; import { SIGNER_API_URL } from '@/constants'; import { useSignMessage } from '@/hooks/use-sign-message'; import { useSubstrateSecureState } from '@/lib/substrate'; +import { useGetParachainStatus } from '@/utils/hooks/api/system/use-get-parachain-status'; enum AuthStatus { UNAUTH, AUTH, - UNSIGNED + UNSIGNED, + BLOCKED } const useAuthCTAProps = (props: AuthCTAProps): AuthCTAProps => { const { t } = useTranslation(); const { hasSignature, buttonProps } = useSignMessage(); + const { data: parachainStatus } = useGetParachainStatus(); const { selectedAccount } = useSubstrateSecureState(); - const status = selectedAccount - ? !SIGNER_API_URL || hasSignature - ? AuthStatus.AUTH - : AuthStatus.UNSIGNED - : AuthStatus.UNAUTH; + const status = useMemo(() => { + if (!selectedAccount) { + return AuthStatus.UNAUTH; + } + + if (!parachainStatus?.isRunning) { + return AuthStatus.BLOCKED; + } + + return !SIGNER_API_URL || hasSignature ? AuthStatus.AUTH : AuthStatus.UNSIGNED; + }, [hasSignature, parachainStatus, selectedAccount]); const dispatch = useDispatch(); @@ -45,6 +54,13 @@ const useAuthCTAProps = (props: AuthCTAProps): AuthCTAProps => { disabled: false, children: t('sign_t&cs') }; + case AuthStatus.BLOCKED: + return { + ...buttonProps, + type: 'button', + disabled: true, + children + }; case AuthStatus.UNAUTH: default: return { diff --git a/src/pages/BTC/BTCOverview/components/LegacyBurnForm/LegacyBurnForm.tsx b/src/pages/BTC/BTCOverview/components/LegacyBurnForm/LegacyBurnForm.tsx index 0e579e16cf..a847c76810 100644 --- a/src/pages/BTC/BTCOverview/components/LegacyBurnForm/LegacyBurnForm.tsx +++ b/src/pages/BTC/BTCOverview/components/LegacyBurnForm/LegacyBurnForm.tsx @@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; -import { ParachainStatus, StoreType } from '@/common/types/util.types'; +import { StoreType } from '@/common/types/util.types'; import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils'; import { CoinIcon } from '@/component-library'; import { AuthCTA } from '@/components'; @@ -47,7 +47,7 @@ const LegacyBurnForm = (): JSX.Element | null => { const [status, setStatus] = React.useState(STATUSES.IDLE); const handleError = useErrorHandler(); - const { bridgeLoaded, parachainStatus } = useSelector((state: StoreType) => state.general); + const { bridgeLoaded } = useSelector((state: StoreType) => state.general); const { data: balances } = useGetBalances(); const { data: collateralCurrencies } = useGetCollateralCurrencies(bridgeLoaded); @@ -278,13 +278,7 @@ const LegacyBurnForm = (): JSX.Element | null => { )} /> - + {t('burn')} diff --git a/src/pages/Dashboard/cards/BTCRelayCard/index.tsx b/src/pages/Dashboard/cards/BTCRelayCard/index.tsx index 0fe8b4ba7a..9278c2df1d 100644 --- a/src/pages/Dashboard/cards/BTCRelayCard/index.tsx +++ b/src/pages/Dashboard/cards/BTCRelayCard/index.tsx @@ -1,12 +1,11 @@ import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; -import { StoreType } from '@/common/types/util.types'; import { formatNumber } from '@/common/utils/utils'; import Ring64, { Ring64Title, Ring64Value } from '@/legacy-components/Ring64'; import { PAGES } from '@/utils/constants/links'; import { getColorShade } from '@/utils/helpers/colors'; +import { useGetBtcBlockHeight } from '@/utils/hooks/api/use-get-btc-block-height'; import Stats, { StatsDd, StatsDt, StatsRouterLink } from '../../Stats'; import DashboardCard from '../DashboardCard'; @@ -24,15 +23,10 @@ interface Props { const BTCRelayCard = ({ hasLinks }: Props): JSX.Element => { const { t } = useTranslation(); // TODO: compute status using blockstream data - const { btcRelayHeight, bitcoinHeight } = useSelector((state: StoreType) => state.general); + const { data: blockHeight } = useGetBtcBlockHeight(); + + const state = blockHeight ? (blockHeight.isOutdated ? Status.Failure : Status.Ok) : Status.Loading; - const outdatedRelayThreshold = 12; - const state = - bitcoinHeight === 0 - ? Status.Loading - : bitcoinHeight - btcRelayHeight >= outdatedRelayThreshold - ? Status.Failure - : Status.Ok; const statusText = state === Status.Loading ? t('loading') @@ -81,7 +75,9 @@ const BTCRelayCard = ({ hasLinks }: Props): JSX.Element => { > {graphText} - {t('dashboard.relay.block_number', { number: formatNumber(btcRelayHeight) })} + + {t('dashboard.relay.block_number', { number: formatNumber(blockHeight?.relay || 0) })} + ); diff --git a/src/pages/Dashboard/cards/ParachainSecurityCard/index.tsx b/src/pages/Dashboard/cards/ParachainSecurityCard/index.tsx index c77c05b822..c04d5f250e 100644 --- a/src/pages/Dashboard/cards/ParachainSecurityCard/index.tsx +++ b/src/pages/Dashboard/cards/ParachainSecurityCard/index.tsx @@ -1,12 +1,11 @@ import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; -import { ParachainStatus, StoreType } from '@/common/types/util.types'; import { WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains'; import Ring64, { Ring64Title, Ring64Value } from '@/legacy-components/Ring64'; import { PAGES } from '@/utils/constants/links'; import { getColorShade } from '@/utils/helpers/colors'; +import { useGetParachainStatus } from '@/utils/hooks/api/system/use-get-parachain-status'; import Stats, { StatsRouterLink } from '../../Stats'; import DashboardCard from '../DashboardCard'; @@ -17,20 +16,23 @@ interface Props { const ParachainSecurityCard = ({ hasLinks }: Props): JSX.Element => { const { t } = useTranslation(); - const { parachainStatus } = useSelector((state: StoreType) => state.general); + + const { data: parachainStatus, isLoading } = useGetParachainStatus(); const getParachainStatusText = () => { - switch (parachainStatus) { - case ParachainStatus.Running: - return t('dashboard.parachain.secure'); - case ParachainStatus.Loading: - return t('loading'); - case ParachainStatus.Error: - case ParachainStatus.Shutdown: - return t('dashboard.parachain.halted'); - default: - return t('no_data'); + if (!parachainStatus && !isLoading) { + return t('no_data'); + } + + if (!parachainStatus || isLoading) { + return t('loading'); } + + if (parachainStatus.isError || parachainStatus.isShutdown) { + return t('dashboard.parachain.halted'); + } + + return t('dashboard.parachain.secure'); }; return ( @@ -41,21 +43,19 @@ const ParachainSecurityCard = ({ hasLinks }: Props): JSX.Element => { diff --git a/src/pages/Dashboard/sub-pages/BTCRelay/BlockstreamCard/index.tsx b/src/pages/Dashboard/sub-pages/BTCRelay/BlockstreamCard/index.tsx index e6b5b9df89..073b166e10 100644 --- a/src/pages/Dashboard/sub-pages/BTCRelay/BlockstreamCard/index.tsx +++ b/src/pages/Dashboard/sub-pages/BTCRelay/BlockstreamCard/index.tsx @@ -12,13 +12,15 @@ import ExternalLink from '@/legacy-components/ExternalLink'; import Ring64, { Ring64Title, Ring64Value } from '@/legacy-components/Ring64'; import genericFetcher, { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher'; import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names'; +import { useGetBtcBlockHeight } from '@/utils/hooks/api/use-get-btc-block-height'; import DashboardCard from '../../../cards/DashboardCard'; import Stats from '../../../Stats'; const BlockstreamCard = (): JSX.Element => { const { t } = useTranslation(); - const { bridgeLoaded, bitcoinHeight } = useSelector((state: StoreType) => state.general); + const { bridgeLoaded } = useSelector((state: StoreType) => state.general); + const { data: blockHeight } = useGetBtcBlockHeight(); const { isIdle: blockstreamTipIdle, @@ -65,7 +67,9 @@ const BlockstreamCard = (): JSX.Element => { > {t('blockstream')} - {t('dashboard.relay.block_number', { number: formatNumber(bitcoinHeight) })} + + {t('dashboard.relay.block_number', { number: formatNumber(blockHeight?.bitcoin || 0) })} + ); diff --git a/src/pages/Dashboard/sub-pages/Home/WrappedTokenCard/index.tsx b/src/pages/Dashboard/sub-pages/Home/WrappedTokenCard/index.tsx index 3d0c2022e5..3e02b5ae4c 100644 --- a/src/pages/Dashboard/sub-pages/Home/WrappedTokenCard/index.tsx +++ b/src/pages/Dashboard/sub-pages/Home/WrappedTokenCard/index.tsx @@ -1,22 +1,22 @@ +import { newMonetaryAmount } from '@interlay/interbtc-api'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; -import { StoreType } from '@/common/types/util.types'; import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils'; -import { WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains'; +import { WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains'; import { useGetPrices } from '@/hooks/api/use-get-prices'; import IssuedChart from '@/pages/Dashboard/IssuedChart'; import { ForeignAssetIdLiteral } from '@/types/currency'; import { PAGES } from '@/utils/constants/links'; import { getTokenPrice } from '@/utils/helpers/prices'; +import { useGetTotalLockedTokens } from '@/utils/hooks/api/tokens/use-get-total-locked-tokens'; import DashboardCard from '../../../cards/DashboardCard'; import Stats, { StatsDd, StatsDt, StatsRouterLink } from '../../../Stats'; const WrappedTokenCard = (): JSX.Element => { - const { totalWrappedTokenAmount } = useSelector((state: StoreType) => state.general); const { t } = useTranslation(); const prices = useGetPrices(); + const { data: totalLockedData } = useGetTotalLockedTokens(); const renderContent = () => { return ( @@ -27,13 +27,13 @@ const WrappedTokenCard = (): JSX.Element => { {t('dashboard.issue.issued')} {t('dashboard.issue.total_interbtc', { - amount: totalWrappedTokenAmount.toHuman(8), + amount: totalLockedData?.wrapped.toHuman(8) || 0, wrappedTokenSymbol: WRAPPED_TOKEN_SYMBOL })} {displayMonetaryAmountInUSDFormat( - totalWrappedTokenAmount, + totalLockedData?.wrapped || newMonetaryAmount(0, WRAPPED_TOKEN), getTokenPrice(prices, ForeignAssetIdLiteral.BTC)?.usd )} diff --git a/src/pages/Dashboard/sub-pages/IssueRequests/UpperContent/index.tsx b/src/pages/Dashboard/sub-pages/IssueRequests/UpperContent/index.tsx index 44e994b3ef..da2afe7c75 100644 --- a/src/pages/Dashboard/sub-pages/IssueRequests/UpperContent/index.tsx +++ b/src/pages/Dashboard/sub-pages/IssueRequests/UpperContent/index.tsx @@ -1,12 +1,11 @@ +import { newMonetaryAmount } from '@interlay/interbtc-api'; import clsx from 'clsx'; import { useErrorHandler, withErrorBoundary } from 'react-error-boundary'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; -import { useSelector } from 'react-redux'; -import { StoreType } from '@/common/types/util.types'; import { displayMonetaryAmountInUSDFormat, formatNumber } from '@/common/utils/utils'; -import { WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains'; +import { WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains'; import { useGetPrices } from '@/hooks/api/use-get-prices'; import ErrorFallback from '@/legacy-components/ErrorFallback'; import Panel from '@/legacy-components/Panel'; @@ -17,11 +16,12 @@ import { ForeignAssetIdLiteral } from '@/types/currency'; import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names'; import { getColorShade } from '@/utils/helpers/colors'; import { getTokenPrice } from '@/utils/helpers/prices'; +import { useGetTotalLockedTokens } from '@/utils/hooks/api/tokens/use-get-total-locked-tokens'; import Stats, { StatsDd, StatsDt } from '../../../Stats'; const UpperContent = (): JSX.Element => { - const { totalWrappedTokenAmount } = useSelector((state: StoreType) => state.general); + const { data: totalLockedData } = useGetTotalLockedTokens(); const { t } = useTranslation(); const prices = useGetPrices(); @@ -61,13 +61,13 @@ const UpperContent = (): JSX.Element => { {t('dashboard.issue.total_interbtc', { - amount: totalWrappedTokenAmount.toHuman(8), + amount: totalLockedData?.wrapped.toHuman(8) || 0, wrappedTokenSymbol: WRAPPED_TOKEN_SYMBOL })} {displayMonetaryAmountInUSDFormat( - totalWrappedTokenAmount, + totalLockedData?.relay || newMonetaryAmount(0, WRAPPED_TOKEN), getTokenPrice(prices, ForeignAssetIdLiteral.BTC)?.usd )} diff --git a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx index 0abf1595a8..6b7475556b 100644 --- a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx +++ b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx @@ -14,11 +14,11 @@ import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { ReactComponent as BitcoinLogoIcon } from '@/assets/img/bitcoin-logo.svg'; -import { ParachainStatus, StoreType } from '@/common/types/util.types'; +import { StoreType } from '@/common/types/util.types'; import { displayMonetaryAmount, displayMonetaryAmountInUSDFormat } from '@/common/utils/utils'; import { Modal, ModalBody, ModalHeader } from '@/component-library'; +import { AuthCTA } from '@/components'; import { - BLOCKS_BEHIND_LIMIT, DEFAULT_ISSUE_BRIDGE_FEE_RATE, DEFAULT_ISSUE_DUST_AMOUNT, DEFAULT_ISSUE_GRIEFING_COLLATERAL_RATE @@ -37,16 +37,15 @@ import { Transaction, useTransaction } from '@/hooks/transaction'; import useAccountId from '@/hooks/use-account-id'; import Hr2 from '@/legacy-components/hrs/Hr2'; import PriceInfo from '@/legacy-components/PriceInfo'; -import SubmitButton from '@/legacy-components/SubmitButton'; import TokenField from '@/legacy-components/TokenField'; import InformationTooltip from '@/legacy-components/tooltips/InformationTooltip'; import InterlayButtonBase from '@/legacy-components/UI/InterlayButtonBase'; -import { useSubstrateSecureState } from '@/lib/substrate'; import { ForeignAssetIdLiteral } from '@/types/currency'; import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names'; import STATUSES from '@/utils/constants/statuses'; import { getExchangeRate } from '@/utils/helpers/oracle'; import { getTokenPrice } from '@/utils/helpers/prices'; +import { useGetBtcBlockHeight } from '@/utils/hooks/api/use-get-btc-block-height'; import SubmittedIssueRequestModal from '../SubmittedIssueRequestModal'; @@ -94,12 +93,11 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro const { t } = useTranslation(); const prices = useGetPrices(); + const { data: blockHeight } = useGetBtcBlockHeight(); + const handleError = useErrorHandler(); - const { selectedAccount } = useSubstrateSecureState(); - const { bridgeLoaded, bitcoinHeight, btcRelayHeight, parachainStatus } = useSelector( - (state: StoreType) => state.general - ); + const { bridgeLoaded } = useSelector((state: StoreType) => state.general); const { data: balances, isLoading: isBalancesLoading } = useGetBalances(); @@ -179,21 +177,25 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens(); - const result = await transaction.executeAsync( - wrappedTokenAmount, - vaultAccountId, - collateralToken, - false, // default - vaults - ); - - const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result.data); - - // TODO: handle issue aggregation - const issueRequest = issueRequests[0]; - handleSubmittedRequestModalOpen(issueRequest); - setSubmitStatus(STATUSES.RESOLVED); - onClose(); + try { + const result = await transaction.executeAsync( + wrappedTokenAmount, + vaultAccountId, + collateralToken, + false, // default + vaults + ); + + const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result.data); + + // TODO: handle issue aggregation + const issueRequest = issueRequests[0]; + handleSubmittedRequestModalOpen(issueRequest); + setSubmitStatus(STATUSES.RESOLVED); + onClose(); + } catch (e) { + setSubmitStatus(STATUSES.IDLE); + } }; const validateForm = (value: string): string | undefined => { @@ -219,7 +221,7 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro }); } - if (bitcoinHeight - btcRelayHeight > BLOCKS_BEHIND_LIMIT) { + if (blockHeight?.isOutdated) { return t('issue_page.error_more_than_6_blocks_behind', { wrappedTokenSymbol: WRAPPED_TOKEN_SYMBOL }); @@ -398,15 +400,9 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro getTokenPrice(prices, ForeignAssetIdLiteral.BTC)?.usd )} /> - + {t('confirm')} - + diff --git a/src/utils/hooks/api/system/use-get-parachain-status.tsx b/src/utils/hooks/api/system/use-get-parachain-status.tsx new file mode 100644 index 0000000000..9a3f54fe6c --- /dev/null +++ b/src/utils/hooks/api/system/use-get-parachain-status.tsx @@ -0,0 +1,37 @@ +import { useErrorHandler } from 'react-error-boundary'; +import { useQuery, UseQueryResult } from 'react-query'; + +import { REFETCH_INTERVAL } from '@/utils/constants/api'; + +type ParachainStatusData = { + isRunning: boolean; + isShutdown: boolean; + isError: boolean; +}; + +type UseGetParachainStatusResult = UseQueryResult; + +const getStatus = async (): Promise => { + const statusCode = await window.bridge.system.getStatusCode(); + + return { + isRunning: Boolean(statusCode.isRunning), + isError: Boolean(statusCode.isError), + isShutdown: Boolean(statusCode.isShutdown) + }; +}; + +const useGetParachainStatus = (): UseGetParachainStatusResult => { + const queryResult = useQuery({ + queryKey: 'parachain-status', + queryFn: getStatus, + refetchInterval: REFETCH_INTERVAL.MINUTE + }); + + useErrorHandler(queryResult.error); + + return queryResult; +}; + +export { useGetParachainStatus }; +export type { ParachainStatusData, UseGetParachainStatusResult }; diff --git a/src/utils/hooks/api/tokens/use-get-total-locked-tokens.tsx b/src/utils/hooks/api/tokens/use-get-total-locked-tokens.tsx new file mode 100644 index 0000000000..45d77c0159 --- /dev/null +++ b/src/utils/hooks/api/tokens/use-get-total-locked-tokens.tsx @@ -0,0 +1,40 @@ +import { CurrencyExt } from '@interlay/interbtc-api'; +import { MonetaryAmount } from '@interlay/monetary-js'; +import { useErrorHandler } from 'react-error-boundary'; +import { useQuery, UseQueryResult } from 'react-query'; + +import { GOVERNANCE_TOKEN, RELAY_CHAIN_NATIVE_TOKEN, WRAPPED_TOKEN } from '@/config/relay-chains'; +import { REFETCH_INTERVAL } from '@/utils/constants/api'; + +type TotalLockedTokensData = { + wrapped: MonetaryAmount; + relay: MonetaryAmount; + governance: MonetaryAmount; +}; + +type UseGetTotalTokensResult = UseQueryResult; + +const getTotalLocked = async (): Promise => { + const [wrapped, relay, governance] = await Promise.all([ + window.bridge.tokens.total(WRAPPED_TOKEN), + window.bridge.tokens.total(RELAY_CHAIN_NATIVE_TOKEN), + window.bridge.tokens.total(GOVERNANCE_TOKEN) + ]); + + return { wrapped, relay, governance }; +}; + +const useGetTotalLockedTokens = (): UseGetTotalTokensResult => { + const queryResult = useQuery({ + queryKey: 'total-locked', + queryFn: getTotalLocked, + refetchInterval: REFETCH_INTERVAL.BLOCK + }); + + useErrorHandler(queryResult.error); + + return queryResult; +}; + +export { useGetTotalLockedTokens }; +export type { TotalLockedTokensData, UseGetTotalTokensResult }; diff --git a/src/utils/hooks/api/use-get-btc-block-height.tsx b/src/utils/hooks/api/use-get-btc-block-height.tsx new file mode 100644 index 0000000000..32d242a715 --- /dev/null +++ b/src/utils/hooks/api/use-get-btc-block-height.tsx @@ -0,0 +1,39 @@ +import { useErrorHandler } from 'react-error-boundary'; +import { useQuery, UseQueryResult } from 'react-query'; + +import { BLOCKS_BEHIND_LIMIT } from '@/config/parachain'; +import { REFETCH_INTERVAL } from '@/utils/constants/api'; + +type BtcBlockHeightData = { + relay: number; + bitcoin: number; + isOutdated: boolean; +}; + +type UseGetBTCBlockHeightResult = UseQueryResult; + +const getBtcBlockHeight = async (): Promise => { + const [relay, bitcoin] = await Promise.all([ + window.bridge.btcRelay.getLatestBlockHeight(), + window.bridge.electrsAPI.getLatestBlockHeight() + ]); + + const isOutdated = bitcoin - relay > BLOCKS_BEHIND_LIMIT; + + return { relay, bitcoin, isOutdated }; +}; + +const useGetBtcBlockHeight = (): UseGetBTCBlockHeightResult => { + const queryResult = useQuery({ + queryKey: 'btc-block-height', + queryFn: getBtcBlockHeight, + refetchInterval: REFETCH_INTERVAL.MINUTE + }); + + useErrorHandler(queryResult.error); + + return queryResult; +}; + +export { useGetBtcBlockHeight }; +export type { BtcBlockHeightData, UseGetBTCBlockHeightResult };