From aa6b049151dfa2c734a38b222ea0cef14fae78b9 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Wed, 18 Oct 2023 16:26:44 +0200 Subject: [PATCH] fix: WC signatures + subsequent transactions (#2650) * fix: reject requests on flow change * fix: success doesn't quit + unsubscriptions * refactor: cleanup code * fix: change event payload + use callback syntax --- src/components/tx-flow/index.tsx | 24 ++++++-- src/services/safe-wallet-provider/index.ts | 2 +- .../useSafeWalletProvider.tsx | 59 +++++++++++++++---- src/services/tx/txEvents.ts | 2 + 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/components/tx-flow/index.tsx b/src/components/tx-flow/index.tsx index eebcbb0338..adbe287136 100644 --- a/src/components/tx-flow/index.tsx +++ b/src/components/tx-flow/index.tsx @@ -2,6 +2,8 @@ import { createContext, type ReactElement, type ReactNode, useState, useEffect, import TxModalDialog from '@/components/common/TxModalDialog' import { usePathname } from 'next/navigation' import useSafeInfo from '@/hooks/useSafeInfo' +import { txDispatch, TxEvent } from '@/services/tx/txEvents' +import { SuccessScreen } from './flows/SuccessScreen' const noop = () => {} @@ -41,18 +43,28 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle } const ok = confirm('Closing this window will discard your current progress.') - if (ok) { - handleModalClose() - } + if (!ok) return + + // Reject if the flow is closed + txDispatch(TxEvent.USER_QUIT, {}) + + handleModalClose() }, [shouldWarn, handleModalClose]) const setTxFlow = useCallback( - (txFlow: TxModalContextType['txFlow'], onClose?: () => void, shouldWarn?: boolean) => { - setFlow(txFlow) + (newTxFlow: TxModalContextType['txFlow'], onClose?: () => void, shouldWarn?: boolean) => { + setFlow((prevFlow) => { + // Reject if a flow is open and the user changes to a different one + if (prevFlow && prevFlow !== newTxFlow && newTxFlow?.type !== SuccessScreen) { + txDispatch(TxEvent.USER_QUIT, {}) + } + + return newTxFlow + }) setOnClose(() => onClose ?? noop) setShouldWarn(shouldWarn ?? true) }, - [setFlow, setOnClose], + [], ) // Show the confirmation dialog if user navigates diff --git a/src/services/safe-wallet-provider/index.ts b/src/services/safe-wallet-provider/index.ts index 03177d9b5c..d7b3b1bfde 100644 --- a/src/services/safe-wallet-provider/index.ts +++ b/src/services/safe-wallet-provider/index.ts @@ -27,7 +27,7 @@ interface RpcRequest { params?: unknown[] } -enum RpcErrorCode { +export enum RpcErrorCode { INVALID_PARAMS = -32602, USER_REJECTED = 4001, UNSUPPORTED_METHOD = 4200, diff --git a/src/services/safe-wallet-provider/useSafeWalletProvider.tsx b/src/services/safe-wallet-provider/useSafeWalletProvider.tsx index bc4904d191..638f0c62a0 100644 --- a/src/services/safe-wallet-provider/useSafeWalletProvider.tsx +++ b/src/services/safe-wallet-provider/useSafeWalletProvider.tsx @@ -2,6 +2,7 @@ import { useContext, useEffect, useMemo, useRef } from 'react' import { BigNumber } from 'ethers' import { useRouter } from 'next/router' +import { RpcErrorCode } from '.' import type { AppInfo, WalletSDK } from '.' import { SafeWalletProvider } from '.' import useSafeInfo from '@/hooks/useSafeInfo' @@ -51,12 +52,28 @@ export const _useTxFlowApi = (chainId: string, safeAddress: string): WalletSDK | const { title, options } = NotificationMessages.SIGNATURE_REQUEST(appInfo) showNotification(title, options) - return new Promise((resolve) => { - const unsubscribe = safeMsgSubscribe(SafeMsgEvent.SIGNATURE_PREPARED, ({ requestId, signature }) => { - if (requestId === id) { - resolve({ signature }) - unsubscribe() - } + return new Promise((resolve, reject) => { + const unsubscribe = () => { + unsubscribeSignaturePrepared() + unsubscribeUserQuit() + } + + const unsubscribeSignaturePrepared = safeMsgSubscribe( + SafeMsgEvent.SIGNATURE_PREPARED, + ({ requestId, signature }) => { + if (requestId === id) { + resolve({ signature }) + unsubscribe() + } + }, + ) + + const unsubscribeUserQuit = txSubscribe(TxEvent.USER_QUIT, () => { + reject({ + code: RpcErrorCode.USER_REJECTED, + message: 'User rejected signature request', + }) + unsubscribe() }) }) } @@ -101,13 +118,29 @@ export const _useTxFlowApi = (chainId: string, safeAddress: string): WalletSDK | const { title, options } = NotificationMessages.TRANSACTION_REQUEST(appInfo) showNotification(title, options) - return new Promise((resolve) => { - const unsubscribe = txSubscribe(TxEvent.SAFE_APPS_REQUEST, async ({ safeAppRequestId, safeTxHash, txId }) => { - if (safeAppRequestId === id) { - const txHash = txId ? pendingTxs.current[txId] : undefined - resolve({ safeTxHash, txHash }) - unsubscribe() - } + return new Promise((resolve, reject) => { + const unsubscribe = () => { + unsubscribeSignaturePrepared() + unsubscribeUserQuit() + } + + const unsubscribeSignaturePrepared = txSubscribe( + TxEvent.SAFE_APPS_REQUEST, + async ({ safeAppRequestId, safeTxHash, txId }) => { + if (safeAppRequestId === id) { + const txHash = txId ? pendingTxs.current[txId] : undefined + resolve({ safeTxHash, txHash }) + unsubscribe() + } + }, + ) + + const unsubscribeUserQuit = txSubscribe(TxEvent.USER_QUIT, () => { + reject({ + code: RpcErrorCode.USER_REJECTED, + message: 'User rejected transaction', + }) + unsubscribe() }) }) }, diff --git a/src/services/tx/txEvents.ts b/src/services/tx/txEvents.ts index b7bd037f1c..7d8c2b57da 100644 --- a/src/services/tx/txEvents.ts +++ b/src/services/tx/txEvents.ts @@ -2,6 +2,7 @@ import EventBus from '@/services/EventBus' import type { RequestId } from '@safe-global/safe-apps-sdk' export enum TxEvent { + USER_QUIT = 'USER_QUIT', SIGNED = 'SIGNED', SIGN_FAILED = 'SIGN_FAILED', PROPOSED = 'PROPOSED', @@ -27,6 +28,7 @@ type Id = { txId: string; groupKey?: string } | { txId?: string; groupKey: strin type HumanDescription = { humanDescription?: string } interface TxEvents { + [TxEvent.USER_QUIT]: {} [TxEvent.SIGNED]: { txId?: string } [TxEvent.SIGN_FAILED]: HumanDescription & { txId?: string; error: Error } [TxEvent.PROPOSE_FAILED]: HumanDescription & { error: Error }