From 804c4e0955cc66016a9cc62ff8646cb37d26d842 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 30 Oct 2024 15:31:58 +0100 Subject: [PATCH 01/16] Add `inProgress` status for sending BTC transaction The status will tell us when the wallet window opens and when the transaction is completed. --- dapp/src/hooks/useDepositBTCTransaction.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dapp/src/hooks/useDepositBTCTransaction.ts b/dapp/src/hooks/useDepositBTCTransaction.ts index 6bfa1e7a0..201567507 100644 --- a/dapp/src/hooks/useDepositBTCTransaction.ts +++ b/dapp/src/hooks/useDepositBTCTransaction.ts @@ -8,6 +8,7 @@ type SendBitcoinTransactionParams = Parameters< > type UseDepositBTCTransactionReturn = { + inProgress: boolean transactionHash?: string sendBitcoinTransaction: ( ...params: SendBitcoinTransactionParams @@ -24,6 +25,7 @@ export function useDepositBTCTransaction( const [transactionHash, setTransactionHash] = useState( undefined, ) + const [inProgress, setInProgress] = useState(false) useEffect(() => { if (transactionHash && onSuccess) { @@ -47,19 +49,22 @@ export function useDepositBTCTransaction( const satoshis = Number(amount) + setInProgress(true) // @ts-expect-error adjust types to handle bitcoin wallet wrappers // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment const txhash: string = await client.sendBitcoin(recipient, satoshis) + setInProgress(false) setTransactionHash(txhash) } catch (error) { if (onError) { onError(error) } console.error(error) + setInProgress(false) } }, [address, connector, onError], ) - return { sendBitcoinTransaction, transactionHash } + return { sendBitcoinTransaction, transactionHash, inProgress } } From 9e6fbe663c7bd2ac4deca08db6c0c3eb3fc6bb9d Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 30 Oct 2024 15:54:22 +0100 Subject: [PATCH 02/16] Update screens for deposit and withdrawal flows The "Skeleton" loading screen from both Deposit and Withdraw flows is no longer needed. The new flow adds two steps: - "Opening your wallet for signature" Screen - "Awaiting Transaction" Screen --- .../ActiveStakingStep/DepositBTCModal.tsx | 37 +++------ .../BuildTransactionModal.tsx | 29 +++++++ .../ActiveUnstakingStep/SignMessageModal.tsx | 48 +++-------- .../TransactionModal/ModalContentWrapper.tsx | 3 - .../TriggerTransactionModal.tsx | 32 -------- .../WalletInteractionModal.tsx | 79 +++++++++++++++++++ 6 files changed, 129 insertions(+), 99 deletions(-) create mode 100644 dapp/src/components/TransactionModal/ActiveUnstakingStep/BuildTransactionModal.tsx delete mode 100644 dapp/src/components/TransactionModal/TriggerTransactionModal.tsx create mode 100644 dapp/src/components/TransactionModal/WalletInteractionModal.tsx diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 6a9472487..f42659fab 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -11,12 +11,10 @@ import { } from "#/hooks" import { eip1193, logPromiseFailure } from "#/utils" import { PROCESS_STATUSES } from "#/types" -import { Highlight, ModalCloseButton } from "@chakra-ui/react" -import { TextMd } from "#/components/shared/Typography" import { setStatus, setTxHash } from "#/store/action-flow" -import { queryKeysFactory } from "#/constants" -import { Alert, AlertIcon, AlertDescription } from "#/components/shared/Alert" -import TriggerTransactionModal from "../TriggerTransactionModal" +import { ONE_SEC_IN_MILLISECONDS, queryKeysFactory } from "#/constants" +import { useTimeout } from "@chakra-ui/react" +import WalletInteractionModal from "../WalletInteractionModal" const { userKeys } = queryKeysFactory @@ -62,10 +60,8 @@ export default function DepositBTCModal() { [onError, handlePause], ) - const { sendBitcoinTransaction, transactionHash } = useDepositBTCTransaction( - onDepositBTCSuccess, - onDepositBTCError, - ) + const { sendBitcoinTransaction, transactionHash, inProgress } = + useDepositBTCTransaction(onDepositBTCSuccess, onDepositBTCError) useEffect(() => { if (transactionHash) { @@ -95,22 +91,9 @@ export default function DepositBTCModal() { logPromiseFailure(handledDepositBTC()) }, [handledDepositBTC]) - return ( - <> - - - - - - - - You will receive your Rewards once the deposit transaction is - completed. - - - - - - - ) + useTimeout(handledDepositBTCWrapper, ONE_SEC_IN_MILLISECONDS) + + if (inProgress) return + + return } diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/BuildTransactionModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/BuildTransactionModal.tsx new file mode 100644 index 000000000..c6b6f81af --- /dev/null +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/BuildTransactionModal.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { + Button, + ModalBody, + ModalCloseButton, + ModalHeader, +} from "@chakra-ui/react" +import Spinner from "#/components/shared/Spinner" +import { TextMd } from "#/components/shared/Typography" + +export default function BuildTransactionModal({ + onClose, +}: { + onClose: () => void +}) { + return ( + <> + + Building transaction data... + + + We are building your withdrawal data. + + + + ) +} diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index 03f7d84a7..b8c153e83 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -6,39 +6,22 @@ import { useExecuteFunction, useInvalidateQueries, useModal, + useTimeout, useTransactionDetails, } from "#/hooks" import { ACTION_FLOW_TYPES, PROCESS_STATUSES } from "#/types" -import { Button, ModalCloseButton } from "@chakra-ui/react" import { dateToUnixTimestamp, eip1193, logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" import { useInitializeWithdraw } from "#/acre-react/hooks" -import { queryKeysFactory } from "#/constants" +import { ONE_SEC_IN_MILLISECONDS, queryKeysFactory } from "#/constants" import { activityInitialized } from "#/store/wallet" -import TriggerTransactionModal from "../TriggerTransactionModal" +import BuildTransactionModal from "./BuildTransactionModal" +import WalletInteractionModal from "../WalletInteractionModal" const { userKeys } = queryKeysFactory type WithdrawalStatus = "building-data" | "signature" | "transaction" -const withdrawalStatusToContent: Record< - WithdrawalStatus, - { title: string; subtitle: string } -> = { - "building-data": { - title: "Building transaction data...", - subtitle: "We are building your withdrawal data.", - }, - signature: { - title: "Waiting signature...", - subtitle: "Please complete the signing process in your wallet.", - }, - transaction: { - title: "Waiting for withdrawal initialization...", - subtitle: "Withdrawal initialization in progress...", - }, -} - const sessionIdToPromise: Record< number, { @@ -167,8 +150,6 @@ export default function SignMessageModal() { logPromiseFailure(handleSignMessage()) }, [handleSignMessage]) - const { title, subtitle } = withdrawalStatusToContent[status] - const onClose = () => { const currentSessionId = sessionId.current const sessionData = sessionIdToPromise[currentSessionId] @@ -183,18 +164,11 @@ export default function SignMessageModal() { closeModal() } - return ( - <> - - - - - - ) + useTimeout(handleInitWithdrawAndSignMessageWrapper, ONE_SEC_IN_MILLISECONDS) + + // TODO: This step should be split into several steps (building data and opening a wallet). + if (status === "building-data") + return + + return } diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index bf08288a4..b3d017ea2 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -7,7 +7,6 @@ import { BaseModalProps, PROCESS_STATUSES } from "#/types" import React from "react" import ActionFormModal from "./ActionFormModal" import ErrorModal from "./ErrorModal" -import LoadingModal from "./LoadingModal" import ResumeModal from "./ResumeModal" import SuccessModal from "./SuccessModal" import NotEnoughFundsModal from "./ActiveUnstakingStep/NotEnoughFundsModal" @@ -25,8 +24,6 @@ export default function ModalContentWrapper({ if (!tokenAmount || status === PROCESS_STATUSES.REFINE_AMOUNT) return - if (status === PROCESS_STATUSES.LOADING) return - if (status === PROCESS_STATUSES.SUCCEEDED) return if (status === PROCESS_STATUSES.FAILED) diff --git a/dapp/src/components/TransactionModal/TriggerTransactionModal.tsx b/dapp/src/components/TransactionModal/TriggerTransactionModal.tsx deleted file mode 100644 index 9f0d16cab..000000000 --- a/dapp/src/components/TransactionModal/TriggerTransactionModal.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { ReactNode } from "react" -import { ModalBody, ModalHeader, useTimeout } from "@chakra-ui/react" -import Spinner from "#/components/shared/Spinner" -import { TextMd } from "#/components/shared/Typography" -import { ONE_SEC_IN_MILLISECONDS } from "#/constants" - -export default function TriggerTransactionModal({ - callback, - children, - delay = ONE_SEC_IN_MILLISECONDS, - title = "Awaiting transaction...", - subtitle = "Please complete the transaction in your wallet.", -}: { - callback: () => void - children: ReactNode - delay?: number - title?: string - subtitle?: string -}) { - useTimeout(callback, delay) - - return ( - <> - {title} - - - {subtitle} - {children} - - - ) -} diff --git a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx new file mode 100644 index 000000000..1e64c02bd --- /dev/null +++ b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx @@ -0,0 +1,79 @@ +import React from "react" +import { + AlertDescription, + ModalBody, + ModalCloseButton, + ModalHeader, + Image, + HStack, + Progress, + ProgressProps, +} from "@chakra-ui/react" +import { AcreSignIcon } from "#/assets/icons" +import { useConnector } from "#/hooks" +import { Alert, AlertIcon } from "../shared/Alert" +import { TextMd } from "../shared/Typography" + +const ICON_STYLES = { + boxSize: 14, + rounded: "full", +} + +type WalletInteractionStep = "opening-wallet" | "awaiting-transaction" + +const DATA: Record< + WalletInteractionStep, + { header: string; description: string; progressProps?: ProgressProps } +> = { + "opening-wallet": { + header: "Opening your wallet for signature", + description: + "Confirm the deposit by signing the transaction with your wallet.", + }, + "awaiting-transaction": { + header: "Awaiting signature confirmation", + description: "Waiting for your wallet to confirm the transaction.", + progressProps: { transform: "scaleX(-1)" }, + }, +} + +export default function WalletInteractionModal({ + step, +}: { + step: WalletInteractionStep +}) { + const connector = useConnector() + const { header, description, progressProps } = DATA[step] + + return ( + <> + {step === "opening-wallet" && } + + {header} + + + + + {/* TODO: Create correct progress bar */} + + + + {description} + {step === "awaiting-transaction" && ( + + + + This may take up to a minute. + Don't close this window. + + + )} + + + ) +} From 51a3965d2c44fc8791f48b473bf03b7e72d66cc9 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 30 Oct 2024 16:30:43 +0100 Subject: [PATCH 03/16] Update `initializeWithdrawal` fn We want to know when the transaction data are built. We add callback that is triggered after building data. --- dapp/src/acre-react/hooks/useInitializeWithdraw.ts | 3 +++ .../ActiveUnstakingStep/SignMessageModal.tsx | 3 +++ sdk/src/lib/redeemer-proxy.ts | 6 ++++++ sdk/src/modules/account.ts | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/dapp/src/acre-react/hooks/useInitializeWithdraw.ts b/dapp/src/acre-react/hooks/useInitializeWithdraw.ts index 8c434f21c..28997a41b 100644 --- a/dapp/src/acre-react/hooks/useInitializeWithdraw.ts +++ b/dapp/src/acre-react/hooks/useInitializeWithdraw.ts @@ -1,5 +1,6 @@ import { useCallback } from "react" import { + BuiltDataStepCallback, MessageSignedStepCallback, OnSignMessageStepCallback, } from "@acre-btc/sdk/dist/src/lib/redeemer-proxy" @@ -11,6 +12,7 @@ export default function useInitializeWithdraw() { return useCallback( async ( amount: bigint, + builtDataStepCallback?: BuiltDataStepCallback, onSignMessageStep?: OnSignMessageStepCallback, messageSignedStep?: MessageSignedStepCallback, ) => { @@ -18,6 +20,7 @@ export default function useInitializeWithdraw() { return acre.account.initializeWithdrawal( amount, + builtDataStepCallback, onSignMessageStep, messageSignedStep, ) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index b8c153e83..1b2833c09 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -62,6 +62,8 @@ export default function SignMessageModal() { } }, []) + const builtDataStepCallback = useCallback(() => Promise.resolve(), []) + const onSignMessageCallback = useCallback(async () => { setWaitingStatus("signature") return Promise.race([ @@ -104,6 +106,7 @@ export default function SignMessageModal() { const { redemptionKey } = await initializeWithdraw( amount, + builtDataStepCallback, onSignMessageCallback, messageSignedCallback, ) diff --git a/sdk/src/lib/redeemer-proxy.ts b/sdk/src/lib/redeemer-proxy.ts index 9568a378c..ec47d02d6 100644 --- a/sdk/src/lib/redeemer-proxy.ts +++ b/sdk/src/lib/redeemer-proxy.ts @@ -7,6 +7,7 @@ import { OrangeKitSdk } from "@orangekit/sdk" import { AcreContracts } from "./contracts" import { BitcoinProvider } from "./bitcoin" +export type BuiltDataStepCallback = (safeTxData: Hex) => Promise export type OnSignMessageStepCallback = (messageToSign: string) => Promise export type MessageSignedStepCallback = (signedMessage: string) => Promise @@ -25,6 +26,8 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { #sharesAmount: bigint + #builtDataStepCallback?: BuiltDataStepCallback + #onSignMessageStepCallback?: OnSignMessageStepCallback #messageSignedStepCallback?: MessageSignedStepCallback @@ -39,6 +42,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { }, bitcoinProvider: BitcoinProvider, sharesAmount: bigint, + builtDataStepCallback?: BuiltDataStepCallback, onSignMessageStepCallback?: OnSignMessageStepCallback, messageSignedStepCallback?: MessageSignedStepCallback, ) { @@ -47,6 +51,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { this.#account = account this.#bitcoinProvider = bitcoinProvider this.#sharesAmount = sharesAmount + this.#builtDataStepCallback = builtDataStepCallback this.#onSignMessageStepCallback = onSignMessageStepCallback this.#messageSignedStepCallback = messageSignedStepCallback } @@ -61,6 +66,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { this.#sharesAmount, redemptionData, ) + await this.#builtDataStepCallback?.(safeTxData) const transactionHash = await this.#orangeKitSdk.sendTransaction( `0x${this.#contracts.stBTC.getChainIdentifier().identifierHex}`, diff --git a/sdk/src/modules/account.ts b/sdk/src/modules/account.ts index 4fb568977..78b9a86fa 100644 --- a/sdk/src/modules/account.ts +++ b/sdk/src/modules/account.ts @@ -6,6 +6,7 @@ import Tbtc from "./tbtc" import AcreSubgraphApi from "../lib/api/AcreSubgraphApi" import { DepositStatus } from "../lib/api/TbtcApi" import OrangeKitTbtcRedeemerProxy, { + BuiltDataStepCallback, MessageSignedStepCallback, OnSignMessageStepCallback, } from "../lib/redeemer-proxy" @@ -183,6 +184,8 @@ export default class Account { /** * Initializes the withdrawal process. * @param amount Bitcoin amount to withdraw in 1e8 satoshi precision. + * @param builtDataStepCallback A callback triggered after the data + * building step. * @param onSignMessageStepCallback A callback triggered before the message * signing step. * @param messageSignedStepCallback A callback triggered after the message @@ -191,6 +194,7 @@ export default class Account { */ async initializeWithdrawal( btcAmount: bigint, + builtDataStepCallback?: BuiltDataStepCallback, onSignMessageStepCallback?: OnSignMessageStepCallback, messageSignedStepCallback?: MessageSignedStepCallback, ): Promise<{ transactionHash: string; redemptionKey: string }> { @@ -209,6 +213,7 @@ export default class Account { }, this.#bitcoinProvider, shares, + builtDataStepCallback, onSignMessageStepCallback, messageSignedStepCallback, ) From d0c04182b1e7708aedfb0b14ce085b642e2d98f7 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 30 Oct 2024 16:42:12 +0100 Subject: [PATCH 04/16] Add a new step for the withdrawal flow --- .../ActiveUnstakingStep/SignMessageModal.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index 1b2833c09..3872e9e4c 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -20,7 +20,7 @@ import WalletInteractionModal from "../WalletInteractionModal" const { userKeys } = queryKeysFactory -type WithdrawalStatus = "building-data" | "signature" | "transaction" +type WithdrawalStatus = "building-data" | "built-data" | "signature" const sessionIdToPromise: Record< number, @@ -62,7 +62,10 @@ export default function SignMessageModal() { } }, []) - const builtDataStepCallback = useCallback(() => Promise.resolve(), []) + const builtDataStepCallback = useCallback(() => { + setWaitingStatus("built-data") + return Promise.resolve() + }, []) const onSignMessageCallback = useCallback(async () => { setWaitingStatus("signature") @@ -72,12 +75,6 @@ export default function SignMessageModal() { ]) }, []) - const messageSignedCallback = useCallback(() => { - setWaitingStatus("transaction") - dispatch(setStatus(PROCESS_STATUSES.LOADING)) - return Promise.resolve() - }, [dispatch]) - const onSignMessageSuccess = useCallback(() => { handleBitcoinPositionInvalidation() dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) @@ -108,7 +105,6 @@ export default function SignMessageModal() { amount, builtDataStepCallback, onSignMessageCallback, - messageSignedCallback, ) dispatch( @@ -169,9 +165,11 @@ export default function SignMessageModal() { useTimeout(handleInitWithdrawAndSignMessageWrapper, ONE_SEC_IN_MILLISECONDS) - // TODO: This step should be split into several steps (building data and opening a wallet). if (status === "building-data") return + if (status === "built-data") + return + return } From 9c8840f03f96e992f8f0637f20c0afd183ccda1a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 31 Oct 2024 10:17:43 +0100 Subject: [PATCH 05/16] Fix failed SDK test --- sdk/test/modules/account.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/test/modules/account.test.ts b/sdk/test/modules/account.test.ts index 8c871adbd..d9ed71e21 100644 --- a/sdk/test/modules/account.test.ts +++ b/sdk/test/modules/account.test.ts @@ -415,6 +415,7 @@ describe("Account", () => { mockedShares, undefined, undefined, + undefined, ) }) From 8fdbec7c32a39d432c6c328be5bdcd6e839f2ad2 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 31 Oct 2024 10:22:48 +0100 Subject: [PATCH 06/16] Add padding for the connector icon --- dapp/src/components/TransactionModal/WalletInteractionModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx index 1e64c02bd..e0a625616 100644 --- a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx +++ b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx @@ -61,7 +61,7 @@ export default function WalletInteractionModal({ isIndeterminate {...progressProps} /> - + {description} {step === "awaiting-transaction" && ( From f47c3e9cbfd27a9667c5e81cf61c80b90583dc93 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 31 Oct 2024 13:08:00 +0100 Subject: [PATCH 07/16] Set the right description in `WalletInteractionModal` --- .../WalletInteractionModal.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx index e0a625616..31c8977ab 100644 --- a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx +++ b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx @@ -10,7 +10,8 @@ import { ProgressProps, } from "@chakra-ui/react" import { AcreSignIcon } from "#/assets/icons" -import { useConnector } from "#/hooks" +import { useActionFlowType, useConnector } from "#/hooks" +import { ACTION_FLOW_TYPES } from "#/types" import { Alert, AlertIcon } from "../shared/Alert" import { TextMd } from "../shared/Typography" @@ -23,16 +24,20 @@ type WalletInteractionStep = "opening-wallet" | "awaiting-transaction" const DATA: Record< WalletInteractionStep, - { header: string; description: string; progressProps?: ProgressProps } + { + header: string + description: (action: string) => string + progressProps?: ProgressProps + } > = { "opening-wallet": { header: "Opening your wallet for signature", - description: - "Confirm the deposit by signing the transaction with your wallet.", + description: (action) => + `Confirm the ${action} by signing the transaction with your wallet.`, }, "awaiting-transaction": { header: "Awaiting signature confirmation", - description: "Waiting for your wallet to confirm the transaction.", + description: () => "Waiting for your wallet to confirm the transaction.", progressProps: { transform: "scaleX(-1)" }, }, } @@ -42,6 +47,7 @@ export default function WalletInteractionModal({ }: { step: WalletInteractionStep }) { + const type = useActionFlowType() const connector = useConnector() const { header, description, progressProps } = DATA[step] @@ -63,7 +69,11 @@ export default function WalletInteractionModal({ /> - {description} + + {description( + type === ACTION_FLOW_TYPES.STAKE ? "deposit" : "withdraw", + )} + {step === "awaiting-transaction" && ( From a8801982a440299ccd279f418a27728dc832da10 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 31 Oct 2024 13:20:48 +0100 Subject: [PATCH 08/16] Use `finally` to update state --- dapp/src/hooks/useDepositBTCTransaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/hooks/useDepositBTCTransaction.ts b/dapp/src/hooks/useDepositBTCTransaction.ts index 201567507..5146e1457 100644 --- a/dapp/src/hooks/useDepositBTCTransaction.ts +++ b/dapp/src/hooks/useDepositBTCTransaction.ts @@ -53,13 +53,13 @@ export function useDepositBTCTransaction( // @ts-expect-error adjust types to handle bitcoin wallet wrappers // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment const txhash: string = await client.sendBitcoin(recipient, satoshis) - setInProgress(false) setTransactionHash(txhash) } catch (error) { if (onError) { onError(error) } console.error(error) + } finally { setInProgress(false) } }, From 48effb3ee5032c92759ef7ddd0f39592dc706bac Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 31 Oct 2024 13:29:24 +0100 Subject: [PATCH 09/16] Show correct modal when `transactionHash` is defined --- .../TransactionModal/ActiveStakingStep/DepositBTCModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index f42659fab..b9d3e04c9 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -93,7 +93,8 @@ export default function DepositBTCModal() { useTimeout(handledDepositBTCWrapper, ONE_SEC_IN_MILLISECONDS) - if (inProgress) return + if (inProgress || transactionHash) + return return } From f3f18bec965b9656ade4fb3b65362186c96c1902 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 4 Nov 2024 08:15:29 +0100 Subject: [PATCH 10/16] Add `alt` prop to image component --- .../TransactionModal/WalletInteractionModal.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx index 31c8977ab..ae783f4f0 100644 --- a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx +++ b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx @@ -67,7 +67,13 @@ export default function WalletInteractionModal({ isIndeterminate {...progressProps} /> - + Connector icon {description( From 3fbfbef368e4d1a07c9e922e0865354b3d4e47d0 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 4 Nov 2024 08:24:20 +0100 Subject: [PATCH 11/16] Rename from `type` to `actionType` --- .../components/TransactionModal/WalletInteractionModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx index ae783f4f0..dbbf4c53b 100644 --- a/dapp/src/components/TransactionModal/WalletInteractionModal.tsx +++ b/dapp/src/components/TransactionModal/WalletInteractionModal.tsx @@ -47,7 +47,7 @@ export default function WalletInteractionModal({ }: { step: WalletInteractionStep }) { - const type = useActionFlowType() + const actionType = useActionFlowType() const connector = useConnector() const { header, description, progressProps } = DATA[step] @@ -77,7 +77,7 @@ export default function WalletInteractionModal({ {description( - type === ACTION_FLOW_TYPES.STAKE ? "deposit" : "withdraw", + actionType === ACTION_FLOW_TYPES.STAKE ? "deposit" : "withdraw", )} {step === "awaiting-transaction" && ( From 8ceb6b4e73b454b77008971ce316228eaafffea2 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 4 Nov 2024 09:22:51 +0100 Subject: [PATCH 12/16] Rename from `builtDataStepCallback` to `dataBuiltStepCallback` --- dapp/src/acre-react/hooks/useInitializeWithdraw.ts | 6 +++--- .../ActiveUnstakingStep/SignMessageModal.tsx | 4 ++-- sdk/src/lib/redeemer-proxy.ts | 10 +++++----- sdk/src/modules/account.ts | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dapp/src/acre-react/hooks/useInitializeWithdraw.ts b/dapp/src/acre-react/hooks/useInitializeWithdraw.ts index 28997a41b..c6863e23d 100644 --- a/dapp/src/acre-react/hooks/useInitializeWithdraw.ts +++ b/dapp/src/acre-react/hooks/useInitializeWithdraw.ts @@ -1,6 +1,6 @@ import { useCallback } from "react" import { - BuiltDataStepCallback, + DataBuiltStepCallback, MessageSignedStepCallback, OnSignMessageStepCallback, } from "@acre-btc/sdk/dist/src/lib/redeemer-proxy" @@ -12,7 +12,7 @@ export default function useInitializeWithdraw() { return useCallback( async ( amount: bigint, - builtDataStepCallback?: BuiltDataStepCallback, + dataBuiltStepCallback?: DataBuiltStepCallback, onSignMessageStep?: OnSignMessageStepCallback, messageSignedStep?: MessageSignedStepCallback, ) => { @@ -20,7 +20,7 @@ export default function useInitializeWithdraw() { return acre.account.initializeWithdrawal( amount, - builtDataStepCallback, + dataBuiltStepCallback, onSignMessageStep, messageSignedStep, ) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index 3872e9e4c..3fd8e628b 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -62,7 +62,7 @@ export default function SignMessageModal() { } }, []) - const builtDataStepCallback = useCallback(() => { + const dataBuiltStepCallback = useCallback(() => { setWaitingStatus("built-data") return Promise.resolve() }, []) @@ -103,7 +103,7 @@ export default function SignMessageModal() { const { redemptionKey } = await initializeWithdraw( amount, - builtDataStepCallback, + dataBuiltStepCallback, onSignMessageCallback, ) diff --git a/sdk/src/lib/redeemer-proxy.ts b/sdk/src/lib/redeemer-proxy.ts index ec47d02d6..bb0bb3cf7 100644 --- a/sdk/src/lib/redeemer-proxy.ts +++ b/sdk/src/lib/redeemer-proxy.ts @@ -7,7 +7,7 @@ import { OrangeKitSdk } from "@orangekit/sdk" import { AcreContracts } from "./contracts" import { BitcoinProvider } from "./bitcoin" -export type BuiltDataStepCallback = (safeTxData: Hex) => Promise +export type DataBuiltStepCallback = (safeTxData: Hex) => Promise export type OnSignMessageStepCallback = (messageToSign: string) => Promise export type MessageSignedStepCallback = (signedMessage: string) => Promise @@ -26,7 +26,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { #sharesAmount: bigint - #builtDataStepCallback?: BuiltDataStepCallback + #dataBuiltStepCallback?: DataBuiltStepCallback #onSignMessageStepCallback?: OnSignMessageStepCallback @@ -42,7 +42,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { }, bitcoinProvider: BitcoinProvider, sharesAmount: bigint, - builtDataStepCallback?: BuiltDataStepCallback, + dataBuiltStepCallback?: DataBuiltStepCallback, onSignMessageStepCallback?: OnSignMessageStepCallback, messageSignedStepCallback?: MessageSignedStepCallback, ) { @@ -51,7 +51,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { this.#account = account this.#bitcoinProvider = bitcoinProvider this.#sharesAmount = sharesAmount - this.#builtDataStepCallback = builtDataStepCallback + this.#dataBuiltStepCallback = dataBuiltStepCallback this.#onSignMessageStepCallback = onSignMessageStepCallback this.#messageSignedStepCallback = messageSignedStepCallback } @@ -66,7 +66,7 @@ export default class OrangeKitTbtcRedeemerProxy implements TbtcRedeemerProxy { this.#sharesAmount, redemptionData, ) - await this.#builtDataStepCallback?.(safeTxData) + await this.#dataBuiltStepCallback?.(safeTxData) const transactionHash = await this.#orangeKitSdk.sendTransaction( `0x${this.#contracts.stBTC.getChainIdentifier().identifierHex}`, diff --git a/sdk/src/modules/account.ts b/sdk/src/modules/account.ts index 78b9a86fa..38ddc83ce 100644 --- a/sdk/src/modules/account.ts +++ b/sdk/src/modules/account.ts @@ -6,7 +6,7 @@ import Tbtc from "./tbtc" import AcreSubgraphApi from "../lib/api/AcreSubgraphApi" import { DepositStatus } from "../lib/api/TbtcApi" import OrangeKitTbtcRedeemerProxy, { - BuiltDataStepCallback, + DataBuiltStepCallback, MessageSignedStepCallback, OnSignMessageStepCallback, } from "../lib/redeemer-proxy" @@ -184,7 +184,7 @@ export default class Account { /** * Initializes the withdrawal process. * @param amount Bitcoin amount to withdraw in 1e8 satoshi precision. - * @param builtDataStepCallback A callback triggered after the data + * @param dataBuiltStepCallback A callback triggered after the data * building step. * @param onSignMessageStepCallback A callback triggered before the message * signing step. @@ -194,7 +194,7 @@ export default class Account { */ async initializeWithdrawal( btcAmount: bigint, - builtDataStepCallback?: BuiltDataStepCallback, + dataBuiltStepCallback?: DataBuiltStepCallback, onSignMessageStepCallback?: OnSignMessageStepCallback, messageSignedStepCallback?: MessageSignedStepCallback, ): Promise<{ transactionHash: string; redemptionKey: string }> { @@ -213,7 +213,7 @@ export default class Account { }, this.#bitcoinProvider, shares, - builtDataStepCallback, + dataBuiltStepCallback, onSignMessageStepCallback, messageSignedStepCallback, ) From 1717ea5b817f7f1ba6158f30975579a128027b13 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 5 Nov 2024 11:19:01 +0100 Subject: [PATCH 13/16] Refactor `useDepositBTCTransaction` and use `useMutation` --- .../ActiveStakingStep/DepositBTCModal.tsx | 53 +++++++------ dapp/src/hooks/useDepositBTCTransaction.ts | 79 +++++++------------ dapp/src/hooks/useExecuteFunction.ts | 4 +- dapp/src/types/callback.ts | 2 +- 4 files changed, 60 insertions(+), 78 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index b9d3e04c9..61f1a1a1e 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from "react" +import React, { useCallback } from "react" import { useActionFlowPause, useActionFlowTokenAmount, @@ -34,57 +34,58 @@ export default function DepositBTCModal() { }, [dispatch, handleBitcoinBalanceInvalidation]) const onError = useCallback( - (error?: unknown) => { - console.error(error) - dispatch(setStatus(PROCESS_STATUSES.FAILED)) - }, + () => dispatch(setStatus(PROCESS_STATUSES.FAILED)), [dispatch], ) const handleStake = useExecuteFunction(stake, onStakeBTCSuccess, onError) - const onDepositBTCSuccess = useCallback(() => { - dispatch(setStatus(PROCESS_STATUSES.LOADING)) - - logPromiseFailure(handleStake()) - }, [dispatch, handleStake]) + const onDepositBTCSuccess = useCallback( + (transactionHash: string) => { + dispatch(setTxHash(transactionHash)) + logPromiseFailure(handleStake()) + }, + [dispatch, handleStake], + ) const onDepositBTCError = useCallback( (error: unknown) => { if (eip1193.didUserRejectRequest(error)) { handlePause() } else { - onError(error) + onError() } }, [onError, handlePause], ) - const { sendBitcoinTransaction, transactionHash, inProgress } = - useDepositBTCTransaction(onDepositBTCSuccess, onDepositBTCError) - - useEffect(() => { - if (transactionHash) { - dispatch(setTxHash(transactionHash)) - } - }, [dispatch, transactionHash]) + const { sendBitcoinTransaction, status } = useDepositBTCTransaction( + onDepositBTCSuccess, + onDepositBTCError, + ) const handledDepositBTC = useCallback(async () => { if (!tokenAmount?.amount || !btcAddress || !depositReceipt) return - const status = await verifyDepositAddress(depositReceipt, btcAddress) - - if (status === "valid") { - await sendBitcoinTransaction(btcAddress, tokenAmount?.amount) + const verificationStatus = await verifyDepositAddress( + depositReceipt, + btcAddress, + ) + + if (verificationStatus === "valid") { + sendBitcoinTransaction({ + recipient: btcAddress, + amount: tokenAmount?.amount, + }) } else { onError() } }, [ + tokenAmount?.amount, btcAddress, depositReceipt, - onError, verifyDepositAddress, sendBitcoinTransaction, - tokenAmount?.amount, + onError, ]) const handledDepositBTCWrapper = useCallback(() => { @@ -93,7 +94,7 @@ export default function DepositBTCModal() { useTimeout(handledDepositBTCWrapper, ONE_SEC_IN_MILLISECONDS) - if (inProgress || transactionHash) + if (status === "pending" || status === "success") return return diff --git a/dapp/src/hooks/useDepositBTCTransaction.ts b/dapp/src/hooks/useDepositBTCTransaction.ts index 5146e1457..c3eeb2e03 100644 --- a/dapp/src/hooks/useDepositBTCTransaction.ts +++ b/dapp/src/hooks/useDepositBTCTransaction.ts @@ -1,40 +1,28 @@ -import { useCallback, useEffect, useState } from "react" import { OnErrorCallback, OnSuccessCallback } from "#/types" +import { useMutation } from "@tanstack/react-query" import { useWallet } from "./useWallet" import { useConnector } from "./orangeKit/useConnector" -type SendBitcoinTransactionParams = Parameters< - (recipient: string, amount: bigint) => void -> - -type UseDepositBTCTransactionReturn = { - inProgress: boolean - transactionHash?: string - sendBitcoinTransaction: ( - ...params: SendBitcoinTransactionParams - ) => Promise -} - export function useDepositBTCTransaction( - onSuccess?: OnSuccessCallback, + onSuccess?: OnSuccessCallback, onError?: OnErrorCallback, -): UseDepositBTCTransactionReturn { +) { const connector = useConnector() const { address } = useWallet() - const [transactionHash, setTransactionHash] = useState( - undefined, - ) - const [inProgress, setInProgress] = useState(false) - - useEffect(() => { - if (transactionHash && onSuccess) { - onSuccess() - } - }, [onSuccess, transactionHash]) - - const sendBitcoinTransaction = useCallback( - async (recipient: string, amount: bigint) => { + const { + mutate: sendBitcoinTransaction, + status, + data, + } = useMutation({ + mutationKey: ["send-bitcoin"], + mutationFn: async ({ + recipient, + amount, + }: { + recipient: string + amount: bigint + }) => { if (!address) { throw new Error("Bitcoin account was not connected.") } @@ -42,29 +30,22 @@ export function useDepositBTCTransaction( if (!connector) { throw new Error("Connector was not defined.") } + // @ts-expect-error adjust types to handle bitcoin wallet wrappers + const client = await connector.getClient() - try { - // @ts-expect-error adjust types to handle bitcoin wallet wrappers - const client = await connector.getClient() - - const satoshis = Number(amount) + const satoshis = Number(amount) - setInProgress(true) - // @ts-expect-error adjust types to handle bitcoin wallet wrappers - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment - const txhash: string = await client.sendBitcoin(recipient, satoshis) - setTransactionHash(txhash) - } catch (error) { - if (onError) { - onError(error) - } - console.error(error) - } finally { - setInProgress(false) - } + // @ts-expect-error adjust types to handle bitcoin wallet wrappers + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment + const txhash: string = await client.sendBitcoin(recipient, satoshis) + return txhash + }, + onSuccess: (responseData) => onSuccess?.(responseData), + onError: (error) => { + onError?.(error) + console.error(error) }, - [address, connector, onError], - ) + }) - return { sendBitcoinTransaction, transactionHash, inProgress } + return { sendBitcoinTransaction, status, data } } diff --git a/dapp/src/hooks/useExecuteFunction.ts b/dapp/src/hooks/useExecuteFunction.ts index 9f08aa884..a99ebcf0c 100644 --- a/dapp/src/hooks/useExecuteFunction.ts +++ b/dapp/src/hooks/useExecuteFunction.ts @@ -13,10 +13,10 @@ export function useExecuteFunction< return useCallback( async (...args: Parameters) => { try { - await fn(...args) + const response = await fn(...args) if (onSuccess) { - onSuccess() + onSuccess(response) } } catch (error) { if (onError) { diff --git a/dapp/src/types/callback.ts b/dapp/src/types/callback.ts index ee2275bb9..8fcfac9c0 100644 --- a/dapp/src/types/callback.ts +++ b/dapp/src/types/callback.ts @@ -1,3 +1,3 @@ -export type OnSuccessCallback = () => void +export type OnSuccessCallback = (data: DataType) => void export type OnErrorCallback = (error: ErrorType) => void From 26da7a55c84e4bbd6f27156a5208ac63b04c5429 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 5 Nov 2024 13:00:10 +0100 Subject: [PATCH 14/16] Use `@tanstack/react-query` for deposit/withdraw flow Since we use the `@tanstack/react-query` library we no longer need the `useExecuteFunction` hook. Let's stay consistent use `useMutation` instead of `useExecuteFunction` in the rest of the code. --- .../ActiveStakingStep/DepositBTCModal.tsx | 50 +++++++++++++++--- .../StakingErrorModal/index.tsx | 29 ++++------- .../ActiveUnstakingStep/SignMessageModal.tsx | 19 +++---- dapp/src/hooks/index.ts | 2 - dapp/src/hooks/useDepositBTCTransaction.ts | 51 ------------------- dapp/src/hooks/useExecuteFunction.ts | 30 ----------- 6 files changed, 60 insertions(+), 121 deletions(-) delete mode 100644 dapp/src/hooks/useDepositBTCTransaction.ts delete mode 100644 dapp/src/hooks/useExecuteFunction.ts diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 61f1a1a1e..571db3495 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -3,22 +3,25 @@ import { useActionFlowPause, useActionFlowTokenAmount, useAppDispatch, - useDepositBTCTransaction, - useExecuteFunction, + useConnector, useInvalidateQueries, useStakeFlowContext, useVerifyDepositAddress, + useWallet, } from "#/hooks" import { eip1193, logPromiseFailure } from "#/utils" import { PROCESS_STATUSES } from "#/types" import { setStatus, setTxHash } from "#/store/action-flow" import { ONE_SEC_IN_MILLISECONDS, queryKeysFactory } from "#/constants" import { useTimeout } from "@chakra-ui/react" +import { useMutation } from "@tanstack/react-query" import WalletInteractionModal from "../WalletInteractionModal" const { userKeys } = queryKeysFactory export default function DepositBTCModal() { + const connector = useConnector() + const { address } = useWallet() const tokenAmount = useActionFlowTokenAmount() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const verifyDepositAddress = useVerifyDepositAddress() @@ -38,12 +41,17 @@ export default function DepositBTCModal() { [dispatch], ) - const handleStake = useExecuteFunction(stake, onStakeBTCSuccess, onError) + const { mutate: handleStake } = useMutation({ + mutationKey: ["stake"], + mutationFn: stake, + onSuccess: onStakeBTCSuccess, + onError, + }) const onDepositBTCSuccess = useCallback( (transactionHash: string) => { dispatch(setTxHash(transactionHash)) - logPromiseFailure(handleStake()) + handleStake() }, [dispatch, handleStake], ) @@ -54,15 +62,41 @@ export default function DepositBTCModal() { handlePause() } else { onError() + console.error(error) } }, [onError, handlePause], ) - const { sendBitcoinTransaction, status } = useDepositBTCTransaction( - onDepositBTCSuccess, - onDepositBTCError, - ) + const { mutate: sendBitcoinTransaction, status } = useMutation({ + mutationKey: ["send-bitcoin"], + mutationFn: async ({ + recipient, + amount, + }: { + recipient: string + amount: bigint + }) => { + if (!address) { + throw new Error("Bitcoin account was not connected.") + } + + if (!connector) { + throw new Error("Connector was not defined.") + } + // @ts-expect-error adjust types to handle bitcoin wallet wrappers + const client = await connector.getClient() + + const satoshis = Number(amount) + + // @ts-expect-error adjust types to handle bitcoin wallet wrappers + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment + const txhash: string = await client.sendBitcoin(recipient, satoshis) + return txhash + }, + onSuccess: onDepositBTCSuccess, + onError: onDepositBTCError, + }) const handledDepositBTC = useCallback(async () => { if (!tokenAmount?.amount || !btcAddress || !depositReceipt) return diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx index e94cb4287..a442881e0 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useState } from "react" import { useActionFlowTxHash, useAppDispatch, - useExecuteFunction, useFetchActivities, useStakeFlowContext, } from "#/hooks" @@ -10,6 +9,7 @@ import { PROCESS_STATUSES } from "#/types" import { logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" import { UnexpectedErrorModalBase } from "#/components/UnexpectedErrorModal" +import { useMutation } from "@tanstack/react-query" import ServerErrorModal from "./ServerErrorModal" import RetryModal from "./RetryModal" import LoadingModal from "../../LoadingModal" @@ -24,7 +24,6 @@ export default function StakingErrorModal({ const fetchActivities = useFetchActivities() const txHash = useActionFlowTxHash() - const [isLoading, setIsLoading] = useState(false) const [isServerError, setIsServerError] = useState(false) const onStakeBTCSuccess = useCallback(() => { @@ -34,29 +33,21 @@ export default function StakingErrorModal({ const onStakeBTCError = useCallback(() => setIsServerError(true), []) - const handleStake = useExecuteFunction( - stake, - onStakeBTCSuccess, - onStakeBTCError, - ) + const { mutate: handleStake, status } = useMutation({ + mutationKey: ["stake"], + mutationFn: stake, + onSuccess: onStakeBTCSuccess, + onError: onStakeBTCError, + }) - const handleRetry = useCallback(async () => { - setIsLoading(true) - await handleStake() - setIsLoading(false) - }, [handleStake]) - - const handleRetryWrapper = useCallback( - () => logPromiseFailure(handleRetry()), - [handleRetry], - ) + const isLoading = status === "pending" if (isServerError) - return + return if (isLoading) return - if (txHash) return + if (txHash) return return } diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index 3fd8e628b..6a508a666 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -3,18 +3,18 @@ import { useActionFlowPause, useActionFlowTokenAmount, useAppDispatch, - useExecuteFunction, useInvalidateQueries, useModal, useTimeout, useTransactionDetails, } from "#/hooks" import { ACTION_FLOW_TYPES, PROCESS_STATUSES } from "#/types" -import { dateToUnixTimestamp, eip1193, logPromiseFailure } from "#/utils" +import { dateToUnixTimestamp, eip1193 } from "#/utils" import { setStatus } from "#/store/action-flow" import { useInitializeWithdraw } from "#/acre-react/hooks" import { ONE_SEC_IN_MILLISECONDS, queryKeysFactory } from "#/constants" import { activityInitialized } from "#/store/wallet" +import { useMutation } from "@tanstack/react-query" import BuildTransactionModal from "./BuildTransactionModal" import WalletInteractionModal from "../WalletInteractionModal" @@ -97,8 +97,9 @@ export default function SignMessageModal() { [onSignMessageError, handlePause], ) - const handleSignMessage = useExecuteFunction( - async () => { + const { mutate: handleSignMessage } = useMutation({ + mutationKey: ["sign-message"], + mutationFn: async () => { if (!amount) return const { redemptionKey } = await initializeWithdraw( @@ -141,13 +142,9 @@ export default function SignMessageModal() { }), ) }, - onSignMessageSuccess, + onSuccess: onSignMessageSuccess, onError, - ) - - const handleInitWithdrawAndSignMessageWrapper = useCallback(() => { - logPromiseFailure(handleSignMessage()) - }, [handleSignMessage]) + }) const onClose = () => { const currentSessionId = sessionId.current @@ -163,7 +160,7 @@ export default function SignMessageModal() { closeModal() } - useTimeout(handleInitWithdrawAndSignMessageWrapper, ONE_SEC_IN_MILLISECONDS) + useTimeout(handleSignMessage, ONE_SEC_IN_MILLISECONDS) if (status === "building-data") return diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index a049ac7a0..c9cf87f90 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -5,8 +5,6 @@ export * from "./useDetectThemeMode" export * from "./useSidebar" export * from "./useDocsDrawer" export * from "./useTransactionDetails" -export * from "./useDepositBTCTransaction" -export * from "./useExecuteFunction" export * from "./useStakeFlowContext" export * from "./useInitApp" export * from "./useCurrencyConversion" diff --git a/dapp/src/hooks/useDepositBTCTransaction.ts b/dapp/src/hooks/useDepositBTCTransaction.ts deleted file mode 100644 index c3eeb2e03..000000000 --- a/dapp/src/hooks/useDepositBTCTransaction.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { OnErrorCallback, OnSuccessCallback } from "#/types" -import { useMutation } from "@tanstack/react-query" -import { useWallet } from "./useWallet" -import { useConnector } from "./orangeKit/useConnector" - -export function useDepositBTCTransaction( - onSuccess?: OnSuccessCallback, - onError?: OnErrorCallback, -) { - const connector = useConnector() - const { address } = useWallet() - - const { - mutate: sendBitcoinTransaction, - status, - data, - } = useMutation({ - mutationKey: ["send-bitcoin"], - mutationFn: async ({ - recipient, - amount, - }: { - recipient: string - amount: bigint - }) => { - if (!address) { - throw new Error("Bitcoin account was not connected.") - } - - if (!connector) { - throw new Error("Connector was not defined.") - } - // @ts-expect-error adjust types to handle bitcoin wallet wrappers - const client = await connector.getClient() - - const satoshis = Number(amount) - - // @ts-expect-error adjust types to handle bitcoin wallet wrappers - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment - const txhash: string = await client.sendBitcoin(recipient, satoshis) - return txhash - }, - onSuccess: (responseData) => onSuccess?.(responseData), - onError: (error) => { - onError?.(error) - console.error(error) - }, - }) - - return { sendBitcoinTransaction, status, data } -} diff --git a/dapp/src/hooks/useExecuteFunction.ts b/dapp/src/hooks/useExecuteFunction.ts deleted file mode 100644 index a99ebcf0c..000000000 --- a/dapp/src/hooks/useExecuteFunction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback } from "react" -import { OnErrorCallback, OnSuccessCallback } from "#/types" - -type UseExecuteFunctionReturn = () => Promise - -export function useExecuteFunction< - F extends (...args: never[]) => Promise, ->( - fn: F, - onSuccess?: OnSuccessCallback, - onError?: OnErrorCallback, -): UseExecuteFunctionReturn { - return useCallback( - async (...args: Parameters) => { - try { - const response = await fn(...args) - - if (onSuccess) { - onSuccess(response) - } - } catch (error) { - if (onError) { - onError(error) - } - console.error(error) - } - }, - [fn, onError, onSuccess], - ) -} From 7d77844cccb306bc93571a8dc2701e1c18bada32 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 5 Nov 2024 13:50:04 +0100 Subject: [PATCH 15/16] Display an error message in console for deposit/withdrawal flow --- .../ActiveStakingStep/DepositBTCModal.tsx | 10 ++++++---- .../ActiveUnstakingStep/SignMessageModal.tsx | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 571db3495..e053e0373 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -37,7 +37,10 @@ export default function DepositBTCModal() { }, [dispatch, handleBitcoinBalanceInvalidation]) const onError = useCallback( - () => dispatch(setStatus(PROCESS_STATUSES.FAILED)), + (error: unknown) => { + console.error(error) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch], ) @@ -61,8 +64,7 @@ export default function DepositBTCModal() { if (eip1193.didUserRejectRequest(error)) { handlePause() } else { - onError() - console.error(error) + onError(error) } }, [onError, handlePause], @@ -111,7 +113,7 @@ export default function DepositBTCModal() { amount: tokenAmount?.amount, }) } else { - onError() + onError("Invalid deposit address") } }, [ tokenAmount?.amount, diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index 6a508a666..915a9436a 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -80,9 +80,13 @@ export default function SignMessageModal() { dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) }, [dispatch, handleBitcoinPositionInvalidation]) - const onSignMessageError = useCallback(() => { - dispatch(setStatus(PROCESS_STATUSES.FAILED)) - }, [dispatch]) + const onSignMessageError = useCallback( + (error: unknown) => { + console.error(error) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, + [dispatch], + ) const onError = useCallback( (error: unknown) => { @@ -91,7 +95,7 @@ export default function SignMessageModal() { if (eip1193.didUserRejectRequest(error)) { handlePause() } else { - onSignMessageError() + onSignMessageError(error) } }, [onSignMessageError, handlePause], From 1440e0796c9e8561e2fb8b22ac3a0c482670cd18 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 7 Nov 2024 09:49:58 +0100 Subject: [PATCH 16/16] Keep logic in `useDepositBTCTransaction` --- .../ActiveStakingStep/DepositBTCModal.tsx | 32 +------------- dapp/src/hooks/index.ts | 1 + dapp/src/hooks/useDepositBTCTransaction.ts | 42 +++++++++++++++++++ 3 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 dapp/src/hooks/useDepositBTCTransaction.ts diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index e053e0373..08926094e 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -3,11 +3,10 @@ import { useActionFlowPause, useActionFlowTokenAmount, useAppDispatch, - useConnector, + useDepositBTCTransaction, useInvalidateQueries, useStakeFlowContext, useVerifyDepositAddress, - useWallet, } from "#/hooks" import { eip1193, logPromiseFailure } from "#/utils" import { PROCESS_STATUSES } from "#/types" @@ -20,8 +19,6 @@ import WalletInteractionModal from "../WalletInteractionModal" const { userKeys } = queryKeysFactory export default function DepositBTCModal() { - const connector = useConnector() - const { address } = useWallet() const tokenAmount = useActionFlowTokenAmount() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const verifyDepositAddress = useVerifyDepositAddress() @@ -70,32 +67,7 @@ export default function DepositBTCModal() { [onError, handlePause], ) - const { mutate: sendBitcoinTransaction, status } = useMutation({ - mutationKey: ["send-bitcoin"], - mutationFn: async ({ - recipient, - amount, - }: { - recipient: string - amount: bigint - }) => { - if (!address) { - throw new Error("Bitcoin account was not connected.") - } - - if (!connector) { - throw new Error("Connector was not defined.") - } - // @ts-expect-error adjust types to handle bitcoin wallet wrappers - const client = await connector.getClient() - - const satoshis = Number(amount) - - // @ts-expect-error adjust types to handle bitcoin wallet wrappers - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment - const txhash: string = await client.sendBitcoin(recipient, satoshis) - return txhash - }, + const { mutate: sendBitcoinTransaction, status } = useDepositBTCTransaction({ onSuccess: onDepositBTCSuccess, onError: onDepositBTCError, }) diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index c9cf87f90..b763211d6 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -35,3 +35,4 @@ export { default as useSignMessageAndCreateSession } from "./useSignMessageAndCr export { default as useScrollbarVisibility } from "./useScrollbarVisibility" export { default as useAccessCode } from "./useAccessCode" export { default as useFormField } from "./useFormField" +export { default as useDepositBTCTransaction } from "./useDepositBTCTransaction" diff --git a/dapp/src/hooks/useDepositBTCTransaction.ts b/dapp/src/hooks/useDepositBTCTransaction.ts new file mode 100644 index 000000000..e56f20828 --- /dev/null +++ b/dapp/src/hooks/useDepositBTCTransaction.ts @@ -0,0 +1,42 @@ +import { useMutation, UseMutationOptions } from "@tanstack/react-query" +import { useConnector } from "./orangeKit" +import { useWallet } from "./useWallet" + +type MutationFnParams = { + recipient: string + amount: bigint +} + +type UseDepositBTCTransactionOptions = Omit< + UseMutationOptions, + "mutationKey" | "mutationFn" +> + +export default function useDepositBTCTransaction( + options: UseDepositBTCTransactionOptions, +) { + const connector = useConnector() + const { address } = useWallet() + return useMutation({ + mutationKey: ["send-bitcoin"], + mutationFn: async ({ recipient, amount }: MutationFnParams) => { + if (!address) { + throw new Error("Bitcoin account was not connected.") + } + + if (!connector) { + throw new Error("Connector was not defined.") + } + // @ts-expect-error adjust types to handle bitcoin wallet wrappers + const client = await connector.getClient() + + const satoshis = Number(amount) + + // @ts-expect-error adjust types to handle bitcoin wallet wrappers + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment + const txhash: string = await client.sendBitcoin(recipient, satoshis) + return txhash + }, + ...options, + }) +}