From 0e23a3ab769b4d9abe0510857ff3508a26535103 Mon Sep 17 00:00:00 2001 From: prxt6529 Date: Thu, 5 Mar 2026 10:24:34 +0200 Subject: [PATCH 1/3] Mitigate account switch potential freezing Signed-off-by: prxt6529 --- .../SeizeConnectContext.switch-sync.test.tsx | 184 ++++++++++++++++++ components/auth/SeizeConnectContext.tsx | 29 ++- 2 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 __tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx diff --git a/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx b/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx new file mode 100644 index 0000000000..d3942aaef1 --- /dev/null +++ b/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx @@ -0,0 +1,184 @@ +import { + SeizeConnectProvider, + useSeizeConnectContext, +} from "@/components/auth/SeizeConnectContext"; +import * as authUtils from "@/services/auth/auth.utils"; +import { render, screen, waitFor } from "@testing-library/react"; +import React from "react"; + +jest.mock("@reown/appkit/react", () => ({ + useAppKit: jest.fn(() => ({ + open: jest.fn(), + })), + useAppKitAccount: jest.fn(() => ({ + address: undefined, + isConnected: false, + status: "disconnected", + })), + useAppKitState: jest.fn(() => ({ + open: false, + })), + useDisconnect: jest.fn(() => ({ + disconnect: jest.fn(), + })), + useWalletInfo: jest.fn(() => ({ + walletInfo: null, + })), +})); + +jest.mock("viem", () => ({ + isAddress: jest.fn((address: string) => /^0x[a-fA-F0-9]{40}$/.test(address)), + getAddress: jest.fn((address: string) => address.toLowerCase()), +})); + +jest.mock("@/hooks/useConnectedAccountsUnreadNotifications", () => ({ + useConnectedAccountsUnreadNotifications: jest.fn(() => ({})), +})); + +jest.mock("@/services/auth/auth.utils", () => ({ + canStoreAnotherWalletAccount: jest.fn(() => true), + getConnectedWalletAccounts: jest.fn(() => []), + getWalletAddress: jest.fn(() => null), + setActiveWalletAccount: jest.fn(() => true), + removeAuthJwt: jest.fn(), + WALLET_ACCOUNTS_UPDATED_EVENT: "6529-wallet-accounts-updated", + PROFILE_SWITCHED_EVENT: "6529-profile-switched", +})); + +const addressA = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +const addressB = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +const unknownAddress = "0xcccccccccccccccccccccccccccccccccccccccc"; + +const AddressProbe: React.FC = () => { + const { address } = useSeizeConnectContext(); + return
{address ?? "undefined"}
; +}; + +const buildStoredAccount = (address: string) => ({ + address, + role: null, + profileId: null, + profileHandle: null, +}); + +describe("SeizeConnectContext switch sync guard", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("prefers stored active account while provider still reports previous known account", async () => { + const { useAppKitAccount } = require("@reown/appkit/react"); + const mockGetWalletAddress = + authUtils.getWalletAddress as jest.MockedFunction< + typeof authUtils.getWalletAddress + >; + const mockGetConnectedWalletAccounts = + authUtils.getConnectedWalletAccounts as jest.MockedFunction< + typeof authUtils.getConnectedWalletAccounts + >; + + const accountState = { + address: addressA, + isConnected: true, + status: "connected", + }; + let activeStoredAddress = addressB; + + (useAppKitAccount as jest.Mock).mockImplementation(() => accountState); + mockGetWalletAddress.mockImplementation(() => activeStoredAddress); + mockGetConnectedWalletAccounts.mockReturnValue([ + buildStoredAccount(addressA), + buildStoredAccount(addressB), + ]); + + const { rerender } = render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId("active-address")).toHaveTextContent(addressB); + }); + + activeStoredAddress = addressA; + accountState.address = addressB; + + rerender( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId("active-address")).toHaveTextContent(addressA); + }); + }); + + it("keeps unknown-wallet connect flow preferring live provider address", async () => { + const { useAppKitAccount } = require("@reown/appkit/react"); + const mockGetWalletAddress = + authUtils.getWalletAddress as jest.MockedFunction< + typeof authUtils.getWalletAddress + >; + const mockGetConnectedWalletAccounts = + authUtils.getConnectedWalletAccounts as jest.MockedFunction< + typeof authUtils.getConnectedWalletAccounts + >; + + (useAppKitAccount as jest.Mock).mockReturnValue({ + address: unknownAddress, + isConnected: true, + status: "connected", + }); + mockGetWalletAddress.mockReturnValue(addressA); + mockGetConnectedWalletAccounts.mockReturnValue([ + buildStoredAccount(addressA), + buildStoredAccount(addressB), + ]); + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId("active-address")).toHaveTextContent( + unknownAddress + ); + }); + }); + + it("preserves normal single-account behavior", async () => { + const { useAppKitAccount } = require("@reown/appkit/react"); + const mockGetWalletAddress = + authUtils.getWalletAddress as jest.MockedFunction< + typeof authUtils.getWalletAddress + >; + const mockGetConnectedWalletAccounts = + authUtils.getConnectedWalletAccounts as jest.MockedFunction< + typeof authUtils.getConnectedWalletAccounts + >; + + (useAppKitAccount as jest.Mock).mockReturnValue({ + address: addressA, + isConnected: true, + status: "connected", + }); + mockGetWalletAddress.mockReturnValue(addressA); + mockGetConnectedWalletAccounts.mockReturnValue([ + buildStoredAccount(addressA), + ]); + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId("active-address")).toHaveTextContent(addressA); + }); + }); +}); diff --git a/components/auth/SeizeConnectContext.tsx b/components/auth/SeizeConnectContext.tsx index cfeb9ad1a1..4d611692c6 100644 --- a/components/auth/SeizeConnectContext.tsx +++ b/components/auth/SeizeConnectContext.tsx @@ -509,6 +509,8 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({ return; } + const activeStoredAddress = getWalletAddress(); + if ( account.address && account.isConnected && @@ -521,6 +523,31 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({ normalizeAddress(checksummedConnectedAddress) ); + if (isKnownStoredAccount && activeStoredAddress) { + const isActiveStoredAddressValid = isAddress(activeStoredAddress); + if (isActiveStoredAddressValid) { + const checksummedStoredActiveAddress = getAddress(activeStoredAddress); + const isStoredActiveKnownAccount = storedConnectedAccounts.some( + (storedAccount) => + normalizeAddress(storedAccount.address) === + normalizeAddress(checksummedStoredActiveAddress) + ); + const isSwitchTransition = + normalizeAddress(checksummedConnectedAddress) !== + normalizeAddress(checksummedStoredActiveAddress); + + if (isStoredActiveKnownAccount && isSwitchTransition) { + const isAlreadyConnected = + walletState.status === "connected" && + walletState.address === checksummedStoredActiveAddress; + if (!isAlreadyConnected) { + setConnected(checksummedStoredActiveAddress); + } + return; + } + } + } + // If wallet is connected to an address that is not in stored profiles yet, // prefer the live wallet address so auth can prompt and add it. if (!isKnownStoredAccount) { @@ -557,8 +584,6 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({ } } - const activeStoredAddress = getWalletAddress(); - if (activeStoredAddress && isAddress(activeStoredAddress)) { const checksummedStoredAddress = getAddress(activeStoredAddress); const isAlreadyConnected = From ef3f81b7b2ce523cf6d258c9d771f09c2e32dc3c Mon Sep 17 00:00:00 2001 From: prxt6529 Date: Thu, 5 Mar 2026 10:29:32 +0200 Subject: [PATCH 2/3] WIP Signed-off-by: prxt6529 --- components/auth/SeizeConnectContext.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/auth/SeizeConnectContext.tsx b/components/auth/SeizeConnectContext.tsx index 4d611692c6..6fb45c0f34 100644 --- a/components/auth/SeizeConnectContext.tsx +++ b/components/auth/SeizeConnectContext.tsx @@ -526,7 +526,8 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({ if (isKnownStoredAccount && activeStoredAddress) { const isActiveStoredAddressValid = isAddress(activeStoredAddress); if (isActiveStoredAddressValid) { - const checksummedStoredActiveAddress = getAddress(activeStoredAddress); + const checksummedStoredActiveAddress = + getAddress(activeStoredAddress); const isStoredActiveKnownAccount = storedConnectedAccounts.some( (storedAccount) => normalizeAddress(storedAccount.address) === From e009984ea3836401a38cb85c39de678b3245d227 Mon Sep 17 00:00:00 2001 From: prxt6529 Date: Thu, 5 Mar 2026 10:41:02 +0200 Subject: [PATCH 3/3] WIP Signed-off-by: prxt6529 --- .../auth/SeizeConnectContext.switch-sync.test.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx b/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx index d3942aaef1..cef839694f 100644 --- a/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx +++ b/__tests__/components/auth/SeizeConnectContext.switch-sync.test.tsx @@ -54,9 +54,13 @@ const AddressProbe: React.FC = () => { return
{address ?? "undefined"}
; }; -const buildStoredAccount = (address: string) => ({ +const buildStoredAccount = ( + address: string +): authUtils.ConnectedWalletAccount => ({ address, + refreshToken: "dummy-refresh-token", role: null, + jwt: null, profileId: null, profileHandle: null, });