diff --git a/src/App.tsx b/src/App.tsx index 00eb554d..6e6f2293 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,8 +22,7 @@ import { Token } from "./enums" import { usePosthog } from "./hooks/posthog" import { useSentry } from "./hooks/sentry" import { - useSubscribeToOptimisticMintingFinalizedEvent, - useSubscribeToOptimisticMintingRequestedEvent, + useSubscribeToOptimisticMintingFinalizedEventForCurrentAccount, useSubscribeToRedemptionRequestedEvent, } from "./hooks/tbtc" import { useSubscribeToDepositRevealedEvent } from "./hooks/tbtc/useSubsribeToDepositRevealedEvent" @@ -39,8 +38,7 @@ import { isSameETHAddress } from "./web3/utils" const Web3EventHandlerComponent = () => { useSubscribeToERC20TransferEvent(Token.TBTC) useSubscribeToDepositRevealedEvent() - useSubscribeToOptimisticMintingFinalizedEvent() - useSubscribeToOptimisticMintingRequestedEvent() + useSubscribeToOptimisticMintingFinalizedEventForCurrentAccount() useSubscribeToRedemptionRequestedEvent() return <> diff --git a/src/hooks/tbtc/index.ts b/src/hooks/tbtc/index.ts index df6fad68..521e65d6 100644 --- a/src/hooks/tbtc/index.ts +++ b/src/hooks/tbtc/index.ts @@ -1,4 +1,3 @@ -export * from "./useFetchDepositDetails" export * from "./useFetchRecentDeposits" export * from "./useFetchTBTCMetrics" export * from "./useRedemptionEstimatedFees" diff --git a/src/hooks/tbtc/useFetchDepositDetails.ts b/src/hooks/tbtc/useFetchDepositDetails.ts deleted file mode 100644 index ebfd650f..00000000 --- a/src/hooks/tbtc/useFetchDepositDetails.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { useEffect, useState } from "react" -import { useThreshold } from "../../contexts/ThresholdContext" -import { getContractPastEvents, isEmptyOrZeroAddress } from "../../web3/utils" -import { reverseTxHash } from "../../threshold-ts/utils" -import { useSubscribeToOptimisticMintingEvents } from "../useSubscribeToOptimisticMintingEvents" -import { useTbtcState } from "../useTbtcState" - -export type DepositData = { - depositRevealedTxHash: string - amount: string - btcTxHash: string - optimisticMintingRequestedTxHash: string - optimisticMintingFinalizedTxHash: string - confirmations: number - requiredConfirmations: number - treasuryFee: string - optimisticMintFee: string -} - -const DEFAULT_DEPOSIT_DATA: DepositData = { - depositRevealedTxHash: "", - amount: "0", - btcTxHash: "", - optimisticMintingRequestedTxHash: "", - optimisticMintingFinalizedTxHash: "", - confirmations: 0, - requiredConfirmations: 0, - treasuryFee: "", - optimisticMintFee: "", -} - -export const useFetchDepositDetails = (depositKey: string | undefined) => { - const threshold = useThreshold() - const [isFetching, setIsFetching] = useState(false) - const [error, setError] = useState("") - const [depositData, setDepositData] = useState(DEFAULT_DEPOSIT_DATA) - const { optimisticMintingFinalizedTxHash, optimisticMintingRequestedTxHash } = - useSubscribeToOptimisticMintingEvents(depositKey) - const { txConfirmations } = useTbtcState() - - useEffect(() => { - const fetch = async () => { - setIsFetching(true) - try { - const { depositor } = (await threshold.tbtc.bridgeContract.deposits( - depositKey - )) as { - depositor: string - } - if (isEmptyOrZeroAddress(depositor)) { - throw new Error("Deposit not found...") - } - - const revealedDeposits = await threshold.tbtc.findAllRevealedDeposits( - depositor - ) - - const deposit = revealedDeposits.find( - (deposit) => deposit.depositKey === depositKey - ) - - if (!deposit) { - throw new Error( - "Could not find `DepositRevealed` event by given deposit key." - ) - } - - const optimisticMintingRequestedEvents = await getContractPastEvents( - threshold.tbtc.vaultContract, - { - eventName: "OptimisticMintingRequested", - fromBlock: deposit.blockNumber, - filterParams: [undefined, depositKey, depositor], - } - ) - - const optimisticMintingFinalizedEvents = await getContractPastEvents( - threshold.tbtc.vaultContract, - { - eventName: "OptimisticMintingFinalized", - fromBlock: deposit.blockNumber, - filterParams: [undefined, depositKey, depositor], - } - ) - - const btcTxHash = reverseTxHash(deposit.fundingTxHash) - const confirmations = - (await threshold.tbtc.getTransactionConfirmations(btcTxHash)) ?? - txConfirmations - const requiredConfirmations = - threshold.tbtc.minimumNumberOfConfirmationsNeeded(deposit.amount) - - const { treasuryFee, optimisticMintFee, amountToMint } = - await threshold.tbtc.getEstimatedDepositFees(deposit.amount) - - setDepositData({ - btcTxHash: btcTxHash.toString(), - depositRevealedTxHash: deposit.txHash, - amount: amountToMint, - treasuryFee, - optimisticMintFee, - optimisticMintingRequestedTxHash: - optimisticMintingRequestedEvents[0]?.transactionHash, - optimisticMintingFinalizedTxHash: - optimisticMintingFinalizedEvents[0]?.transactionHash, - requiredConfirmations, - confirmations, - }) - } catch (error) { - setError((error as Error).toString()) - } - setIsFetching(false) - } - - if (depositKey) { - fetch() - } - }, [depositKey, threshold, reverseTxHash]) - - useEffect(() => { - setDepositData( - (prevState) => - ({ - ...prevState, - optimisticMintingRequestedTxHash, - optimisticMintingFinalizedTxHash, - } as DepositData) - ) - }, [optimisticMintingFinalizedTxHash, optimisticMintingRequestedTxHash]) - - useEffect(() => { - setDepositData((prevState) => ({ - ...prevState, - confirmations: txConfirmations, - })) - }, [txConfirmations]) - - return { isFetching, data: depositData, error } -} diff --git a/src/hooks/tbtc/useSubscribeToOptimisticMintingFinalizedEvent.ts b/src/hooks/tbtc/useSubscribeToOptimisticMintingFinalizedEvent.ts index c3523407..fe9c9143 100644 --- a/src/hooks/tbtc/useSubscribeToOptimisticMintingFinalizedEvent.ts +++ b/src/hooks/tbtc/useSubscribeToOptimisticMintingFinalizedEvent.ts @@ -5,7 +5,6 @@ import { useAppDispatch } from "../store" import { useTBTCVaultContract } from "./useTBTCVaultContract" import { useSubscribeToContractEvent } from "../../web3/hooks" import { useTbtcState } from "../useTbtcState" -import { MintingStep } from "../../types/tbtc" import { useThreshold } from "../../contexts/ThresholdContext" type OptimisticMintingFinalizedEventCallback = ( @@ -33,38 +32,64 @@ export const useSubscribeToOptimisticMintingFinalizedEventBase = ( ) } -export const useSubscribeToOptimisticMintingFinalizedEvent = () => { - const threshold = useThreshold() - const { updateState, utxo, mintingStep } = useTbtcState() - const dispatch = useAppDispatch() - const { account } = useWeb3React() +/** + * Subscribes to optimistic minting finalized events based on the deposit key. + * This is used to update the deposit details data state needed for Deposit + * Details page. + * @param {string} depositKey String representing the deposit key. + */ +export const useSubscribeToOptimisticMintingFinalizedEvent = ( + depositKey?: string +) => { + const { updateDepositDetailsDataState } = useTbtcState() useSubscribeToOptimisticMintingFinalizedEventBase( - (...args) => { - const [, depositKey, , , event] = args - const depositKeyFromEvent = depositKey.toHexString() - const depositKeyFromUTXO = utxo - ? threshold.tbtc.buildDepositKey( - utxo.transactionHash.toString(), - utxo.outputIndex, - "big-endian" - ) - : "" - - if ( - mintingStep === MintingStep.MintingSuccess && - depositKeyFromEvent === depositKeyFromUTXO - ) { - updateState("optimisticMintingFinalizedTxHash", event.transactionHash) + (minter, depositKeyEventParam, depositor, optimisticMintingDebt, event) => { + const depositKeyFromEvent = depositKeyEventParam.toHexString() + if (depositKeyFromEvent === depositKey) { + updateDepositDetailsDataState( + "optimisticMintingFinalizedTxHashFromEvent", + event.transactionHash + ) } - - dispatch( - tbtcSlice.actions.optimisticMintingFinalized({ - depositKey: depositKeyFromEvent, - txHash: event.transactionHash, - }) - ) }, - [null, null, account] + undefined, + true ) } + +/** + * Subscribes to optimistic minting finalized events based on the currently + * connected account. This is used to update the bridge activity state (for the + * current account) when the optimistic minting is finalized. + */ +export const useSubscribeToOptimisticMintingFinalizedEventForCurrentAccount = + () => { + const threshold = useThreshold() + const { utxo } = useTbtcState() + const dispatch = useAppDispatch() + const { account } = useWeb3React() + + useSubscribeToOptimisticMintingFinalizedEventBase( + (...args) => { + const [, depositKey, , , event] = args + const depositKeyFromEvent = depositKey.toHexString() + const depositKeyFromUTXO = utxo + ? threshold.tbtc.buildDepositKey( + utxo.transactionHash.toString(), + utxo.outputIndex, + "big-endian" + ) + : "" + + // Updates bridge activity state if the deposit key from the event matches + dispatch( + tbtcSlice.actions.optimisticMintingFinalized({ + depositKey: depositKeyFromEvent, + txHash: event.transactionHash, + }) + ) + }, + [null, null, account] + ) + } diff --git a/src/hooks/tbtc/useSubscribeToOptimisticMintingRequestedEvent.ts b/src/hooks/tbtc/useSubscribeToOptimisticMintingRequestedEvent.ts index c4bb78c3..2e77d0c2 100644 --- a/src/hooks/tbtc/useSubscribeToOptimisticMintingRequestedEvent.ts +++ b/src/hooks/tbtc/useSubscribeToOptimisticMintingRequestedEvent.ts @@ -16,7 +16,7 @@ type OptimisticMintingRequestedEventCallback = ( event: Event ) => void -export const useSubscribeToOptimisticMintingRequestedEventBase = ( +const useSubscribeToOptimisticMintingRequestedEventBase = ( callback: OptimisticMintingRequestedEventCallback, filterParams?: any[string], shouldSubscribeIfUserNotConnected: boolean = false @@ -33,37 +33,36 @@ export const useSubscribeToOptimisticMintingRequestedEventBase = ( ) } -export const useSubscribeToOptimisticMintingRequestedEvent = () => { - const threshold = useThreshold() - const { updateState, utxo, mintingStep } = useTbtcState() - const { account } = useWeb3React() +/** + * Subscribes to optimistic minting requested events based on the deposit key. + * This is used to update the deposit details data state needed for Deposit + * Details page. + * @param {string} depositKey String representing the deposit key. + */ +export const useSubscribeToOptimisticMintingRequestedEvent = ( + depositKey?: string +) => { + const { updateDepositDetailsDataState } = useTbtcState() useSubscribeToOptimisticMintingRequestedEventBase( ( - minter: string, - depositKey: BigNumber, - depositor: string, - amount: BigNumber, - fundingTxHash: unknown, - fundingOutputIndex: BigNumber, - event: Event + minter, + depositKeyEventParam, + depositor, + amount, + fundingTxHash, + fundingOutputIndex, + event ) => { - const depositKeyFromEvent = depositKey.toHexString() - const depositKeyFromUTXO = utxo - ? threshold.tbtc.buildDepositKey( - utxo.transactionHash.toString(), - utxo.outputIndex, - "big-endian" - ) - : "" - - if ( - mintingStep === MintingStep.MintingSuccess && - depositKeyFromEvent === depositKeyFromUTXO - ) { - updateState("optimisticMintingRequestedTxHash", event.transactionHash) + const depositKeyFromEvent = depositKeyEventParam.toHexString() + if (depositKeyFromEvent === depositKey) { + updateDepositDetailsDataState( + "optimisticMintingRequestedTxHashFromEvent", + event.transactionHash + ) } }, - [null, null, account] + undefined, + true ) } diff --git a/src/hooks/useSubscribeToOptimisticMintingEvents.ts b/src/hooks/useSubscribeToOptimisticMintingEvents.ts deleted file mode 100644 index a69747ec..00000000 --- a/src/hooks/useSubscribeToOptimisticMintingEvents.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useState } from "react" -import { - useSubscribeToOptimisticMintingRequestedEventBase, - useSubscribeToOptimisticMintingFinalizedEventBase, -} from "./tbtc" - -export const useSubscribeToOptimisticMintingEvents = (depositKey?: string) => { - const [ - optimisticMintingRequestedTxHash, - setOptimisticMintingRequestedTxHash, - ] = useState("") - const [ - optimisticMintingFinalizedTxHash, - setOptimisticMintingFinalizedTxHashTxHash, - ] = useState("") - - useSubscribeToOptimisticMintingRequestedEventBase( - ( - minter, - depositKeyEventParam, - depositor, - amount, - fundingTxHash, - fundingOutputIndex, - event - ) => { - const depositKeyFromEvent = depositKeyEventParam.toHexString() - if (depositKeyFromEvent === depositKey) { - setOptimisticMintingRequestedTxHash(event.transactionHash) - } - }, - undefined, - true - ) - - useSubscribeToOptimisticMintingFinalizedEventBase( - (minter, depositKeyEventParam, depositor, optimisticMintingDebt, event) => { - const depositKeyFromEvent = depositKeyEventParam.toHexString() - if (depositKeyFromEvent === depositKey) { - setOptimisticMintingFinalizedTxHashTxHash(event.transactionHash) - } - }, - undefined, - true - ) - - return { optimisticMintingRequestedTxHash, optimisticMintingFinalizedTxHash } -} diff --git a/src/hooks/useTbtcState.ts b/src/hooks/useTbtcState.ts index c3d018a2..81b9516a 100644 --- a/src/hooks/useTbtcState.ts +++ b/src/hooks/useTbtcState.ts @@ -1,8 +1,16 @@ import { useCallback } from "react" import { RootState } from "../store" -import { updateState as updateStateAction } from "../store/tbtc" +import { + updateState as updateStateAction, + updateDepositDetailsDataState as updateDepositDetailsDataStateAction, +} from "../store/tbtc" import { useAppDispatch, useAppSelector } from "./store" -import { MintingStep, TbtcStateKey, UseTbtcState } from "../types/tbtc" +import { + DepositDetailsDataStateKey, + MintingStep, + TbtcStateKey, + UseTbtcState, +} from "../types/tbtc" export const useTbtcState: UseTbtcState = () => { const tbtcState = useAppSelector((state: RootState) => state.tbtc) @@ -14,6 +22,12 @@ export const useTbtcState: UseTbtcState = () => { [dispatch] ) + const updateDepositDetailsDataState = useCallback( + (key: DepositDetailsDataStateKey, value: any) => + dispatch(updateDepositDetailsDataStateAction({ key, value })), + [dispatch] + ) + const resetDepositData = useCallback(() => { updateState("ethAddress", undefined) updateState("blindingFactor", undefined) @@ -26,15 +40,13 @@ export const useTbtcState: UseTbtcState = () => { updateState("thresholdNetworkFee", undefined) updateState("mintingFee", undefined) updateState("utxo", undefined) - updateState("txConfirmations", undefined) updateState("depositRevealedTxHash", undefined) - updateState("optimisticMintingRequestedTxHash", undefined) - updateState("optimisticMintingFinalizedTxHash", undefined) }, [updateState]) return { ...tbtcState, updateState, + updateDepositDetailsDataState, resetDepositData, } } diff --git a/src/pages/tBTC/Bridge/DepositDetails.tsx b/src/pages/tBTC/Bridge/DepositDetails.tsx index e417c076..cf85b3e8 100644 --- a/src/pages/tBTC/Bridge/DepositDetails.tsx +++ b/src/pages/tBTC/Bridge/DepositDetails.tsx @@ -14,14 +14,14 @@ import { TBTCTokenContractLink } from "../../../components/tBTC" import { Toast } from "../../../components/Toast" import { InlineTokenBalance } from "../../../components/TokenBalance" import { TransactionDetailsAmountItem } from "../../../components/TransactionDetails" -import ViewInBlockExplorer, { - Chain as ViewInBlockExplorerChain, -} from "../../../components/ViewInBlockExplorer" import { useAppDispatch } from "../../../hooks/store" -import { DepositData, useFetchDepositDetails } from "../../../hooks/tbtc" +import { + useSubscribeToOptimisticMintingFinalizedEvent, + useSubscribeToOptimisticMintingRequestedEvent, +} from "../../../hooks/tbtc" import { useTbtcState } from "../../../hooks/useTbtcState" import { tbtcSlice } from "../../../store/tbtc" -import { PageComponent } from "../../../types" +import { DepositDetailsData, PageComponent } from "../../../types" import { BridgeProcessDetailsPageSkeleton } from "./components/BridgeProcessDetailsPageSkeleton" import { DepositDetailsStep1, @@ -35,11 +35,17 @@ export const DepositDetails: PageComponent = () => { const { state } = useLocation() const navigate = useNavigate() const dispatch = useAppDispatch() - const { updateState, depositDetailsStep } = useTbtcState() - const { isFetching, data, error } = useFetchDepositDetails(depositKey) + const { + updateState, + depositDetailsStep, + depositDetails: { isFetching, data, error }, + } = useTbtcState() const [isSafelyCloseInfoToastVisible, setIsSafelyCloseInfoToastVisible] = useState(depositDetailsStep !== "bitcoin-confirmations") + useSubscribeToOptimisticMintingRequestedEvent(depositKey) + useSubscribeToOptimisticMintingFinalizedEvent(depositKey) + const [mintingProgressStep, setMintingProgressStep] = useState("bitcoin-confirmations") @@ -57,42 +63,40 @@ export const DepositDetails: PageComponent = () => { // Extract deposit details values to use them as a dependency in hook // dependency array. - const { - btcTxHash: btcDepositTxHash, - amount, - confirmations, - requiredConfirmations, - depositRevealedTxHash, - optimisticMintingRequestedTxHash, - optimisticMintingFinalizedTxHash, - treasuryFee: thresholdNetworkFee, - optimisticMintFee: mintingFee, - } = data + const btcDepositTxHash = data?.btcTxHash + const amount = data?.amount ?? "0" + const confirmations = data?.confirmations + const requiredConfirmations = data?.requiredConfirmations + const optimisticMintingRequestedTxHash = + data?.optimisticMintingRequestedTxHashFromEvent ?? + data?.optimisticMintingRequestedTxHash + const optimisticMintingFinalizedTxHash = + data?.optimisticMintingFinalizedTxHashFromEvent ?? + data?.optimisticMintingFinalizedTxHash + const thresholdNetworkFee = data?.treasuryFee + const mintingFee = data?.optimisticMintFee useEffect(() => { + if (depositKey) { + dispatch(tbtcSlice.actions.requestDepositDetailsData({ depositKey })) + } + + // Clear deposit details data when the depositKey is changed return () => { updateState("depositDetailsStep", undefined) + dispatch(tbtcSlice.actions.clearDepositDetailsData({})) } }, [depositKey]) useEffect(() => { - if ( - !!btcDepositTxHash && - confirmations !== undefined && - requiredConfirmations !== undefined && - confirmations < requiredConfirmations - ) { - dispatch( - tbtcSlice.actions.fetchUtxoConfirmations({ - utxo: { transactionHash: btcDepositTxHash, value: amount }, - }) - ) + if (shouldStartFromFirstStep) { + updateState("depositDetailsStep", "bitcoin-confirmations") + return } - }, [dispatch, btcDepositTxHash, amount, confirmations, requiredConfirmations]) - useEffect(() => { - if (!confirmations || !requiredConfirmations || shouldStartFromFirstStep) + if ((!confirmations && confirmations != 0) || !requiredConfirmations) { return + } const step = getMintingProgressStep({ confirmations, @@ -158,6 +162,12 @@ export const DepositDetails: PageComponent = () => { step={mintingProgressStep} confirmations={confirmations} requiredConfirmations={requiredConfirmations} + optimisticMintingRequestedTxHash={ + optimisticMintingRequestedTxHash + } + optimisticMintingFinalizedTxHash={ + optimisticMintingFinalizedTxHash + } btcTxHash={btcDepositTxHash} updateStep={setMintingProgressStep} amount={amount} @@ -186,7 +196,7 @@ type DepositDetailsTimelineStep = const getMintingProgressStep = ( depositDetails?: Omit< - DepositData, + DepositDetailsData, | "depositRevealedTxHash" | "btcTxHash" | "amount" diff --git a/src/pages/tBTC/Bridge/Minting/KnowledgeBaseLinks.tsx b/src/pages/tBTC/Bridge/Minting/KnowledgeBaseLinks.tsx index 8616a645..e2b4df50 100644 --- a/src/pages/tBTC/Bridge/Minting/KnowledgeBaseLinks.tsx +++ b/src/pages/tBTC/Bridge/Minting/KnowledgeBaseLinks.tsx @@ -1,11 +1,6 @@ import { StackProps, VStack } from "@chakra-ui/react" import { FC, useState } from "react" import { ExternalHref } from "../../../../enums" -import { - useFetchDepositDetails, - useSubscribeToOptimisticMintingFinalizedEventBase, - useSubscribeToOptimisticMintingRequestedEventBase, -} from "../../../../hooks/tbtc" import { BridgeProcessResources, BridgeProcessResourcesItemProps, @@ -33,41 +28,18 @@ export const KnowledgeBaseLinks: FC = ({ depositKey = "", ...restProps }) => { - const { data } = useFetchDepositDetails(depositKey) - const [mintingRequestedTxHash, setMintingRequestedTxHash] = useState() - const [mintingFinalizedTxHash, setMintingFinalizedTxHash] = useState() - const { depositDetailsStep } = useTbtcState() - useSubscribeToOptimisticMintingRequestedEventBase( - ( - minter, - depositKeyEventParam, - depositor, - amount, - fundingTxHash, - fundingOutputIndex, - event - ) => { - const depositKeyFromEvent = depositKeyEventParam.toHexString() - if (depositKeyFromEvent === depositKey) { - setMintingRequestedTxHash(event.transactionHash) - } - }, - undefined, - true - ) - - useSubscribeToOptimisticMintingFinalizedEventBase( - (minter, depositKeyEventParam, depositor, optimisticMintingDebt, event) => { - const depositKeyFromEvent = depositKeyEventParam.toHexString() - if (depositKeyFromEvent === depositKey) { - setMintingFinalizedTxHash(event.transactionHash) - } - }, - undefined, - true - ) + const { + depositDetails: { data }, + depositDetailsStep, + } = useTbtcState() const btcDepositTxHash = data?.btcTxHash const depositRevealedTxHash = data?.depositRevealedTxHash + const mintingRequestedTxHash = + data?.optimisticMintingFinalizedTxHashFromEvent ?? + data?.optimisticMintingRequestedTxHash + const mintingFinalizedTxHash = + data?.optimisticMintingFinalizedTxHashFromEvent ?? + data?.optimisticMintingRequestedTxHash const transactions: DepositTransactionHistoryItemType[] = [ { label: "Bitcoin Deposit", txHash: btcDepositTxHash, chain: "bitcoin" }, diff --git a/src/store/tbtc/effects.ts b/src/store/tbtc/effects.ts index e59c9a47..b8f0614c 100644 --- a/src/store/tbtc/effects.ts +++ b/src/store/tbtc/effects.ts @@ -2,7 +2,9 @@ import { BitcoinAddressConverter } from "@keep-network/tbtc-v2.ts" import { TaskAbortError } from "@reduxjs/toolkit" import { getChainIdentifier, + getContractPastEvents, isPublicKeyHashTypeAddress, + reverseTxHash, } from "../../threshold-ts/utils" import { MintingStep } from "../../types/tbtc" import { ONE_SEC_IN_MILISECONDS } from "../../utils/date" @@ -11,7 +13,11 @@ import { removeDataForAccount, TBTCLocalStorageDepositData, } from "../../utils/tbtcLocalStorageData" -import { isAddress, isAddressZero } from "../../web3/utils" +import { + isAddress, + isAddressZero, + isEmptyOrZeroAddress, +} from "../../web3/utils" import { AppListenerEffectAPI } from "../listener" import { tbtcSlice } from "./tbtcSlice" @@ -42,6 +48,122 @@ export const fetchBridgeactivityEffect = async ( } } +export const fetchDepositDetailsDataEffect = async ( + action: ReturnType, + listenerApi: AppListenerEffectAPI +) => { + const { depositKey } = action.payload + if (!depositKey) return + + listenerApi.dispatch(tbtcSlice.actions.fetchingDepositDetailsData()) + + try { + const { depositor } = + (await listenerApi.extra.threshold.tbtc.bridgeContract.deposits( + depositKey + )) as { + depositor: string + } + if (isEmptyOrZeroAddress(depositor)) { + throw new Error("Deposit not found...") + } + + const revealedDeposits = + await listenerApi.extra.threshold.tbtc.findAllRevealedDeposits(depositor) + + const deposit = revealedDeposits.find( + (deposit) => deposit.depositKey === depositKey + ) + + if (!deposit) { + throw new Error( + "Could not find `DepositRevealed` event by given deposit key." + ) + } + + const optimisticMintingRequestedEvents = await getContractPastEvents( + listenerApi.extra.threshold.tbtc.vaultContract, + { + eventName: "OptimisticMintingRequested", + fromBlock: deposit.blockNumber, + filterParams: [undefined, depositKey, depositor], + } + ) + + const optimisticMintingFinalizedEvents = await getContractPastEvents( + listenerApi.extra.threshold.tbtc.vaultContract, + { + eventName: "OptimisticMintingFinalized", + fromBlock: deposit.blockNumber, + filterParams: [undefined, depositKey, depositor], + } + ) + + const btcTxHash = reverseTxHash(deposit.fundingTxHash).toString() + const confirmations = + await listenerApi.extra.threshold.tbtc.getTransactionConfirmations( + btcTxHash + ) + const requiredConfirmations = + listenerApi.extra.threshold.tbtc.minimumNumberOfConfirmationsNeeded( + deposit.amount + ) + + const { treasuryFee, optimisticMintFee, amountToMint } = + await listenerApi.extra.threshold.tbtc.getEstimatedDepositFees( + deposit.amount + ) + + const depositDetailsData = { + btcTxHash, + depositRevealedTxHash: deposit.txHash, + amount: amountToMint, + treasuryFee, + optimisticMintFee, + optimisticMintingRequestedTxHash: + optimisticMintingRequestedEvents[0]?.transactionHash, + optimisticMintingFinalizedTxHash: + optimisticMintingFinalizedEvents[0]?.transactionHash, + requiredConfirmations, + confirmations, + } + + const depositKeyFromStore = + listenerApi.getState().tbtc.depositDetails.depositKey + + /** + * If deposit key changed in the store while data was fetching then we don't + * update the store with the fetched data. This is to prevent saving the + * data in the store after we clear the data for given deposit key (for + * example when we leave the deposit details page while data is fetching). + */ + if (depositKey !== depositKeyFromStore) return + listenerApi.dispatch( + tbtcSlice.actions.depositDetailsDataFetched(depositDetailsData) + ) + + /** + * If deposit doesn't have required number of confirmations then we start + * listening for new confirmations. + */ + if (btcTxHash && confirmations < requiredConfirmations) { + listenerApi.dispatch( + tbtcSlice.actions.fetchUtxoConfirmations({ + utxo: { transactionHash: btcTxHash, value: deposit.amount }, + }) + ) + } + } catch (error) { + console.error("Could not fetch deposit details: ", error) + listenerApi.subscribe() + listenerApi.dispatch( + tbtcSlice.actions.depositDetailsDataFetchFailed({ + error: "Could not fetch deposit details.", + }) + ) + } +} + export const findUtxoEffect = async ( action: ReturnType, listenerApi: AppListenerEffectAPI @@ -205,8 +327,9 @@ export const fetchUtxoConfirmationsEffect = async ( ) => { const { utxo } = action.payload const { - tbtc: { txConfirmations }, + tbtc: { depositDetails }, } = listenerApi.getState() + const confirmations = depositDetails.data?.confirmations if (!utxo) return @@ -215,7 +338,7 @@ export const fetchUtxoConfirmationsEffect = async ( utxo.value ) - if (txConfirmations && txConfirmations >= minimumNumberOfConfirmationsNeeded) + if (confirmations && confirmations >= minimumNumberOfConfirmationsNeeded) return // Cancel any in-progress instances of this listener. @@ -231,8 +354,8 @@ export const fetchUtxoConfirmationsEffect = async ( ) ) listenerApi.dispatch( - tbtcSlice.actions.updateState({ - key: "txConfirmations", + tbtcSlice.actions.updateDepositDetailsDataState({ + key: "confirmations", value: confirmations, }) ) @@ -249,13 +372,18 @@ export const fetchUtxoConfirmationsEffect = async ( }) await listenerApi.condition((action) => { - if (!tbtcSlice.actions.updateState.match(action)) return false + // stop listening for confirmations if deposit details data is cleared + if (tbtcSlice.actions.clearDepositDetailsData.match(action)) return true + if (!tbtcSlice.actions.updateDepositDetailsDataState.match(action)) + return false const { key, value } = ( - action as ReturnType + action as ReturnType< + typeof tbtcSlice.actions.updateDepositDetailsDataState + > ).payload return ( - key === "txConfirmations" && value >= minimumNumberOfConfirmationsNeeded + key === "confirmations" && value >= minimumNumberOfConfirmationsNeeded ) }) diff --git a/src/store/tbtc/tbtcSlice.ts b/src/store/tbtc/tbtcSlice.ts index d700a7ba..ac20be22 100644 --- a/src/store/tbtc/tbtcSlice.ts +++ b/src/store/tbtc/tbtcSlice.ts @@ -7,7 +7,8 @@ import { } from "../../threshold-ts/tbtc" import { UpdateStateActionPayload } from "../../types/state" import { - DepositDetailsStep, + DepositDetailsData, + DepositDetailsDataStateKey, MintingStep, TbtcState, TbtcStateKey, @@ -15,10 +16,18 @@ import { import { startAppListening } from "../listener" import { fetchBridgeactivityEffect, + fetchDepositDetailsDataEffect, fetchUtxoConfirmationsEffect, findUtxoEffect, } from "./effects" +const DEFAULT_DEPOSIT_DETAILS = { + depositKey: "", + isFetching: false, + error: "", + data: {} as DepositDetailsData, +} + export const tbtcSlice = createSlice({ name: "tbtc", initialState: { @@ -28,6 +37,7 @@ export const tbtcSlice = createSlice({ error: "", data: [] as BridgeActivity[], }, + depositDetails: DEFAULT_DEPOSIT_DETAILS, } as TbtcState, reducers: { updateState: ( @@ -53,6 +63,42 @@ export const tbtcSlice = createSlice({ state.bridgeActivity.isFetching = false state.bridgeActivity.error = action.payload.error }, + requestDepositDetailsData: ( + state, + action: PayloadAction<{ depositKey: string }> + ) => { + state.depositDetails.depositKey = action.payload.depositKey + }, + fetchingDepositDetailsData: (state) => { + state.depositDetails.isFetching = true + }, + depositDetailsDataFetched: ( + state, + action: PayloadAction + ) => { + state.depositDetails.isFetching = false + state.depositDetails.error = "" + state.depositDetails.data = action.payload + }, + depositDetailsDataFetchFailed: ( + state, + action: PayloadAction<{ error: string }> + ) => { + state.depositDetails.isFetching = false + state.depositDetails.error = action.payload.error + }, + updateDepositDetailsDataState: ( + state, + action: PayloadAction< + UpdateStateActionPayload + > + ) => { + // @ts-ignore + state.depositDetails.data[action.payload.key] = action.payload.value + }, + clearDepositDetailsData: (state, action) => { + state.depositDetails = DEFAULT_DEPOSIT_DETAILS + }, depositRevealed: ( state, action: PayloadAction<{ @@ -229,7 +275,7 @@ function findRedemptionActivity( } } -export const { updateState } = tbtcSlice.actions +export const { updateState, updateDepositDetailsDataState } = tbtcSlice.actions export const registerTBTCListeners = () => { startAppListening({ @@ -237,6 +283,11 @@ export const registerTBTCListeners = () => { effect: fetchBridgeactivityEffect, }) + startAppListening({ + actionCreator: tbtcSlice.actions.requestDepositDetailsData, + effect: fetchDepositDetailsDataEffect, + }) + startAppListening({ actionCreator: tbtcSlice.actions.findUtxo, effect: findUtxoEffect, diff --git a/src/types/tbtc.ts b/src/types/tbtc.ts index 8f66d58b..368a1cfd 100644 --- a/src/types/tbtc.ts +++ b/src/types/tbtc.ts @@ -14,20 +14,39 @@ export interface TbtcState { blindingFactor: string walletPublicKeyHash: string utxo: BitcoinUtxo - txConfirmations: number nextBridgeCrossingInUnix?: number depositRevealedTxHash?: string - optimisticMintingRequestedTxHash?: string - optimisticMintingFinalizedTxHash?: string tBTCMintAmount: string thresholdNetworkFee: string mintingFee: string bridgeActivity: FetchingState + depositDetails: FetchingState & { + depositKey: string + } +} + +type DepositDetailsDataState = DepositDetailsData & { + optimisticMintingRequestedTxHashFromEvent?: string + optimisticMintingFinalizedTxHashFromEvent?: string +} + +export interface DepositDetailsData { + depositRevealedTxHash: string + amount: string + btcTxHash: string + optimisticMintingRequestedTxHash?: string + optimisticMintingFinalizedTxHash?: string + confirmations: number + requiredConfirmations: number + treasuryFee: string + optimisticMintFee: string } export type TbtcStateKey = keyof Omit +export type DepositDetailsDataStateKey = keyof DepositDetailsDataState + export enum MintingStep { ProvideData = "PROVIDE_DATA", Deposit = "DEPOSIT", @@ -46,9 +65,17 @@ export interface UpdateTbtcState { payload: UpdateStateActionPayload } +export interface UpdateDepositDetailsState { + payload: UpdateStateActionPayload +} + export interface UseTbtcState { (): { updateState: (key: TbtcStateKey, value: any) => UpdateTbtcState + updateDepositDetailsDataState: ( + key: DepositDetailsDataStateKey, + value: any + ) => UpdateDepositDetailsState resetDepositData: () => void } & TbtcState }