From cfecc26e326001148aa3a0e79e6a4f70e46617fb Mon Sep 17 00:00:00 2001 From: Aleksandr Makhnev Date: Mon, 11 Nov 2024 17:07:05 +0500 Subject: [PATCH] fix: wallet connect reconnect (#2610) --- .../model/wallet-connect-model.ts | 97 ++++++++++--------- .../OperationSign/lib/operation-sign-utils.ts | 5 + .../operations/OperationSign/lib/types.ts | 1 + .../OperationSign/model/sign-wc-model.ts | 12 ++- .../OperationSign/ui/WalletConnect.tsx | 12 +++ .../model/wc-onboarding-model.ts | 2 +- src/renderer/shared/i18n/locales/en.json | 6 +- .../WalletDetails/model/wc-details-model.ts | 15 ++- 8 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/renderer/entities/walletConnect/model/wallet-connect-model.ts b/src/renderer/entities/walletConnect/model/wallet-connect-model.ts index 00f4da2a41..3d021f4802 100644 --- a/src/renderer/entities/walletConnect/model/wallet-connect-model.ts +++ b/src/renderer/entities/walletConnect/model/wallet-connect-model.ts @@ -50,6 +50,51 @@ const $uri = createStore('').reset(disconnectCurrentSessionStarted); const $accounts = createStore([]).reset(reset); const $pairings = createStore([]).reset(reset); +const createClientFx = createEffect(async (): Promise => { + return Client.init({ + logger: DEFAULT_LOGGER, + relayUrl: DEFAULT_RELAY_URL, + projectId: DEFAULT_PROJECT_ID, + metadata: DEFAULT_APP_METADATA, + }); +}); + +type InitConnectResult = { + uri?: string; + approval: () => Promise; +}; +const initConnectFx = createEffect(({ client, chains, pairing }: InitConnectParams): Promise => { + const optionalNamespaces = { + polkadot: { + chains, + methods: [DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION], + events: [DEFAULT_POLKADOT_EVENTS.CHAIN_CHANGED, DEFAULT_POLKADOT_EVENTS.ACCOUNTS_CHANGED], + }, + }; + + return client.connect({ pairingTopic: pairing?.topic, optionalNamespaces }); +}); + +type ConnectParams = { + client: Client; + approval: () => Promise; + onConnect?: () => void; +}; +type ConnectResult = { + pairings: PairingTypes.Struct[]; + session: SessionTypes.Struct; +}; +const connectFx = createEffect(async ({ client, approval }: ConnectParams): Promise => { + const session = await approval(); + + console.log('Established session:', session); + + return { + pairings: client.pairing.getAll({ active: true }), + session: session as SessionTypes.Struct, + }; +}); + const extendSessionsFx = createEffect((client: Client) => { const sessions = client.session.getAll(); @@ -134,15 +179,6 @@ const sessionTopicUpdatedFx = createEffect( }, ); -const createClientFx = createEffect(async (): Promise => { - return Client.init({ - logger: DEFAULT_LOGGER, - relayUrl: DEFAULT_RELAY_URL, - projectId: DEFAULT_PROJECT_ID, - metadata: DEFAULT_APP_METADATA, - }); -}); - const removePairingFx = createEffect(async ({ client, topic }: { client: Client; topic: string }): Promise => { const reason = getSdkError('USER_DISCONNECTED'); @@ -171,42 +207,6 @@ const updateWcAccountsFx = createEffect( }, ); -type InitConnectResult = { - uri?: string; - approval: () => Promise; -}; -const initConnectFx = createEffect(({ client, chains, pairing }: InitConnectParams): Promise => { - const optionalNamespaces = { - polkadot: { - chains, - methods: [DEFAULT_POLKADOT_METHODS.POLKADOT_SIGN_TRANSACTION], - events: [DEFAULT_POLKADOT_EVENTS.CHAIN_CHANGED, DEFAULT_POLKADOT_EVENTS.ACCOUNTS_CHANGED], - }, - }; - - return client.connect({ pairingTopic: pairing?.topic, optionalNamespaces }); -}); - -type ConnectParams = { - client: Client; - approval: () => Promise; - onConnect?: () => void; -}; -type ConnectResult = { - pairings: PairingTypes.Struct[]; - session: SessionTypes.Struct; -}; -const connectFx = createEffect(async ({ client, approval }: ConnectParams): Promise => { - const session = await approval(); - - console.log('Established session:', session); - - return { - pairings: client.pairing.getAll({ active: true }), - session: session as SessionTypes.Struct, - }; -}); - type DisconnectParams = { client: Client; session: SessionTypes.Struct; @@ -348,15 +348,15 @@ sample({ sample({ clock: disconnectCurrentSessionStarted, source: $session, - filter: (session: SessionTypes.Struct | null): session is SessionTypes.Struct => session !== null, - fn: (session) => session.topic, + filter: (session) => nonNullable(session), + fn: (session) => session!.topic, target: disconnectStarted, }); sample({ clock: disconnectStarted, source: $client, - filter: (client, sessionTopic) => Boolean(client?.session.get(sessionTopic)), + filter: (client, sessionTopic) => nonNullable(client?.session.get(sessionTopic)), fn: (client, sessionTopic) => ({ client: client!, session: client!.session.get(sessionTopic)!, @@ -461,6 +461,7 @@ export const walletConnectModel = { connectionRejected, currentSessionTopicUpdated, sessionTopicUpdated, + sessionTopicUpdateFailed: sessionTopicUpdatedFx.fail, sessionTopicUpdateDone: sessionTopicUpdatedFx.doneData, accountsUpdated, accountsUpdateDone: updateWcAccountsFx.doneData, diff --git a/src/renderer/features/operations/OperationSign/lib/operation-sign-utils.ts b/src/renderer/features/operations/OperationSign/lib/operation-sign-utils.ts index 26be003697..241ba094a5 100644 --- a/src/renderer/features/operations/OperationSign/lib/operation-sign-utils.ts +++ b/src/renderer/features/operations/OperationSign/lib/operation-sign-utils.ts @@ -9,6 +9,7 @@ export const operationSignUtils = { isReconnectingStep, isConnectedStep, isRejectedStep, + isFailedStep, isReadyToReconnectStep, isTopicExist, transformEcdsaSignature, @@ -30,6 +31,10 @@ function isReadyToReconnectStep(step: ReconnectStep): boolean { return step === ReconnectStep.READY_TO_RECONNECT; } +function isFailedStep(step: ReconnectStep): boolean { + return step === ReconnectStep.FAILED; +} + function isTopicExist(session?: SessionTypes.Struct | null): boolean { return Boolean(session?.topic); } diff --git a/src/renderer/features/operations/OperationSign/lib/types.ts b/src/renderer/features/operations/OperationSign/lib/types.ts index fb0e740224..66bd42c07c 100644 --- a/src/renderer/features/operations/OperationSign/lib/types.ts +++ b/src/renderer/features/operations/OperationSign/lib/types.ts @@ -8,6 +8,7 @@ export const enum ReconnectStep { READY_TO_RECONNECT, RECONNECTING, REJECTED, + FAILED, SUCCESS, } diff --git a/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts b/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts index 05a485ba40..f824480733 100644 --- a/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts +++ b/src/renderer/features/operations/OperationSign/model/sign-wc-model.ts @@ -43,6 +43,7 @@ const $isStatusShown = combine( operationSignUtils.isReconnectingStep(reconnectStep) || operationSignUtils.isConnectedStep(reconnectStep) || operationSignUtils.isRejectedStep(reconnectStep) || + operationSignUtils.isFailedStep(reconnectStep) || isSigningRejected ); }, @@ -95,6 +96,14 @@ sample({ target: walletConnectModel.events.connect, }); +sample({ + clock: [walletConnectModel.events.initConnectFailed, walletConnectModel.events.sessionTopicUpdateFailed], + source: $reconnectStep, + filter: (step) => step === ReconnectStep.RECONNECTING, + fn: () => ReconnectStep.FAILED, + target: $reconnectStep, +}); + sample({ clock: walletConnectModel.events.connected, source: { @@ -118,7 +127,8 @@ sample({ sample({ clock: combineEvents({ - events: [reconnectStarted, walletConnectModel.events.sessionTopicUpdateDone, walletConnectModel.events.connected], + events: [walletConnectModel.events.sessionTopicUpdateDone], + reset: reconnectStarted, }), source: { signer: operationSignModel.$signer, diff --git a/src/renderer/features/operations/OperationSign/ui/WalletConnect.tsx b/src/renderer/features/operations/OperationSign/ui/WalletConnect.tsx index a5b560d35d..c73e2843bc 100644 --- a/src/renderer/features/operations/OperationSign/ui/WalletConnect.tsx +++ b/src/renderer/features/operations/OperationSign/ui/WalletConnect.tsx @@ -190,6 +190,18 @@ export const WalletConnect = ({ apis, signingPayloads, validateBalance, onGoBack }; } + if (operationSignUtils.isFailedStep(reconnectStep)) { + return { + title: t('operation.walletConnect.failedTitle'), + description: t('operation.walletConnect.failedDescription'), + content: , + onClose: () => { + signWcModel.events.reconnectAborted(); + onGoBack(); + }, + }; + } + if (isSigningRejected) { return { title: t('operation.walletConnect.rejected'), diff --git a/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts b/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts index fa2bdbd45e..441860b64d 100644 --- a/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts +++ b/src/renderer/pages/Onboarding/WalletConnect/model/wc-onboarding-model.ts @@ -3,7 +3,7 @@ import { createEvent, createStore, sample } from 'effector'; import { walletConnectModel } from '@/entities/walletConnect'; import { Step } from '../lib/constants'; -const $step = createStore(Step.CLOSED).reset([walletConnectModel.events.disconnectCurrentSessionStarted]); +const $step = createStore(Step.CLOSED).reset(walletConnectModel.events.disconnectCurrentSessionStarted); const onboardingStarted = createEvent(); diff --git a/src/renderer/shared/i18n/locales/en.json b/src/renderer/shared/i18n/locales/en.json index 1b4cc96e62..d1815b02d3 100644 --- a/src/renderer/shared/i18n/locales/en.json +++ b/src/renderer/shared/i18n/locales/en.json @@ -1011,6 +1011,8 @@ "title": "{ walletName } is not connected" }, "rejected": "Rejected", + "failedTitle": "Something went wrong", + "failedDescription": "Please try again later or create a new wallet", "signTitle_one": "Confirm the operation on your { walletName } app", "signTitle_other": "Confirm { count } operations on your { walletName } app", "tryAgainButton": "Try again" @@ -1682,8 +1684,8 @@ "refreshButton": "Refresh", "rejectDescription": "The operation was successfully canceled", "rejectTitle": "Rejected", - "failedDescription": "Something went wrong", - "failedTitle": "Failed" + "failedDescription": "Please try again later or create a new wallet", + "failedTitle": "Something went wrong" } }, "wallets": { diff --git a/src/renderer/widgets/WalletDetails/model/wc-details-model.ts b/src/renderer/widgets/WalletDetails/model/wc-details-model.ts index 52866d81e9..37c0e8f017 100644 --- a/src/renderer/widgets/WalletDetails/model/wc-details-model.ts +++ b/src/renderer/widgets/WalletDetails/model/wc-details-model.ts @@ -56,8 +56,9 @@ sample({ wallet: walletSelectModel.$walletForDetails, session: walletConnectModel.$session, }, - filter: ({ step, wallet, session }) => - step === ReconnectStep.RECONNECTING && Boolean(wallet) && Boolean(session?.topic), + filter: ({ step, wallet, session }) => { + return step === ReconnectStep.RECONNECTING && Boolean(wallet) && Boolean(session?.topic); + }, fn: ({ wallet, session }) => ({ accounts: wallet!.accounts, topic: session!.topic, @@ -67,7 +68,8 @@ sample({ sample({ clock: combineEvents({ - events: [reconnectStarted, walletConnectModel.events.sessionTopicUpdateDone, walletConnectModel.events.connected], + events: [walletConnectModel.events.sessionTopicUpdateDone], + reset: reconnectStarted, }), source: { wallet: walletSelectModel.$walletForDetails, @@ -77,10 +79,13 @@ sample({ filter: ({ wallet }) => Boolean(wallet), fn: ({ wallet, newAccounts, chains }) => { const updatedAccounts: WcAccount[] = []; + const chainIds = Object.keys(chains); for (const newAccount of newAccounts) { const [_, chainId, address] = newAccount.split(':'); - const chain = chains[chainId as ChainId]; + + const fullChainId = chainIds.find((chain) => chain.includes(chainId)); + const chain = fullChainId && chains[fullChainId as ChainId]; if (!chain) continue; @@ -106,7 +111,7 @@ sample({ }); sample({ - clock: walletConnectModel.events.initConnectFailed, + clock: [walletConnectModel.events.initConnectFailed, walletConnectModel.events.sessionTopicUpdateFailed], source: $reconnectStep, filter: (step) => step === ReconnectStep.RECONNECTING, fn: () => ReconnectStep.FAILED,