From e8a69e88a6e65af67d9989ca3b3595712f4de81f Mon Sep 17 00:00:00 2001 From: Mohammed Odeh Date: Tue, 11 Nov 2025 17:30:36 -0500 Subject: [PATCH 1/5] optimize --- packages/core/src/__clients__/core.ts | 97 +- packages/core/src/__types__/method-types.ts | 4 + .../src/providers/client/Provider.tsx | 1174 +++++++++-------- packages/react-wallet-kit/src/utils/utils.ts | 18 +- 4 files changed, 730 insertions(+), 563 deletions(-) diff --git a/packages/core/src/__clients__/core.ts b/packages/core/src/__clients__/core.ts index 5c8d69261..5a81216cf 100644 --- a/packages/core/src/__clients__/core.ts +++ b/packages/core/src/__clients__/core.ts @@ -566,12 +566,13 @@ export class TurnkeyClient { expirationSeconds, }); - await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey!); - - await this.storeSession({ - sessionToken: sessionResponse.session, - sessionKey, - }); + await Promise.all([ + this.apiKeyStamper?.deleteKeyPair(generatedPublicKey!), + this.storeSession({ + sessionToken: sessionResponse.session, + sessionKey, + }), + ]); generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup @@ -1119,12 +1120,13 @@ export class TurnkeyClient { expirationSeconds, }); - await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey!); - - await this.storeSession({ - sessionToken: sessionResponse.session, - sessionKey, - }); + await Promise.all([ + this.apiKeyStamper?.deleteKeyPair(generatedPublicKey!), + this.storeSession({ + sessionToken: sessionResponse.session, + sessionKey, + }), + ]); generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup @@ -1939,11 +1941,25 @@ export class TurnkeyClient { async () => { let embedded: EmbeddedWallet[] = []; + const organizationId = + organizationIdFromParams || session?.organizationId; + const userId = userIdFromParams || session?.userId; + + // we start fetching user early if we have the required params (needed for connected wallets) + // this runs in parallel with the embedded wallet fetching below + let userPromise: + | Promise<{ ethereum: string[]; solana: string[] }> + | undefined; + if (organizationId && userId && this.walletManager?.connector) { + userPromise = this.fetchUser({ + userId, + organizationId, + stampWith, + }).then(getAuthenticatorAddresses); + } + // if connectedOnly is true, we skip fetching embedded wallets if (!connectedOnly) { - const organizationId = - organizationIdFromParams || session?.organizationId; - if (!organizationId) { throw new TurnkeyError( "No organization ID provided and no active session found. Please log in first or pass in an organization ID.", @@ -1951,7 +1967,6 @@ export class TurnkeyClient { ); } - const userId = userIdFromParams || session?.userId; if (!userId) { throw new TurnkeyError( "No user ID provided and no active session found. Please log in first or pass in a user ID.", @@ -1959,18 +1974,19 @@ export class TurnkeyClient { ); } - const accounts = await fetchAllWalletAccountsWithCursor( - this.httpClient, - organizationId, - stampWith, - ); - - const walletsRes = await this.httpClient.getWallets( - { + const [accounts, walletsRes] = await Promise.all([ + fetchAllWalletAccountsWithCursor( + this.httpClient, organizationId, - }, - stampWith, - ); + stampWith, + ), + this.httpClient.getWallets( + { + organizationId, + }, + stampWith, + ), + ]); // create a map of walletId to EmbeddedWallet for easy lookup const walletMap: Map = new Map( @@ -2006,6 +2022,16 @@ export class TurnkeyClient { groupedProviders.set(walletId, group); } + // we fetch user once for all connected wallets to avoid duplicate `fetchUser` calls + // this is only done if we have `organizationId` and `userId` + // Note: this was started earlier in parallel with embedded wallet fetching for performance + let authenticatorAddresses: + | { ethereum: string[]; solana: string[] } + | undefined; + if (userPromise) { + authenticatorAddresses = await userPromise; + } + // has to be done in a for of loop so we can await each fetchWalletAccounts call individually // otherwise await Promise.all would cause them all to fire at once breaking passkey only set ups // (multiple wallet fetches at once causing "OperationError: A request is already pending.") @@ -2032,6 +2058,7 @@ export class TurnkeyClient { organizationId: organizationIdFromParams, }), ...(userIdFromParams !== undefined && { userId: userIdFromParams }), + ...(authenticatorAddresses && { authenticatorAddresses }), }); wallet.accounts = accounts; @@ -2064,6 +2091,7 @@ export class TurnkeyClient { * @param params.stampWith - parameter to stamp the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet). * @param params.organizationId - organization ID to target (defaults to the session's organization ID). * @param params.userId - user ID to target (defaults to the session's user ID). + * @param params.authenticatorAddresses - optional authenticator addresses to avoid redundant user fetches (this is used for connected wallets to determine if a connected wallet is an authenticator) * * @returns A promise that resolves to an array of `v1WalletAccount` objects. * @throws {TurnkeyError} If no active session is found or if there is an error fetching wallet accounts. @@ -2159,9 +2187,12 @@ export class TurnkeyClient { let ethereumAddresses: string[] = []; let solanaAddresses: string[] = []; - // we only fetch the user if we have to the organizationId and userId - // if not that means `isAuthenticator` will always be false - if (organizationId && userId) { + if (params.authenticatorAddresses) { + ({ ethereum: ethereumAddresses, solana: solanaAddresses } = + params.authenticatorAddresses); + } else if (organizationId && userId) { + // we only fetch the user if authenticator addresses aren't provided and we have the organizationId and userId + // if not, then that means `isAuthenticator` will always be false const user = await this.fetchUser({ userId, organizationId, @@ -4212,8 +4243,10 @@ export class TurnkeyClient { async () => { const session = await this.storageManager.getSession(sessionKey); if (session) { - await this.apiKeyStamper?.deleteKeyPair(session.publicKey!); - await this.storageManager.clearSession(sessionKey); + await Promise.all([ + this.apiKeyStamper?.deleteKeyPair(session.publicKey!), + this.storageManager.clearSession(sessionKey), + ]); } else { throw new TurnkeyError( `No session found with key: ${sessionKey}`, diff --git a/packages/core/src/__types__/method-types.ts b/packages/core/src/__types__/method-types.ts index c975b86a3..f6edd436b 100644 --- a/packages/core/src/__types__/method-types.ts +++ b/packages/core/src/__types__/method-types.ts @@ -194,6 +194,10 @@ export type FetchWalletAccountsParams = { stampWith?: StamperType | undefined; organizationId?: string; userId?: string; + authenticatorAddresses?: { + ethereum: string[]; + solana: string[]; + }; }; export type FetchPrivateKeysParams = { diff --git a/packages/react-wallet-kit/src/providers/client/Provider.tsx b/packages/react-wallet-kit/src/providers/client/Provider.tsx index 0bac64bff..7e98eb08d 100644 --- a/packages/react-wallet-kit/src/providers/client/Provider.tsx +++ b/packages/react-wallet-kit/src/providers/client/Provider.tsx @@ -843,8 +843,7 @@ export const ClientProvider: React.FC = ({ return; } setSession(allLocalStorageSessions[activeSessionKey]); - await refreshUser(); - await refreshWallets(); + await Promise.all([refreshUser(), refreshWallets()]); return; } @@ -1013,131 +1012,6 @@ export const ClientProvider: React.FC = ({ }; } - /** - * @internal - * Schedules a session expiration and warning timer for the given session key. - * - * - Sets up two timers: one for warning before expiry and one for actual expiry. - * - Uses capped timeouts under the hood so delays > 24.8 days are safe (see utils/timers.ts). - * - * @param params.sessionKey - The key of the session to schedule expiration for. - * @param params.expiry - The expiration time in seconds since epoch. - * @throws {TurnkeyError} If an error occurs while scheduling the session expiration. - */ - async function scheduleSessionExpiration(params: { - sessionKey: string; - expiry: number; // seconds since epoch - }) { - const { sessionKey, expiry } = params; - - try { - const warnKey = `${sessionKey}-warning`; - - // Replace any prior timers for this session - clearKey(expiryTimeoutsRef.current, sessionKey); - clearKey(expiryTimeoutsRef.current, warnKey); - - const now = Date.now(); - const expiryMs = expiry * 1000; - const timeUntilExpiry = expiryMs - now; - - const beforeExpiry = async () => { - const activeSession = await getSession(); - - if (!activeSession && expiryTimeoutsRef.current[warnKey]) { - // Keep nudging until session materializes (short 10s timer is fine) - setTimeoutInMap( - expiryTimeoutsRef.current, - warnKey, - beforeExpiry, - 10_000, - ); - return; - } - - const session = await getSession({ sessionKey }); - if (!session) return; - - callbacks?.beforeSessionExpiry?.({ sessionKey }); - - if (masterConfig?.auth?.autoRefreshSession) { - await refreshSession({ - expirationSeconds: session.expirationSeconds!, - sessionKey, - }); - } - }; - - const expireSession = async () => { - const expiredSession = await getSession({ sessionKey }); - if (!expiredSession) return; - - callbacks?.onSessionExpired?.({ sessionKey }); - - if ((await getActiveSessionKey()) === sessionKey) { - setSession(undefined); - } - - setAllSessions((prev) => { - if (!prev) return prev; - const next = { ...prev }; - delete next[sessionKey]; - return next; - }); - - await clearSession({ sessionKey }); - - // Remove timers for this session - clearKey(expiryTimeoutsRef.current, sessionKey); - clearKey(expiryTimeoutsRef.current, warnKey); - - await logout(); - }; - - // Already expired → expire immediately - if (timeUntilExpiry <= 0) { - await expireSession(); - return; - } - - // Warning timer (if threshold is in the future) - const warnAt = expiryMs - SESSION_WARNING_THRESHOLD_MS; - if (warnAt <= now) { - void beforeExpiry(); // fire-and-forget is fine - } else { - setCappedTimeoutInMap( - expiryTimeoutsRef.current, - warnKey, - beforeExpiry, - warnAt - now, - ); - } - - // Actual expiry timer (safe for long delays) - setCappedTimeoutInMap( - expiryTimeoutsRef.current, - sessionKey, - expireSession, - timeUntilExpiry, - ); - } catch (error) { - if ( - error instanceof TurnkeyError || - error instanceof TurnkeyNetworkError - ) { - callbacks?.onError?.(error); - } else { - callbacks?.onError?.( - new TurnkeyError( - `Failed to schedule session expiration for ${sessionKey}`, - TurnkeyErrorCodes.SCHEDULE_SESSION_EXPIRY_ERROR, - error, - ), - ); - } - } - } - /** * Clears all scheduled session timers (warning + expiry). * @@ -1207,8 +1081,7 @@ export const ClientProvider: React.FC = ({ setSession(session); setAllSessions(allSessions); - await refreshWallets(); - await refreshUser(); + await Promise.all([refreshWallets(), refreshUser()]); if ( masterConfig?.auth?.verifyWalletOnSignup === true && @@ -1298,6 +1171,82 @@ export const ClientProvider: React.FC = ({ [client], ); + const getActiveSessionKey = useCallback(async (): Promise< + string | undefined + > => { + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + return withTurnkeyErrorHandling( + () => client.getActiveSessionKey(), + undefined, + callbacks, + "Failed to get active session key", + ); + }, [client, callbacks]); + + const logout: (params?: LogoutParams) => Promise = useCallback( + async (params?: { sessionKey?: string }): Promise => { + if (!client) { + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + } + await withTurnkeyErrorHandling( + async () => { + // If no sessionKey is provided, we try to get the active one. + let sessionKey = params?.sessionKey; + if (!sessionKey) sessionKey = await getActiveSessionKey(); + await client.logout(params); + // We only handle post logout if the sessionKey is defined since that means we actually logged out of a session. + if (sessionKey) handlePostLogout(sessionKey); + }, + undefined, + callbacks, + "Failed to logout", + ); + + return; + }, + [client, callbacks, getActiveSessionKey], + ); + + const getSession = useCallback( + async (params?: GetSessionParams): Promise => { + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + return withTurnkeyErrorHandling( + () => client.getSession(params), + undefined, + callbacks, + "Failed to get session", + ); + }, + [client, callbacks], + ); + + const getAllSessions = useCallback(async (): Promise< + Record | undefined + > => { + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + return withTurnkeyErrorHandling( + () => client.getAllSessions(), + undefined, + callbacks, + "Failed to get all sessions", + ); + }, [client, callbacks]); + const createPasskey = useCallback( async (params?: CreatePasskeyParams): Promise => { if (!client) { @@ -1308,7 +1257,7 @@ export const ClientProvider: React.FC = ({ } return withTurnkeyErrorHandling( () => client.createPasskey({ ...params }), - () => logout(), + undefined, callbacks, "Failed to create passkey", ); @@ -1316,31 +1265,362 @@ export const ClientProvider: React.FC = ({ [client, callbacks], ); - const logout: (params?: LogoutParams) => Promise = useCallback( - async (params?: { sessionKey?: string }): Promise => { + const fetchUser = useCallback( + async (params?: FetchUserParams): Promise => { + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + return withTurnkeyErrorHandling( + () => client.fetchUser(params), + () => logout(), + callbacks, + "Failed to fetch user", + ); + }, + [client, callbacks, logout], + ); + + const fetchWalletProviders = useCallback( + async (chain?: Chain): Promise => { if (!client) { throw new TurnkeyError( "Client is not initialized.", TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, ); } - await withTurnkeyErrorHandling( + return withTurnkeyErrorHandling( async () => { - // If no sessionKey is provided, we try to get the active one. - let sessionKey = params?.sessionKey; - if (!sessionKey) sessionKey = await getActiveSessionKey(); - await client.logout(params); - // We only handle post logout if the sessionKey is defined since that means we actually logged out of a session. - if (sessionKey) handlePostLogout(sessionKey); + const newProviders = await client.fetchWalletProviders(chain); + + // we update state with the latest providers + // we keep this state so that initializeWalletProviderListeners() re-runs + // whenever the list of connected providers changes + // this ensures we attach disconnect listeners for each connected provider + setWalletProviders(newProviders); + + return newProviders; }, + undefined, + callbacks, + "Failed to fetch wallet providers", + ); + }, + [client, callbacks], + ); + + const fetchWallets = useCallback( + async (params?: FetchWalletsParams): Promise => { + if (!client) { + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + } + return withTurnkeyErrorHandling( + () => client.fetchWallets(params), () => logout(), callbacks, - "Failed to logout", + "Failed to fetch wallets", ); + }, + [client, callbacks, logout], + ); + const refreshUser = useCallback( + async (params?: RefreshUserParams): Promise => { + if (!masterConfig?.autoRefreshManagedState) return; + const { stampWith, organizationId, userId } = params || {}; + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + const user = await withTurnkeyErrorHandling( + () => + fetchUser({ + stampWith, + ...(organizationId && { organizationId }), + ...(userId && { userId }), + }), + () => logout(), + callbacks, + "Failed to refresh user", + ); + if (user) { + setUser(user); + } + }, + [client, callbacks, fetchUser, logout, masterConfig], + ); + + const refreshWallets = useCallback( + async (params?: RefreshWalletsParams): Promise => { + if (!masterConfig?.autoRefreshManagedState) return []; + + const { stampWith, organizationId, userId } = params || {}; + + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + const walletProviders = await withTurnkeyErrorHandling( + () => fetchWalletProviders(), + undefined, + callbacks, + "Failed to refresh wallets", + ); + + const wallets = await withTurnkeyErrorHandling( + () => + fetchWallets({ + stampWith, + walletProviders, + ...(organizationId && { organizationId }), + ...(userId && { userId }), + }), + () => logout(), + callbacks, + "Failed to refresh wallets", + ); + if (wallets) { + setWallets(wallets); + } + + return wallets; + }, + [ + client, + callbacks, + fetchWalletProviders, + fetchWallets, + logout, + masterConfig, + ], + ); + + const clearSession = useCallback( + async (params?: ClearSessionParams): Promise => { + if (!client) + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + await withTurnkeyErrorHandling( + async () => client.clearSession(params), + undefined, + callbacks, + "Failed to clear session", + ); + const sessionKey = params?.sessionKey ?? (await getActiveSessionKey()); + if (!sessionKey) return; + if (!params?.sessionKey) { + setSession(undefined); + } + clearSessionTimeouts([sessionKey]); + // clear only the cleared session from allSessions + const newAllSessions = { ...allSessions }; + if (newAllSessions) { + delete newAllSessions[sessionKey]; + } + setAllSessions(newAllSessions); return; }, - [client, callbacks], + [client, callbacks, getActiveSessionKey, allSessions], + ); + + /** + * @internal + * Schedules a session expiration and warning timer for the given session key. + * + * - Sets up two timers: one for warning before expiry and one for actual expiry. + * - Uses capped timeouts under the hood so delays > 24.8 days are safe (see utils/timers.ts). + * + * @param params.sessionKey - The key of the session to schedule expiration for. + * @param params.expiry - The expiration time in seconds since epoch. + * @throws {TurnkeyError} If an error occurs while scheduling the session expiration. + */ + const scheduleSessionExpiration = useCallback( + async (params: { + sessionKey: string; + expiry: number; // seconds since epoch + }) => { + const { sessionKey, expiry } = params; + + try { + const warnKey = `${sessionKey}-warning`; + + // Replace any prior timers for this session + clearKey(expiryTimeoutsRef.current, sessionKey); + clearKey(expiryTimeoutsRef.current, warnKey); + + const now = Date.now(); + const expiryMs = expiry * 1000; + const timeUntilExpiry = expiryMs - now; + + const runRefresh = async () => { + const activeSession = await getSession(); + + if (!activeSession && expiryTimeoutsRef.current[warnKey]) { + // Keep nudging until session materializes (short 10s timer is fine) + setTimeoutInMap( + expiryTimeoutsRef.current, + warnKey, + runRefresh, + 10_000, + ); + return; + } + + const session = await getSession({ sessionKey }); + if (!session) return; + + callbacks?.beforeSessionExpiry?.({ sessionKey }); + + if (masterConfig?.auth?.autoRefreshSession) { + await refreshSession({ + expirationSeconds: session.expirationSeconds!, + sessionKey, + }); + } + }; + + const expireSession = async () => { + const expiredSession = await getSession({ sessionKey }); + if (!expiredSession) return; + + callbacks?.onSessionExpired?.({ sessionKey }); + + if ((await getActiveSessionKey()) === sessionKey) { + setSession(undefined); + } + + setAllSessions((prev) => { + if (!prev) return prev; + const next = { ...prev }; + delete next[sessionKey]; + return next; + }); + + await clearSession({ sessionKey }); + + // Remove timers for this session + clearKey(expiryTimeoutsRef.current, sessionKey); + clearKey(expiryTimeoutsRef.current, warnKey); + + await logout(); + }; + + // Already expired → expire immediately + if (timeUntilExpiry <= 0) { + await expireSession(); + return; + } + + // Warning timer (if threshold is in the future) + const warnAt = expiryMs - SESSION_WARNING_THRESHOLD_MS; + if (warnAt <= now) { + void runRefresh(); // fire-and-forget is fine + } else { + setCappedTimeoutInMap( + expiryTimeoutsRef.current, + warnKey, + runRefresh, + warnAt - now, + ); + } + + // Actual expiry timer (safe for long delays) + setCappedTimeoutInMap( + expiryTimeoutsRef.current, + sessionKey, + expireSession, + timeUntilExpiry, + ); + } catch (error) { + if ( + error instanceof TurnkeyError || + error instanceof TurnkeyNetworkError + ) { + callbacks?.onError?.(error); + } else { + callbacks?.onError?.( + new TurnkeyError( + `Failed to schedule session expiration for ${sessionKey}`, + TurnkeyErrorCodes.SCHEDULE_SESSION_EXPIRY_ERROR, + error, + ), + ); + } + } + }, + // Note: `refreshSession()` is intentionally NOT in the dependency array even though + // it's called in runRefresh. This is because: `refreshSession()` itself calls `scheduleSessionExpiration()`, + // creating a circular dependency that would cause infinite re-renders + // + // this is fine because `refreshSession()` reference is stable enough for this use case (session + // expiration timers are long-lived and don't need to re-subscribe on every `refreshSession()` change) + [ + callbacks, + masterConfig, + getSession, + getActiveSessionKey, + clearSession, + logout, + ], + ); + + const refreshSession = useCallback( + async ( + params?: RefreshSessionParams, + ): Promise => { + if (!client) { + throw new TurnkeyError( + "Client is not initialized.", + TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, + ); + } + + const activeSessionKey = await client.getActiveSessionKey(); + if (!activeSessionKey) { + throw new TurnkeyError( + "No active session found.", + TurnkeyErrorCodes.NO_SESSION_FOUND, + ); + } + + const sessionKey = params?.sessionKey ?? activeSessionKey; + + const res = await withTurnkeyErrorHandling( + () => client.refreshSession({ ...params }), + () => logout(), + callbacks, + "Failed to refresh session", + ); + const session = await getSession({ sessionKey }); + + if (session && sessionKey) { + await scheduleSessionExpiration({ + sessionKey, + expiry: session.expiry, + }); + } + + const allSessions = await getAllSessions(); + setSession(session); + setAllSessions(allSessions); + return res; + }, + [ + client, + callbacks, + logout, + getSession, + scheduleSessionExpiration, + getAllSessions, + ], ); const loginWithPasskey = useCallback( @@ -1372,7 +1652,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks], + [client, callbacks, masterConfig, logout, handlePostAuth], ); const signUpWithPasskey = useCallback( @@ -1441,28 +1721,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks], - ); - - const fetchWalletProviders = useCallback( - async (chain?: Chain): Promise => { - if (!client) { - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - } - const newProviders = await client.fetchWalletProviders(chain); - - // we update state with the latest providers - // we keep this state so that initializeWalletProviderListeners() re-runs - // whenever the list of connected providers changes - // this ensures we attach disconnect listeners for each connected provider - setWalletProviders(newProviders); - - return newProviders; - }, - [client, callbacks], + [client, callbacks, logout, handlePostAuth, masterConfig], ); const connectWalletAccount = useCallback( @@ -1473,40 +1732,48 @@ export const ClientProvider: React.FC = ({ TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, ); } - const address = await client.connectWalletAccount(walletProvider); - let wallets: Wallet[]; + return withTurnkeyErrorHandling( + async () => { + const address = await client.connectWalletAccount(walletProvider); - const s = await getSession(); - if (s) { - // this will update our walletProvider state - wallets = await refreshWallets(); - } else { - wallets = await fetchWallets({ connectedOnly: true }); - } + let wallets: Wallet[]; - // we narrow to only connected wallets - // because we know the account must come from one of them - const connectedWallets = wallets.filter( - (w): w is ConnectedWallet => w.source === WalletSource.Connected, - ); + const s = await getSession(); + if (s) { + // this will update our walletProvider state + wallets = await refreshWallets(); + } else { + wallets = await fetchWallets({ connectedOnly: true }); + } - // find the matching account - const matchedAccount = connectedWallets - .flatMap((w) => w.accounts) - .find((a) => a.address === address); + // we narrow to only connected wallets + // because we know the account must come from one of them + const connectedWallets = wallets.filter( + (w): w is ConnectedWallet => w.source === WalletSource.Connected, + ); - if (!matchedAccount) { - throw new TurnkeyError( - `No connected wallet account found for address: ${address}`, - TurnkeyErrorCodes.NO_WALLET_FOUND, - ); - } + // find the matching account + const matchedAccount = connectedWallets + .flatMap((w) => w.accounts) + .find((a) => a.address === address); + + if (!matchedAccount) { + throw new TurnkeyError( + `No connected wallet account found for address: ${address}`, + TurnkeyErrorCodes.NO_WALLET_FOUND, + ); + } - return matchedAccount; + return matchedAccount; + }, + () => logout(), + callbacks, + "Failed to connect wallet account", + ); }, - [client, callbacks], + [client, callbacks, getSession, logout, refreshWallets, fetchWallets], ); const disconnectWalletAccount = useCallback( @@ -1517,10 +1784,18 @@ export const ClientProvider: React.FC = ({ TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, ); } - await client.disconnectWalletAccount(walletProvider); - // no need here to call `refreshWallets()` because the provider emits a disconnect event which - // will trigger a refresh via the listener we set up in `initializeWalletProviderListeners()` + await withTurnkeyErrorHandling( + async () => { + await client.disconnectWalletAccount(walletProvider); + + // no need here to call `refreshWallets()` because the provider emits a disconnect event which + // will trigger a refresh via the listener we set up in `initializeWalletProviderListeners()` + }, + undefined, + callbacks, + "Failed to disconnect wallet account", + ); }, [client, callbacks], ); @@ -1534,9 +1809,16 @@ export const ClientProvider: React.FC = ({ ); } - await client.switchWalletAccountChain({ ...params, walletProviders }); + await withTurnkeyErrorHandling( + async () => { + await client.switchWalletAccountChain({ ...params, walletProviders }); + }, + undefined, + callbacks, + "Failed to switch wallet account chain", + ); }, - [client, callbacks, walletProviders], + [client, walletProviders, callbacks], ); const buildWalletLoginRequest = useCallback( @@ -1556,12 +1838,12 @@ export const ClientProvider: React.FC = ({ DEFAULT_SESSION_EXPIRATION_IN_SECONDS; return await withTurnkeyErrorHandling( () => client.buildWalletLoginRequest({ ...params, expirationSeconds }), - () => logout(), + undefined, callbacks, "Failed to login with wallet", ); }, - [client, callbacks], + [client, callbacks, masterConfig], ); const loginWithWallet = useCallback( @@ -1579,7 +1861,7 @@ export const ClientProvider: React.FC = ({ DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await withTurnkeyErrorHandling( () => client.loginWithWallet({ ...params, expirationSeconds }), - () => logout(), + undefined, callbacks, "Failed to login with wallet", ); @@ -1593,7 +1875,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks], + [client, callbacks, handlePostAuth, masterConfig], ); const signUpWithWallet = useCallback( @@ -1625,7 +1907,7 @@ export const ClientProvider: React.FC = ({ DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await withTurnkeyErrorHandling( () => client.signUpWithWallet({ ...params, expirationSeconds }), - () => logout(), + undefined, callbacks, "Failed to sign up with wallet", ); @@ -1639,7 +1921,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks, masterConfig], + [client, callbacks, handlePostAuth, masterConfig], ); const loginOrSignupWithWallet = useCallback( @@ -1673,7 +1955,7 @@ export const ClientProvider: React.FC = ({ DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await withTurnkeyErrorHandling( () => client.loginOrSignupWithWallet({ ...params, expirationSeconds }), - () => logout(), + undefined, callbacks, "Failed to login or sign up with wallet", ); @@ -1687,7 +1969,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks, masterConfig], + [client, callbacks, handlePostAuth, masterConfig], ); const initOtp = useCallback( @@ -1700,7 +1982,7 @@ export const ClientProvider: React.FC = ({ } return withTurnkeyErrorHandling( () => client.initOtp(params), - () => logout(), + undefined, callbacks, "Failed to initialize OTP", ); @@ -1718,7 +2000,7 @@ export const ClientProvider: React.FC = ({ } return withTurnkeyErrorHandling( () => client.verifyOtp(params), - () => logout(), + undefined, callbacks, "Failed to verify OTP", ); @@ -1737,7 +2019,7 @@ export const ClientProvider: React.FC = ({ const res = await withTurnkeyErrorHandling( () => client.loginWithOtp(params), - () => logout(), + undefined, callbacks, "Failed to login with OTP", ); @@ -1751,7 +2033,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks], + [client, callbacks, handlePostAuth], ); const signUpWithOtp = useCallback( @@ -1785,7 +2067,7 @@ export const ClientProvider: React.FC = ({ const res = await withTurnkeyErrorHandling( () => client.signUpWithOtp(params), - () => logout(), + undefined, callbacks, "Failed to sign up with OTP", ); @@ -1799,7 +2081,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks, masterConfig], + [client, callbacks, masterConfig, handlePostAuth], ); const completeOtp = useCallback( @@ -1838,7 +2120,7 @@ export const ClientProvider: React.FC = ({ const res = await withTurnkeyErrorHandling( () => client.completeOtp(params), - () => logout(), + undefined, callbacks, "Failed to complete OTP", ); @@ -1852,7 +2134,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks, masterConfig], + [client, callbacks, handlePostAuth, masterConfig], ); const loginWithOauth = useCallback( @@ -1866,7 +2148,7 @@ export const ClientProvider: React.FC = ({ const res = await withTurnkeyErrorHandling( () => client.loginWithOauth(params), - () => logout(), + undefined, callbacks, "Failed to login with OAuth", ); @@ -1880,7 +2162,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks], + [client, callbacks, handlePostAuth], ); const signUpWithOauth = useCallback( @@ -1908,7 +2190,7 @@ export const ClientProvider: React.FC = ({ const res = await withTurnkeyErrorHandling( () => client.signUpWithOauth(params), - () => logout(), + undefined, callbacks, "Failed to sign up with OAuth", ); @@ -1922,7 +2204,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks, masterConfig], + [client, callbacks, handlePostAuth, masterConfig], ); const completeOauth = useCallback( @@ -1954,7 +2236,7 @@ export const ClientProvider: React.FC = ({ const res = await withTurnkeyErrorHandling( () => client.completeOauth(params), - () => logout(), + undefined, callbacks, "Failed to complete OAuth", ); @@ -1968,25 +2250,7 @@ export const ClientProvider: React.FC = ({ } return res; }, - [client, callbacks, masterConfig], - ); - - const fetchWallets = useCallback( - async (params?: FetchWalletsParams): Promise => { - if (!client) { - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - } - return withTurnkeyErrorHandling( - () => client.fetchWallets(params), - () => logout(), - callbacks, - "Failed to fetch wallets", - ); - }, - [client, callbacks], + [client, callbacks, masterConfig, handlePostAuth], ); const fetchWalletAccounts = useCallback( @@ -2004,7 +2268,7 @@ export const ClientProvider: React.FC = ({ "Failed to fetch wallet accounts", ); }, - [client, callbacks], + [client, callbacks, logout], ); const fetchPrivateKeys = useCallback( @@ -2022,7 +2286,7 @@ export const ClientProvider: React.FC = ({ "Failed to fetch private keys", ); }, - [client, callbacks], + [client, callbacks, logout], ); const signMessage = useCallback( @@ -2039,7 +2303,7 @@ export const ClientProvider: React.FC = ({ "Failed to sign message", ); }, - [client, callbacks], + [client, callbacks, logout], ); const handleSignMessage = useCallback( @@ -2091,7 +2355,7 @@ export const ClientProvider: React.FC = ({ }); }); }, - [client, callbacks], + [client, callbacks, pushPage], ); const signTransaction = useCallback( @@ -2102,47 +2366,30 @@ export const ClientProvider: React.FC = ({ TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, ); return withTurnkeyErrorHandling( - () => client.signTransaction(params), - () => logout(), - callbacks, - "Failed to sign transaction", - ); - }, - [client, callbacks], - ); - - const signAndSendTransaction = useCallback( - async (params: SignAndSendTransactionParams): Promise => { - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - return withTurnkeyErrorHandling( - () => client.signAndSendTransaction(params), + () => client.signTransaction(params), () => logout(), callbacks, "Failed to sign transaction", ); }, - [client, callbacks], + [client, callbacks, logout], ); - const fetchUser = useCallback( - async (params?: FetchUserParams): Promise => { + const signAndSendTransaction = useCallback( + async (params: SignAndSendTransactionParams): Promise => { if (!client) throw new TurnkeyError( "Client is not initialized.", TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, ); return withTurnkeyErrorHandling( - () => client.fetchUser(params), + () => client.signAndSendTransaction(params), () => logout(), callbacks, - "Failed to fetch user", + "Failed to sign transaction", ); }, - [client, callbacks], + [client, callbacks, logout], ); const fetchOrCreateP256ApiKeyUser = useCallback( @@ -2159,7 +2406,7 @@ export const ClientProvider: React.FC = ({ "Failed to fetch or create delegated access user", ); }, - [client, callbacks], + [client, callbacks, logout], ); const fetchOrCreatePolicies = useCallback( @@ -2178,7 +2425,7 @@ export const ClientProvider: React.FC = ({ "Failed to fetch or create delegated access user", ); }, - [client, callbacks], + [client, callbacks, logout], ); const updateUserEmail = useCallback( @@ -2204,7 +2451,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const removeUserEmail = useCallback( @@ -2230,7 +2477,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const updateUserPhoneNumber = useCallback( @@ -2256,7 +2503,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const removeUserPhoneNumber = useCallback( @@ -2282,7 +2529,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const updateUserName = useCallback( @@ -2308,7 +2555,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const addOauthProvider = useCallback( @@ -2334,7 +2581,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const removeOauthProviders = useCallback( @@ -2360,7 +2607,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const addPasskey = useCallback( @@ -2386,7 +2633,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const removePasskeys = useCallback( @@ -2412,7 +2659,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks], + [client, callbacks, logout, refreshUser], ); const createWallet = useCallback( @@ -2438,7 +2685,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, session, callbacks], + [client, session, callbacks, logout, refreshWallets, getSession], ); const createWalletAccounts = useCallback( @@ -2464,7 +2711,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, session, callbacks], + [client, session, callbacks, logout, getSession, refreshWallets], ); const exportWallet = useCallback( @@ -2490,7 +2737,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, session, callbacks], + [client, session, callbacks, logout, getSession, refreshWallets], ); const exportPrivateKey = useCallback( @@ -2508,7 +2755,7 @@ export const ClientProvider: React.FC = ({ ); return res; }, - [client, callbacks], + [client, callbacks, logout], ); const exportWalletAccount = useCallback( @@ -2534,7 +2781,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks, masterConfig, session, user], + [client, callbacks, logout, getSession, refreshWallets], ); const importWallet = useCallback( @@ -2561,7 +2808,7 @@ export const ClientProvider: React.FC = ({ }); return res; }, - [client, callbacks, masterConfig, session, user], + [client, callbacks, logout, getSession, refreshWallets], ); const importPrivateKey = useCallback( @@ -2579,7 +2826,7 @@ export const ClientProvider: React.FC = ({ ); return res; }, - [client, callbacks, masterConfig, session, user], + [client, callbacks, logout], ); const deleteSubOrganization = useCallback( @@ -2598,7 +2845,7 @@ export const ClientProvider: React.FC = ({ "Failed to delete sub-organization", ); }, - [client, callbacks, masterConfig, session, user], + [client, callbacks, logout], ); const storeSession = useCallback( @@ -2627,40 +2874,19 @@ export const ClientProvider: React.FC = ({ setSession(session); setAllSessions(allSessions); - await refreshWallets(); - await refreshUser(); - }, - [client, callbacks, masterConfig, session, user], - ); - - const clearSession = useCallback( - async (params?: ClearSessionParams): Promise => { - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - await withTurnkeyErrorHandling( - async () => client.clearSession(params), - () => logout(), - callbacks, - "Failed to clear session", - ); - const sessionKey = params?.sessionKey ?? (await getActiveSessionKey()); - if (!sessionKey) return; - if (!params?.sessionKey) { - setSession(undefined); - } - clearSessionTimeouts([sessionKey]); - // clear only the cleared session from allSessions - const newAllSessions = { ...allSessions }; - if (newAllSessions) { - delete newAllSessions[sessionKey]; - } - setAllSessions(newAllSessions); - return; + await Promise.all([refreshWallets(), refreshUser()]); }, - [client, callbacks, session, user, masterConfig], + [ + client, + callbacks, + logout, + getActiveSessionKey, + getSession, + scheduleSessionExpiration, + getAllSessions, + refreshWallets, + refreshUser, + ], ); const clearAllSessions = useCallback(async (): Promise => { @@ -2678,84 +2904,7 @@ export const ClientProvider: React.FC = ({ callbacks, "Failed to clear all sessions", ); - }, [client, callbacks, session, user, masterConfig]); - - const refreshSession = useCallback( - async ( - params?: RefreshSessionParams, - ): Promise => { - if (!client) { - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - } - - const activeSessionKey = await client.getActiveSessionKey(); - if (!activeSessionKey) { - throw new TurnkeyError( - "No active session found.", - TurnkeyErrorCodes.NO_SESSION_FOUND, - ); - } - - const sessionKey = params?.sessionKey ?? activeSessionKey; - - const res = await withTurnkeyErrorHandling( - () => client.refreshSession({ ...params }), - () => logout(), - callbacks, - "Failed to refresh session", - ); - const session = await getSession({ sessionKey }); - - if (session && sessionKey) { - await scheduleSessionExpiration({ - sessionKey, - expiry: session.expiry, - }); - } - - const allSessions = await getAllSessions(); - setSession(session); - setAllSessions(allSessions); - return res; - }, - [client, callbacks, scheduleSessionExpiration, session, user, masterConfig], - ); - - const getSession = useCallback( - async (params?: GetSessionParams): Promise => { - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - return withTurnkeyErrorHandling( - () => client.getSession(params), - () => logout(), - callbacks, - "Failed to get session", - ); - }, - [client, callbacks, masterConfig, session, user], - ); - - const getAllSessions = useCallback(async (): Promise< - Record | undefined - > => { - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - return withTurnkeyErrorHandling( - () => client.getAllSessions(), - () => logout(), - callbacks, - "Failed to get all sessions", - ); - }, [client, callbacks, masterConfig, session, user]); + }, [client, callbacks, logout, clearSessionTimeouts]); const setActiveSession = useCallback( async (params: SetActiveSessionParams): Promise => { @@ -2786,8 +2935,7 @@ export const ClientProvider: React.FC = ({ setSession(session); await withTurnkeyErrorHandling( async () => { - await refreshWallets(); - await refreshUser(); + await Promise.all([refreshWallets(), refreshUser()]); }, () => logout(), callbacks, @@ -2795,25 +2943,9 @@ export const ClientProvider: React.FC = ({ ); return; }, - [client, callbacks, session, user, masterConfig], + [client, callbacks, logout, getSession, refreshWallets, refreshUser], ); - const getActiveSessionKey = useCallback(async (): Promise< - string | undefined - > => { - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - return withTurnkeyErrorHandling( - () => client.getActiveSessionKey(), - () => logout(), - callbacks, - "Failed to get active session key", - ); - }, [client, callbacks, masterConfig, session, user]); - const clearUnusedKeyPairs = useCallback(async (): Promise => { if (!client) throw new TurnkeyError( @@ -2822,11 +2954,11 @@ export const ClientProvider: React.FC = ({ ); return withTurnkeyErrorHandling( () => client.clearUnusedKeyPairs(), - () => logout(), + undefined, callbacks, "Failed to clear unused key pairs", ); - }, [client, callbacks, masterConfig, session, user]); + }, [client, callbacks]); const createApiKeyPair = useCallback( async (params?: CreateApiKeyPairParams): Promise => { @@ -2837,12 +2969,12 @@ export const ClientProvider: React.FC = ({ ); return withTurnkeyErrorHandling( () => client.createApiKeyPair(params), - () => logout(), + undefined, callbacks, "Failed to create API key pair", ); }, - [client, callbacks, session, user, masterConfig], + [client, callbacks], ); const getProxyAuthConfig = @@ -2854,38 +2986,11 @@ export const ClientProvider: React.FC = ({ ); return withTurnkeyErrorHandling( () => client.getProxyAuthConfig(), - () => logout(), + undefined, callbacks, "Failed to get proxy auth config", ); - }, [client, callbacks, masterConfig, session, user]); - - const refreshUser = useCallback( - async (params?: RefreshUserParams): Promise => { - if (!masterConfig?.autoRefreshManagedState) return; - const { stampWith, organizationId, userId } = params || {}; - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - const user = await withTurnkeyErrorHandling( - () => - fetchUser({ - stampWith, - ...(organizationId && { organizationId }), - ...(userId && { userId }), - }), - () => logout(), - callbacks, - "Failed to refresh user", - ); - if (user) { - setUser(user); - } - }, - [client, callbacks, fetchUser, masterConfig, session, user], - ); + }, [client, callbacks]); const fetchBootProofForAppProof = useCallback( async (params: FetchBootProofForAppProofParams): Promise => { @@ -2901,7 +3006,7 @@ export const ClientProvider: React.FC = ({ "Failed to fetch or create delegated access user", ); }, - [client, callbacks], + [client, callbacks, logout], ); const verifyAppProofs = useCallback( @@ -2918,54 +3023,7 @@ export const ClientProvider: React.FC = ({ "Failed to verify app proofs", ); }, - [client, callbacks], - ); - - const refreshWallets = useCallback( - async (params?: RefreshWalletsParams): Promise => { - if (!masterConfig?.autoRefreshManagedState) return []; - - const { stampWith, organizationId, userId } = params || {}; - - if (!client) - throw new TurnkeyError( - "Client is not initialized.", - TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED, - ); - const walletProviders = await withTurnkeyErrorHandling( - () => fetchWalletProviders(), - () => logout(), - callbacks, - "Failed to refresh wallets", - ); - - const wallets = await withTurnkeyErrorHandling( - () => - fetchWallets({ - stampWith, - walletProviders, - ...(organizationId && { organizationId }), - ...(userId && { userId }), - }), - () => logout(), - callbacks, - "Failed to refresh wallets", - ); - if (wallets) { - setWallets(wallets); - } - - return wallets; - }, - [ - client, - callbacks, - fetchWalletProviders, - fetchWallets, - masterConfig, - session, - user, - ], + [client, callbacks, logout], ); const handleDiscordOauth = useCallback( @@ -3152,15 +3210,7 @@ export const ClientProvider: React.FC = ({ throw error; } }, - [ - client, - callbacks, - completeOauth, - createApiKeyPair, - masterConfig, - session, - user, - ], + [client, callbacks, completeOauth, createApiKeyPair, masterConfig], ); const handleXOauth = useCallback( @@ -3347,7 +3397,7 @@ export const ClientProvider: React.FC = ({ throw error; } }, - [client, callbacks, masterConfig, session, user], + [client, callbacks, completeOauth, createApiKeyPair, masterConfig], ); const handleGoogleOauth = useCallback( @@ -3512,7 +3562,7 @@ export const ClientProvider: React.FC = ({ throw error; } }, - [client, callbacks, masterConfig, session, user], + [callbacks, completeOauth, createApiKeyPair, masterConfig], ); const handleAppleOauth = useCallback( @@ -3673,7 +3723,7 @@ export const ClientProvider: React.FC = ({ throw error; } }, - [client, callbacks, masterConfig, session, user], + [callbacks, completeOauth, createApiKeyPair, masterConfig], ); const handleFacebookOauth = useCallback( @@ -3864,7 +3914,7 @@ export const ClientProvider: React.FC = ({ throw error; } }, - [client, callbacks, masterConfig, client, session, user], + [callbacks, completeOauth, createApiKeyPair, masterConfig], ); const handleLogin = useCallback( @@ -3885,7 +3935,7 @@ export const ClientProvider: React.FC = ({ showTitle: logo ? false : true, }); }, - [pushPage, masterConfig, client, session, user], + [pushPage, masterConfig], ); const handleExportWallet = useCallback( @@ -3916,7 +3966,7 @@ export const ClientProvider: React.FC = ({ }), ); }, - [pushPage, masterConfig, client, session, user], + [pushPage], ); const handleExportPrivateKey = useCallback( @@ -3953,7 +4003,7 @@ export const ClientProvider: React.FC = ({ }), ); }, - [pushPage, masterConfig, client, session, user], + [pushPage], ); const handleExportWalletAccount = useCallback( @@ -3986,7 +4036,7 @@ export const ClientProvider: React.FC = ({ }), ); }, - [pushPage, masterConfig, client, session, user], + [pushPage], ); const handleImportWallet = useCallback( @@ -4043,7 +4093,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [pushPage, logout], ); const handleImportPrivateKey = useCallback( @@ -4098,7 +4148,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [pushPage, logout], ); const handleUpdateUserName = useCallback( @@ -4199,7 +4249,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [client, getSession, user, pushPage, closeModal, updateUserName, logout], ); const handleUpdateUserPhoneNumber = useCallback( @@ -4359,7 +4409,19 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [ + client, + masterConfig, + getSession, + pushPage, + closeModal, + logout, + initOtp, + verifyOtp, + updateUserPhoneNumber, + user, + callbacks, + ], ); const handleUpdateUserEmail = useCallback( @@ -4504,7 +4566,19 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [ + client, + getSession, + pushPage, + closeModal, + logout, + initOtp, + masterConfig, + verifyOtp, + updateUserEmail, + user, + callbacks, + ], ); const handleAddEmail = useCallback( @@ -4651,7 +4725,19 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, client, session, user], + [ + client, + getSession, + pushPage, + closeModal, + user, + logout, + initOtp, + masterConfig, + verifyOtp, + updateUserEmail, + callbacks, + ], ); const handleAddPhoneNumber = useCallback( @@ -4819,7 +4905,19 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [ + client, + masterConfig, + getSession, + pushPage, + closeModal, + user, + logout, + callbacks, + initOtp, + verifyOtp, + updateUserPhoneNumber, + ], ); const handleRemovePasskey = useCallback( @@ -4879,7 +4977,7 @@ export const ClientProvider: React.FC = ({ "Failed to remove passkey", ); }, - [pushPage, masterConfig, client, session, user], + [client, getSession, pushPage, logout, callbacks], ); const handleAddPasskey = useCallback( @@ -4948,7 +5046,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [client, getSession, addPasskey, pushPage, closeModal], ); const handleRemoveOauthProvider = useCallback( @@ -5016,7 +5114,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [client, getSession, pushPage], ); const handleAddOauthProvider = useCallback( @@ -5115,7 +5213,18 @@ export const ClientProvider: React.FC = ({ } }); }, - [pushPage, masterConfig, client, session, user], + [ + client, + getSession, + addOauthProvider, + pushPage, + closeModal, + handleDiscordOauth, + handleXOauth, + handleGoogleOauth, + handleAppleOauth, + handleFacebookOauth, + ], ); const handleConnectExternalWallet = useCallback( @@ -5162,7 +5271,7 @@ export const ClientProvider: React.FC = ({ }); }); }, - [pushPage, masterConfig, client, session, user], + [client, masterConfig, fetchWalletProviders, pushPage], ); const handleRemoveUserEmail = useCallback( @@ -5217,7 +5326,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [getSession, pushPage], ); const handleRemoveUserPhoneNumber = useCallback( @@ -5272,7 +5381,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, masterConfig, client, session, user], + [getSession, pushPage], ); const handleOnRamp = useCallback( @@ -5486,7 +5595,7 @@ export const ClientProvider: React.FC = ({ }); }); }, - [pushPage, client], + [getSession, client, pushPage], ); const handleVerifyAppProofs = useCallback( @@ -5542,7 +5651,7 @@ export const ClientProvider: React.FC = ({ ); } }, - [pushPage, client], + [getSession, pushPage], ); useEffect(() => { @@ -5609,7 +5718,8 @@ export const ClientProvider: React.FC = ({ // while the user is unauthenticated // // WalletProviders state is updated regardless of session state - if (session) { + const currentSession = await getSession(); + if (currentSession) { // this updates both the wallets and walletProviders state await debouncedRefreshWallets(); } else { @@ -5630,7 +5740,13 @@ export const ClientProvider: React.FC = ({ return () => { cleanup(); }; - }, [client, walletProviders, session]); + }, [ + client, + walletProviders, + getSession, + debouncedRefreshWallets, + debouncedFetchWalletProviders, + ]); useEffect(() => { // authState must be consistent with session state. We found during testing that there are cases where the session and authState can be out of sync in very rare edge cases. diff --git a/packages/react-wallet-kit/src/utils/utils.ts b/packages/react-wallet-kit/src/utils/utils.ts index b3615354b..aa7e977a2 100644 --- a/packages/react-wallet-kit/src/utils/utils.ts +++ b/packages/react-wallet-kit/src/utils/utils.ts @@ -84,7 +84,7 @@ export const isValidSession = (session?: Session | undefined): boolean => { export async function withTurnkeyErrorHandling( fn: () => Promise, - sessionExpireFn: () => Promise, + sessionExpireFn?: () => Promise, callbacks?: { onError?: (error: TurnkeyError) => void }, fallbackMessage = "An unknown error occurred", fallbackCode = TurnkeyErrorCodes.UNKNOWN, @@ -98,7 +98,21 @@ export async function withTurnkeyErrorHandling( tkError = error; if (tkError.code === TurnkeyErrorCodes.SESSION_EXPIRED) { - await sessionExpireFn(); + if (sessionExpireFn) { + await sessionExpireFn(); + } else { + // this should never happen. If it does, it means an SDK function is making + // a session-based API call without providing a `sessionExpireFn()` for handling + // SESSION_EXPIRED errors in `withTurnkeyErrorHandling()` + console.error( + "SESSION_EXPIRED encountered but no sessionExpireFn provided. Contact Turnkey support.", + ); + throw new TurnkeyError( + "SESSION_EXPIRED received without sessionExpireFn handler", + TurnkeyErrorCodes.INTERNAL_ERROR, + error, + ); + } } // skip onError for WalletConnect expired errors From eee1eccbe39776b695d590b70ad40acaa6f36b67 Mon Sep 17 00:00:00 2001 From: Mohammed Odeh Date: Wed, 12 Nov 2025 14:34:57 -0500 Subject: [PATCH 2/5] optimize client init --- packages/core/package.json | 4 +- packages/core/src/__clients__/core.ts | 23 +- .../core/src/__types__/external-wallets.ts | 3 + .../core/src/__wallet__/mobile/manager.ts | 9 +- .../src/__wallet__/wallet-connect/base.ts | 124 +++-- .../src/__wallet__/wallet-connect/client.ts | 5 + packages/core/src/__wallet__/web/manager.ts | 73 +-- .../src/components/auth/Wallet.tsx | 123 ++++- .../src/providers/client/Provider.tsx | 33 ++ pnpm-lock.yaml | 438 +++++++++--------- 10 files changed, 513 insertions(+), 322 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index ad0579c8f..dce105a96 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,8 +43,8 @@ "@turnkey/webauthn-stamper": "workspace:*", "@wallet-standard/app": "^1.1.0", "@wallet-standard/base": "^1.1.0", - "@walletconnect/sign-client": "^2.21.8", - "@walletconnect/types": "^2.21.8", + "@walletconnect/sign-client": "^2.23.0", + "@walletconnect/types": "^2.23.0", "cross-fetch": "^3.1.5", "ethers": "^6.10.0", "jwt-decode": "4.0.0", diff --git a/packages/core/src/__clients__/core.ts b/packages/core/src/__clients__/core.ts index 5a81216cf..1d33e008b 100644 --- a/packages/core/src/__clients__/core.ts +++ b/packages/core/src/__clients__/core.ts @@ -191,22 +191,37 @@ export class TurnkeyClient { // Initialize the API key stamper this.apiKeyStamper = new CrossPlatformApiKeyStamper(this.storageManager); - await this.apiKeyStamper.init(); + + // we parallelize independent initializations: + // - API key stamper init + // - Passkey stamper creation and init (if configured) + // - Wallet manager creation (if configured) + const initTasks: Promise[] = [this.apiKeyStamper.init()]; if (this.config.passkeyConfig) { - this.passkeyStamper = new CrossPlatformPasskeyStamper( + const passkeyStamper = new CrossPlatformPasskeyStamper( this.config.passkeyConfig, ); - await this.passkeyStamper.init(); + initTasks.push( + passkeyStamper.init().then(() => { + this.passkeyStamper = passkeyStamper; + }), + ); } if ( this.config.walletConfig?.features?.auth || this.config.walletConfig?.features?.connecting ) { - this.walletManager = await createWalletManager(this.config.walletConfig); + initTasks.push( + createWalletManager(this.config.walletConfig).then((manager) => { + this.walletManager = manager; + }), + ); } + await Promise.all(initTasks); + // Initialize the HTTP client with the appropriate stampers // Note: not passing anything here since we want to use the configured stampers and this.config this.httpClient = this.createHttpClient(); diff --git a/packages/core/src/__types__/external-wallets.ts b/packages/core/src/__types__/external-wallets.ts index 3fbeb1bf6..5a47dabc5 100644 --- a/packages/core/src/__types__/external-wallets.ts +++ b/packages/core/src/__types__/external-wallets.ts @@ -69,7 +69,10 @@ export interface WalletProvider { info: WalletProviderInfo; provider: WalletRpcProvider; connectedAddresses: string[]; + + // WalletConnect specific uri?: string; + isLoading?: boolean; } /** @internal */ diff --git a/packages/core/src/__wallet__/mobile/manager.ts b/packages/core/src/__wallet__/mobile/manager.ts index 27b2455ab..4bce03edf 100644 --- a/packages/core/src/__wallet__/mobile/manager.ts +++ b/packages/core/src/__wallet__/mobile/manager.ts @@ -52,14 +52,15 @@ export class MobileWalletManager { if (cfg.walletConnect && enableWalletConnect) { this.wcClient = new WalletConnectClient(); - const wcUnified = new WalletConnectWallet(this.wcClient); + const wcUnified = new WalletConnectWallet(this.wcClient, undefined, { + ethereumNamespaces, + solanaNamespaces, + }); this.wallets[WalletInterfaceType.WalletConnect] = wcUnified; // add async init step to the initializer queue - this.initializers.push(() => - wcUnified.init({ ethereumNamespaces, solanaNamespaces }), - ); + this.initializers.push(() => wcUnified.init()); // register WalletConnect as a wallet interface for each enabled chain if (enableWalletConnectEvm) { diff --git a/packages/core/src/__wallet__/wallet-connect/base.ts b/packages/core/src/__wallet__/wallet-connect/base.ts index f174300f5..fa2375c1e 100644 --- a/packages/core/src/__wallet__/wallet-connect/base.ts +++ b/packages/core/src/__wallet__/wallet-connect/base.ts @@ -16,15 +16,17 @@ import { WalletConnectInterface, SwitchableChain, } from "../../__types__"; -import type { WalletConnectClient } from "./client"; import type { SessionTypes } from "@walletconnect/types"; +import type { WalletConnectClient } from "./client"; import { Transaction } from "ethers"; type WalletConnectChangeEvent = | { type: "disconnect" } | { type: "chainChanged"; chainId?: string } | { type: "update" } - | { type: "proposalExpired" }; + | { type: "proposalExpired" } + | { type: "initialized" } + | { type: "failed"; error?: unknown }; export class WalletConnectWallet implements WalletConnectInterface { readonly interfaceType = WalletInterfaceType.WalletConnect; @@ -37,6 +39,7 @@ export class WalletConnectWallet implements WalletConnectInterface { private uri?: string; private isRegeneratingUri = false; + private isInitialized = false; private changeListeners = new Set< (event?: WalletConnectChangeEvent) => void @@ -60,8 +63,29 @@ export class WalletConnectWallet implements WalletConnectInterface { * updating `this.uri` so the UI can present a fresh QR/deeplink. * * @param client - The low-level WalletConnect client used for session/RPC. + * @param ensureReady - Optional callback to ensure WalletConnect is initialized before operations. + * @param namespaces - Optional namespace configuration to set up configured chains. */ - constructor(private client: WalletConnectClient) { + constructor( + private client: WalletConnectClient, + private ensureReady?: () => Promise, + namespaces?: { + ethereumNamespaces: string[]; + solanaNamespaces: string[]; + }, + ) { + if (namespaces) { + this.ethereumNamespaces = namespaces.ethereumNamespaces; + if (this.ethereumNamespaces.length > 0) { + this.ethChain = this.ethereumNamespaces[0]!; + } + + this.solanaNamespaces = namespaces.solanaNamespaces; + if (this.solanaNamespaces.length > 0) { + this.solChain = this.solanaNamespaces[0]!; + } + } + // session updated (actual update to the session for example adding a chain to namespaces) this.client.onSessionUpdate(() => { this.notifyChange({ type: "update" }); @@ -112,52 +136,49 @@ export class WalletConnectWallet implements WalletConnectInterface { } /** - * Initializes WalletConnect pairing flow with the specified namespaces. + * Initializes WalletConnect pairing flow. * - * - Saves the requested chain namespaces (e.g., `["eip155:1", "eip155:137", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"]`). * - If an active session already has connected accounts, pairing is skipped. * - Otherwise initiates a pairing and stores the resulting URI. + * - Namespaces should be set via constructor for this to work. * - * @param opts.ethereumNamespaces - List of EVM CAIP IDs (e.g., "eip155:1"). - * @param opts.solanaNamespaces - List of Solana CAIP IDs (e.g., "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"). - * @throws {Error} If no namespaces are provided for either chain. + * @throws {Error} If no namespaces were configured in constructor. */ - async init(opts: { - ethereumNamespaces: string[]; - solanaNamespaces: string[]; - }): Promise { - this.ethereumNamespaces = opts.ethereumNamespaces; - if (this.ethereumNamespaces.length > 0) { - this.ethChain = this.ethereumNamespaces[0]!; - } + async init(): Promise { + try { + if ( + this.ethereumNamespaces.length === 0 && + this.solanaNamespaces.length === 0 + ) { + throw new Error( + "At least one namespace must be enabled for WalletConnect", + ); + } - this.solanaNamespaces = opts.solanaNamespaces; - if (this.solanaNamespaces.length > 0) { - this.solChain = this.solanaNamespaces[0]!; - } + // we don't want to create more than one active session + // so we don't make a pair request if one is already active + // since pairing would mean initializing a new session + const session = this.client.getSession(); + if (hasConnectedAccounts(session)) { + this.isInitialized = true; + // we notify that initialization is complete + this.notifyChange({ type: "initialized" }); + return; + } - if ( - this.ethereumNamespaces.length === 0 && - this.solanaNamespaces.length === 0 - ) { - throw new Error( - "At least one namespace must be enabled for WalletConnect", - ); - } + const namespaces = this.buildNamespaces(); - // we don't want to create more than one active session - // so we don't make a pair request if one is already active - // since pairing would mean initializing a new session - const session = this.client.getSession(); - if (hasConnectedAccounts(session)) { - return; + await this.client.pair(namespaces).then((newUri) => { + this.uri = newUri; + this.isInitialized = true; + // we notify that initialization is complete + this.notifyChange({ type: "initialized" }); + }); + } catch (error) { + // we emit a failed event + this.notifyChange({ type: "failed", error }); + throw error; } - - const namespaces = this.buildNamespaces(); - - await this.client.pair(namespaces).then((newUri) => { - this.uri = newUri; - }); } /** @@ -165,6 +186,7 @@ export class WalletConnectWallet implements WalletConnectInterface { * * - Builds an EVM provider (if Ethereum namespaces are enabled). * - Builds a Solana provider (if Solana namespaces are enabled). + * - Before initialization, returns placeholder providers with isLoading: true. * * @returns A promise resolving to an array of WalletProvider objects. */ @@ -194,12 +216,18 @@ export class WalletConnectWallet implements WalletConnectInterface { * * - Calls `approve()` on the underlying client when pairing is pending. * - Throws if the approved session contains no connected accounts. + * - Waits for WalletConnect initialization if still in progress. * * @param _provider - Unused (present for interface compatibility). * @returns A promise that resolves with the connected wallet's address. * @throws {Error} If the session contains no accounts. */ async connectWalletAccount(provider: WalletProvider): Promise { + // we ensure WalletConnect is fully initialized before connecting + if (this.ensureReady) { + await this.ensureReady(); + } + const session = await this.client.approve(); let address: string | undefined; @@ -222,7 +250,7 @@ export class WalletConnectWallet implements WalletConnectInterface { } /** - * Switches the user’s WalletConnect session to a new EVM chain. + * Switches the user's WalletConnect session to a new EVM chain. * * - Ethereum-only: only supported for providers on the Ethereum namespace. * - No add-then-switch: WalletConnect cannot add chains mid-session. The target chain @@ -230,6 +258,7 @@ export class WalletConnectWallet implements WalletConnectInterface { * you must include it in the walletConfig. * - Accepts a hex chain ID (e.g., "0x1"). If a `SwitchableChain` is passed, only its `id` * (hex chain ID) is used; metadata is ignored for WalletConnect. + * - Waits for WalletConnect initialization if still in progress. * * @param provider - The WalletProvider returned by `getProviders()`. * @param chainOrId - Hex chain ID (e.g., "0x1") or a `SwitchableChain` (its `id` is used). @@ -241,6 +270,11 @@ export class WalletConnectWallet implements WalletConnectInterface { provider: WalletProvider, chainOrId: string | SwitchableChain, ): Promise { + // we ensure WalletConnect is fully initialized + if (this.ensureReady) { + await this.ensureReady(); + } + if (provider.chainInfo.namespace !== Chain.Ethereum) { throw new Error("Only EVM wallets support chain switching"); } @@ -408,12 +442,18 @@ export class WalletConnectWallet implements WalletConnectInterface { * * - Ethereum: signs a fixed challenge and recovers the compressed secp256k1 public key. * - Solana: decodes the base58-encoded address to raw bytes. + * - Waits for WalletConnect initialization if still in progress. * * @param provider - The WalletProvider to fetch the key from. * @returns A compressed public key as a hex string. * @throws {Error} If no account is available or the namespace is unsupported. */ async getPublicKey(provider: WalletProvider): Promise { + // we ensure WalletConnect is fully initialized + if (this.ensureReady) { + await this.ensureReady(); + } + const session = this.client.getSession(); if (provider.chainInfo.namespace === Chain.Ethereum) { @@ -547,6 +587,7 @@ export class WalletConnectWallet implements WalletConnectInterface { provider: this.makeProvider(this.ethChain), connectedAddresses: address ? [address] : [], ...(this.uri && { uri: this.uri }), + isLoading: !this.isInitialized, }; } @@ -574,6 +615,7 @@ export class WalletConnectWallet implements WalletConnectInterface { provider: this.makeProvider(this.solChain), connectedAddresses: address ? [address] : [], ...(this.uri && { uri: this.uri }), + isLoading: !this.isInitialized, }; } diff --git a/packages/core/src/__wallet__/wallet-connect/client.ts b/packages/core/src/__wallet__/wallet-connect/client.ts index ab52c0f3f..aeece6eff 100644 --- a/packages/core/src/__wallet__/wallet-connect/client.ts +++ b/packages/core/src/__wallet__/wallet-connect/client.ts @@ -202,6 +202,11 @@ export class WalletConnectClient { * @returns The most recent session, or `null` if none are active. */ getSession(): SessionTypes.Struct | null { + // we return null if the client hasn't been initialized yet + if (!this.client?.session) { + return null; + } + const sessions = this.client.session.getAll(); return sessions.length ? sessions[sessions.length - 1]! : null; } diff --git a/packages/core/src/__wallet__/web/manager.ts b/packages/core/src/__wallet__/web/manager.ts index 4c8080ca6..51cee7d33 100644 --- a/packages/core/src/__wallet__/web/manager.ts +++ b/packages/core/src/__wallet__/web/manager.ts @@ -70,24 +70,26 @@ export class WebWalletManager { // if WalletConnect is configured, set it up if (cfg.walletConnect && enableWalletConnect) { this.wcClient = new WalletConnectClient(); - const wcUnified = new WalletConnectWallet(this.wcClient); + const wcUnified = new WalletConnectWallet( + this.wcClient, + () => this.ensureWalletConnectReady(), + { ethereumNamespaces, solanaNamespaces }, + ); this.wallets[WalletInterfaceType.WalletConnect] = wcUnified; // add async init step to the initializer queue this.initializers.push(() => - withTimeout( - wcUnified.init({ ethereumNamespaces, solanaNamespaces }), - 5000, - "WalletConnect wallet", - ).catch((err) => { - // WalletConnect can be a bit unreliable, so instead of throwing an error - // we handle failures silently to avoid blocking the rest of the client - // from initializing. If setup fails, we also remove WalletConnect - // from the available wallets list - console.error("Failed to init WalletConnect wallet:", err); - this.removeWalletInterface(WalletInterfaceType.WalletConnect); - }), + withTimeout(wcUnified.init(), 15000, "WalletConnect wallet").catch( + (err) => { + // WalletConnect can be a bit unreliable, so instead of throwing an error + // we handle failures silently to avoid blocking the rest of the client + // from initializing. If setup fails, we also remove WalletConnect + // from the available wallets list + console.error("Failed to init WalletConnect wallet:", err); + this.removeWalletInterface(WalletInterfaceType.WalletConnect); + }, + ), ); // register WalletConnect as a wallet interface for each enabled chain @@ -109,31 +111,48 @@ export class WebWalletManager { } } + // promise that resolves when WalletConnect is fully initialized + private wcInitPromise?: Promise; + /** * Initializes WalletConnect components and any registered wallet interfaces. * * - Initializes the low-level WalletConnect client with the provided config. * - Runs any registered async wallet initializers (currently only `WalletConnectWallet`). + * - WalletConnect initialization happens in the background and does not block this method. * * @param cfg - Wallet manager configuration used for initializing the WalletConnect client. */ async init(cfg: TWalletManagerConfig): Promise { if (this.wcClient) { - try { - await this.wcClient.init(cfg.walletConnect!); - } catch (error) { - // WalletConnect can be a bit unreliable, so instead of throwing an error - // we handle failures silently to avoid blocking the rest of the client - // from initializing. If setup fails, we also remove WalletConnect - // from the available wallets list - console.error("Failed to initialize WalletConnect client", error); - this.removeWalletInterface(WalletInterfaceType.WalletConnect); - } + // we start WalletConnect initialization in the background (this is non-blocking) + this.wcInitPromise = (async () => { + try { + await this.wcClient!.init(cfg.walletConnect!); + // initialize the high-level WalletConnectWallet after client is ready + await Promise.all(this.initializers.map((fn) => fn())); + } catch (error) { + // WalletConnect can be a bit unreliable, so instead of throwing an error + // we handle failures silently to avoid blocking the rest of the client + // from initializing. If setup fails, we also remove WalletConnect + // from the available wallets list + console.error("Failed to initialize WalletConnect", error); + this.removeWalletInterface(WalletInterfaceType.WalletConnect); + } + })(); } + } - // we initialize the high-level WalletConnectWallet - // we do this because we can't init this inside the constructor since it's async - await Promise.all(this.initializers.map((fn) => fn())); + /** + * Ensures WalletConnect is fully initialized before proceeding. + * This should be called before any WalletConnect operations. + * + * @returns A promise that resolves when WalletConnect is ready. + */ + async ensureWalletConnectReady(): Promise { + if (this.wcInitPromise) { + await this.wcInitPromise; + } } /** @@ -141,6 +160,8 @@ export class WebWalletManager { * * - If a chain is specified, filters wallet interfaces that support that chain. * - Aggregates providers across all wallet interfaces and filters WalletConnect results accordingly. + * - Returns native wallet providers immediately without waiting for WalletConnect. + * - WalletConnect providers will only be included if WalletConnect has already initialized. * * @param chain - Optional chain to filter providers by (e.g., Ethereum, Solana). * @returns A promise that resolves to an array of `WalletProvider` objects. diff --git a/packages/react-wallet-kit/src/components/auth/Wallet.tsx b/packages/react-wallet-kit/src/components/auth/Wallet.tsx index a48ebcac1..eddda4fbb 100644 --- a/packages/react-wallet-kit/src/components/auth/Wallet.tsx +++ b/packages/react-wallet-kit/src/components/auth/Wallet.tsx @@ -64,7 +64,27 @@ export function ExternalWalletChainSelector( props: ExternalWalletSelectorProps, ) { const { providers, onSelect, onDisconnect } = props; - const { isMobile } = useModal(); + + const { walletProviders } = useTurnkey(); + const { isMobile, closeModal } = useModal(); + + // we find matching providers in current state + const currentProviders = providers.map((inputProvider) => + walletProviders.find( + (p) => + p.interfaceType === inputProvider.interfaceType && + p.chainInfo.namespace === inputProvider.chainInfo.namespace, + ), + ).filter((p): p is WalletProvider => p !== undefined); + + // if no providers are found then that means that the user entered this screen + // while WalletConnect was still initializing, and then it failed to initialize + useEffect(() => { + if (currentProviders.length === 0) { + closeModal(); + } + }, [currentProviders.length, closeModal]); + const shouldShowDisconnect = onDisconnect !== undefined; const handleSelect = (provider: WalletProvider) => { @@ -398,6 +418,43 @@ export function ConnectedIndicator(props: ConnectedIndicatorProps) { ); } +interface QRCodeDisplayProps { + uri: string; + icon: string; + isBlurred?: boolean; + showOverlay?: boolean; +} + +function QRCodeDisplay(props: QRCodeDisplayProps) { + const { uri, icon, isBlurred = false, showOverlay = false } = props; + + return ( +
+ {/* @ts-expect-error: qrcode.react uses a different React type version */} + + {showOverlay && ( +
+
+
+ )} +
+ ); +} + export interface WalletConnectScreenProps { provider: WalletProvider; successPageDuration: number | undefined; @@ -434,17 +491,22 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { p.chainInfo.namespace === inputProvider.chainInfo.namespace, ); - if (!provider) { - throw new Error("WalletConnect provider not found"); - } + // if provider is not found then that means that the user entered this screen + // while WalletConnect was still initializing, and then it failed to initialize + useEffect(() => { + if (!provider) { + closeModal(); + } + }, [provider, closeModal]); - const connectedAccount = provider.connectedAddresses?.[0] ?? null; + const connectedAccount = provider?.connectedAddresses?.[0] ?? null; // Initial connection effect useEffect(() => { if (provider) { latestProviderRef.current = provider; - if (!isConnecting) { + // we don't try to connect if WalletConnect is still initializing or we are already connecting + if (!isConnecting && !provider.isLoading) { runAction(provider); } } @@ -480,7 +542,7 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { const handleCopy = () => { setShowCopied(true); - navigator.clipboard.writeText(`${provider.uri}`); + navigator.clipboard.writeText(`${provider?.uri}`); setTimeout(() => { setShowCopied(false); }, 1500); @@ -529,12 +591,12 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) {
Wallet connect logo Wallet connect logo
@@ -588,6 +650,30 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { )}
+ ) : provider?.isLoading ? ( +
+ + + {/* this is a skeleton for the copy button to maintain layout */} +
+ +
+ Initializing WalletConnect... +
+
+ Preparing your connection. This will only take a moment +
+
) : (
- {provider.uri && ( + {provider?.uri && ( <> - {/* @ts-expect-error: qrcode.react uses a different React type version */} - = ({ // to refresh the uri, there is no need to refresh the wallets state if (evt?.type === "pairingExpired") { debouncedFetchWalletProviders(); + return; + } + + // if WalletConnect initialization failed, refresh providers + // (the failed provider will be removed from the list) + if (evt?.type === "failed") { + console.error("WalletConnect initialization failed:", evt.error); + debouncedFetchWalletProviders(); + return; + } + + if (evt?.type === "initialized") { + // this updates our walletProvider state + const providers = await debouncedFetchWalletProviders(); + + // if we have an active session, we need to restore any possibly connected + // WalletConnect wallets since its now initialized + if (session) { + const wcProviders = providers.filter( + (p) => p.interfaceType === WalletInterfaceType.WalletConnect, + ); + + const wcWallets = await fetchWallets({ + walletProviders: wcProviders, + connectedOnly: true, + }); + + if (wcWallets.length > 0) { + setWallets((prev) => [...prev, ...wcWallets]); + } + } + + return; } // any other event (disconnect, chain switch, accounts changed) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc4149ddf..62c5de24b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,7 +59,7 @@ importers: version: 5.1.1(rollup@4.50.1) '@rollup/plugin-babel': specifier: 5.3.0 - version: 5.3.0(@babel/core@7.28.5)(@types/babel__core@7.20.5)(rollup@4.50.1) + version: 5.3.0(@babel/core@7.26.9)(@types/babel__core@7.20.5)(rollup@4.50.1) '@rollup/plugin-node-resolve': specifier: 16.0.0 version: 16.0.0(rollup@4.50.1) @@ -185,7 +185,7 @@ importers: version: 0.13.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -245,7 +245,7 @@ importers: version: 0.13.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -376,7 +376,7 @@ importers: version: 16.0.3 next: specifier: 15.5.2 - version: 15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -430,7 +430,7 @@ importers: version: 3.2.25 next: specifier: 15.5.2 - version: 15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -481,7 +481,7 @@ importers: version: 18.2.6 next: specifier: 15.5.2 - version: 15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -532,7 +532,7 @@ importers: version: 18.2.6 next: specifier: 15.5.2 - version: 15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -640,7 +640,7 @@ importers: version: 4.0.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -880,7 +880,7 @@ importers: version: 29.7.0(@types/node@24.7.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.7.1)(typescript@5.4.3)) ts-jest: specifier: ^29.1.2 - version: 29.4.1(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(jest-util@29.7.0)(jest@29.7.0(@types/node@24.7.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.7.1)(typescript@5.4.3)))(typescript@5.4.3) + version: 29.4.1(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@24.7.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.7.1)(typescript@5.4.3)))(typescript@5.4.3) tsx: specifier: ^4.7.0 version: 4.20.6 @@ -1058,7 +1058,7 @@ importers: version: 0.13.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -1116,7 +1116,7 @@ importers: version: 0.13.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -1516,7 +1516,7 @@ importers: version: 16.0.3 next: specifier: 15.5.2 - version: 15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -1603,7 +1603,7 @@ importers: version: 0.363.0(react@18.3.1) next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1715,7 +1715,7 @@ importers: version: 0.13.0 next: specifier: 14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -1812,7 +1812,7 @@ importers: version: 0.13.0 next: specifier: 14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -1893,7 +1893,7 @@ importers: version: 0.13.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -2304,7 +2304,7 @@ importers: version: 0.13.0 next: specifier: ^14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -2438,7 +2438,7 @@ importers: version: 0.13.0 next: specifier: 14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) npm: specifier: ^9.7.2 version: 9.9.4 @@ -2844,7 +2844,7 @@ importers: version: 0.542.0(react@18.2.0) next: specifier: 14.2.32 - version: 14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -3036,7 +3036,7 @@ importers: dependencies: '@react-native-async-storage/async-storage': specifier: ^2.2.0 - version: 2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)) + version: 1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)) '@turnkey/api-key-stamper': specifier: workspace:* version: link:../api-key-stamper @@ -3065,11 +3065,11 @@ importers: specifier: ^1.1.0 version: 1.1.0 '@walletconnect/sign-client': - specifier: ^2.21.8 - version: 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) + specifier: ^2.23.0 + version: 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/types': - specifier: ^2.21.8 - version: 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + specifier: ^2.23.0 + version: 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) cross-fetch: specifier: ^3.1.5 version: 3.2.0(encoding@0.1.13) @@ -3568,7 +3568,7 @@ importers: version: 1.12.15 next: specifier: '^15.2.3 ' - version: 15.5.4(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) + version: 15.5.4(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) react-apple-login: specifier: ^1.1.6 version: 1.1.6(prop-types@15.8.1)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) @@ -6359,10 +6359,6 @@ packages: resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.2': - resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} - engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.6': resolution: {integrity: sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==} engines: {node: ^14.21.3 || >=16} @@ -9301,9 +9297,9 @@ packages: resolution: {integrity: sha512-Tp4MHJYcdWD846PH//2r+Mu4wz1/ZU/fr9av1UWFiaYQ2t2TPLDiZxjLw54AAEpMqlEHemwCgiRiAmjR1NDdTQ==} engines: {node: '>=18'} - '@walletconnect/core@2.21.8': - resolution: {integrity: sha512-MD1SY7KAeHWvufiBK8C1MwP9/pxxI7SnKi/rHYfjco2Xvke+M+Bbm2OzvuSN7dYZvwLTkZCiJmBccTNVPCpSUQ==} - engines: {node: '>=18'} + '@walletconnect/core@2.23.0': + resolution: {integrity: sha512-W++xuXf+AsMPrBWn1It8GheIbCTp1ynTQP+aoFB86eUwyCtSiK7UQsn/+vJZdwElrn+Ptp2A0RqQx2onTMVHjQ==} + engines: {node: '>=18.20.8'} '@walletconnect/environment@1.0.1': resolution: {integrity: sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==} @@ -9344,6 +9340,9 @@ packages: '@walletconnect/logger@2.1.2': resolution: {integrity: sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw==} + '@walletconnect/logger@3.0.0': + resolution: {integrity: sha512-DDktPBFdmt5d7U3sbp4e3fQHNS1b6amsR8FmtOnt6L2SnV7VfcZr8VmAGL12zetAR+4fndegbREmX0P8Mw6eDg==} + '@walletconnect/relay-api@1.0.11': resolution: {integrity: sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==} @@ -9369,9 +9368,8 @@ packages: resolution: {integrity: sha512-QaXzmPsMnKGV6tc4UcdnQVNOz4zyXgarvdIQibJ4L3EmLat73r5ZVl4c0cCOcoaV7rgM9Wbphgu5E/7jNcd3Zg==} deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' - '@walletconnect/sign-client@2.21.8': - resolution: {integrity: sha512-lTcUbMjQ0YUZ5wzCLhpBeS9OkWYgLLly6BddEp2+pm4QxiwCCU2Nao0nBJXgzKbZYQOgrEGqtdm/7ze67gjzRA==} - deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + '@walletconnect/sign-client@2.23.0': + resolution: {integrity: sha512-Nzf5x/LnQgC0Yjk0NmkT8kdrIMcScpALiFm9gP0n3CulL+dkf3HumqWzdoTmQSqGPxwHu/TNhGOaRKZLGQXSqw==} '@walletconnect/solana-adapter@0.0.8': resolution: {integrity: sha512-Qb7MT8SdkeBldfUCmF+rYW6vL98mxPuT1yAwww5X2vpx7xEPZvFCoAKnyT5fXu0v56rMxhW3MGejnHyyYdDY7Q==} @@ -9394,8 +9392,8 @@ packages: '@walletconnect/types@2.21.1': resolution: {integrity: sha512-UeefNadqP6IyfwWC1Yi7ux+ljbP2R66PLfDrDm8izmvlPmYlqRerJWJvYO4t0Vvr9wrG4Ko7E0c4M7FaPKT/sQ==} - '@walletconnect/types@2.21.8': - resolution: {integrity: sha512-xuLIPrLxe6viMu8Uk28Nf0sgyMy+4oT0mroOjBe5Vqyft8GTiwUBKZXmrGU9uDzZsYVn1FXLO9CkuNHXda3ODA==} + '@walletconnect/types@2.23.0': + resolution: {integrity: sha512-9ZEOJyx/kNVCRncDHh3Qr9eH7Ih1dXBFB4k1J8iEudkv3t4GhYpXhqIt2kNdQWluPb1BBB4wEuckAT96yKuA8g==} '@walletconnect/universal-provider@2.19.0': resolution: {integrity: sha512-e9JvadT5F8QwdLmd7qBrmACq04MT7LQEe1m3X2Fzvs3DWo8dzY8QbacnJy4XSv5PCdxMWnua+2EavBk8nrI9QA==} @@ -9425,8 +9423,8 @@ packages: '@walletconnect/utils@2.21.1': resolution: {integrity: sha512-VPZvTcrNQCkbGOjFRbC24mm/pzbRMUq2DSQoiHlhh0X1U7ZhuIrzVtAoKsrzu6rqjz0EEtGxCr3K1TGRqDG4NA==} - '@walletconnect/utils@2.21.8': - resolution: {integrity: sha512-HtMraGJ9qXo55l4wGSM1aZvyz0XVv460iWhlRGAyRl9Yz8RQeKyXavDhwBfcTFha/6kwLxPExqQ+MURtKeVVXw==} + '@walletconnect/utils@2.23.0': + resolution: {integrity: sha512-bVyv4Hl+/wVGueZ6rEO0eYgDy5deSBA4JjpJHAMOdaNoYs05NTE1HymV2lfPQQHuqc7suYexo9jwuW7i3JLuAA==} '@walletconnect/window-getters@1.0.1': resolution: {integrity: sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==} @@ -13256,14 +13254,6 @@ packages: typescript: optional: true - ox@0.7.1: - resolution: {integrity: sha512-+k9fY9PRNuAMHRFIUbiK9Nt5seYHHzSQs9Bj+iMETcGtlpS7SmBzcGSVUQO3+nqGLEiNK4598pHNFlVRaZbRsg==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - ox@0.8.7: resolution: {integrity: sha512-W1f0FiMf9NZqtHPEDEAEkyzZDwbIKfmH2qmQx8NNiQ/9JhxrSblmtLJsSfTtQG5YKowLOnBlLVguCyxm/7ztxw==} peerDependencies: @@ -13452,6 +13442,13 @@ packages: pino-std-serializers@4.0.0: resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@10.0.0: + resolution: {integrity: sha512-eI9pKwWEix40kfvSzqEP6ldqOoBIN7dwD/o91TY5z8vQI12sAffpR/pOqAD1IVVwIVHDpHjkq0joBPdJD0rafA==} + hasBin: true + pino@7.11.0: resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} hasBin: true @@ -13808,6 +13805,9 @@ packages: process-warning@1.0.0: resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -14174,6 +14174,10 @@ packages: resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} engines: {node: '>= 12.13.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + recast@0.21.5: resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} engines: {node: '>= 4'} @@ -14547,6 +14551,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slow-redact@0.3.2: + resolution: {integrity: sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -14923,6 +14930,9 @@ packages: thread-stream@0.15.2: resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + three-mesh-bvh@0.8.3: resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} peerDependencies: @@ -15554,14 +15564,6 @@ packages: typescript: optional: true - viem@2.31.0: - resolution: {integrity: sha512-U7OMQ6yqK+bRbEIarf2vqxL7unSEQvNxvML/1zG7suAmKuJmipqdVTVJGKBCJiYsm/EremyO2FS4dHIPpGv+eA==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - viem@2.34.0: resolution: {integrity: sha512-HJZG9Wt0DLX042MG0PK17tpataxtdAEhpta9/Q44FqKwy3xZMI5Lx4jF+zZPuXFuYjZ68R0PXqRwlswHs6r4gA==} peerDependencies: @@ -20606,10 +20608,6 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 - '@noble/curves@1.9.2': - dependencies: - '@noble/hashes': 1.8.0 - '@noble/curves@1.9.6': dependencies: '@noble/hashes': 1.8.0 @@ -21420,6 +21418,11 @@ snapshots: react-native: 0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10) optional: true + '@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))': + dependencies: + merge-options: 3.0.4 + react-native: 0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10) + '@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@19.1.17)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))': dependencies: merge-options: 3.0.4 @@ -21431,11 +21434,6 @@ snapshots: merge-options: 3.0.4 react-native: 0.76.5(@babel/core@7.26.9)(@babel/preset-env@7.20.2(@babel/core@7.26.9))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10) - '@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))': - dependencies: - merge-options: 3.0.4 - react-native: 0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10) - '@react-native/assets-registry@0.76.5': {} '@react-native/babel-plugin-codegen@0.76.5(@babel/preset-env@7.20.2(@babel/core@7.26.9))': @@ -26474,7 +26472,7 @@ snapshots: '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 @@ -26651,7 +26649,7 @@ snapshots: '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/logger': 2.1.2 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 @@ -26688,21 +26686,21 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/core@2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/jsonrpc-ws-connection': 1.0.16(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) - '@walletconnect/logger': 2.1.2 + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/logger': 3.0.0 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) - '@walletconnect/utils': 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/utils': 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(typescript@5.4.3)(zod@3.25.76) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.39.3 events: 3.3.0 @@ -26867,7 +26865,7 @@ snapshots: '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/sign-client': 2.21.1(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@4.1.11) '@walletconnect/types': 2.21.1 '@walletconnect/universal-provider': 2.21.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@4.1.11) @@ -26975,13 +26973,13 @@ snapshots: - ioredis - uploadthing - '@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@19.1.17)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))': + '@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))': dependencies: '@walletconnect/safe-json': 1.0.2 idb-keyval: 6.2.2 unstorage: 1.17.1(idb-keyval@6.2.2) optionalDependencies: - '@react-native-async-storage/async-storage': 1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@19.1.17)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)) + '@react-native-async-storage/async-storage': 1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -27002,13 +27000,13 @@ snapshots: - ioredis - uploadthing - '@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))': + '@walletconnect/keyvaluestorage@1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@19.1.17)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))': dependencies: '@walletconnect/safe-json': 1.0.2 idb-keyval: 6.2.2 unstorage: 1.17.1(idb-keyval@6.2.2) optionalDependencies: - '@react-native-async-storage/async-storage': 2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)) + '@react-native-async-storage/async-storage': 1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@19.1.17)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -27034,6 +27032,11 @@ snapshots: '@walletconnect/safe-json': 1.0.2 pino: 7.11.0 + '@walletconnect/logger@3.0.0': + dependencies: + '@walletconnect/safe-json': 1.0.2 + pino: 10.0.0 + '@walletconnect/relay-api@1.0.11': dependencies: '@walletconnect/jsonrpc-types': 1.0.4 @@ -27412,16 +27415,16 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/sign-client@2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@walletconnect/core': 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/core': 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/logger': 2.1.2 + '@walletconnect/logger': 3.0.0 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) - '@walletconnect/utils': 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) + '@walletconnect/types': 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/utils': 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(typescript@5.4.3)(zod@3.25.76) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -27551,7 +27554,7 @@ snapshots: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -27609,7 +27612,7 @@ snapshots: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/logger': 2.1.2 events: 3.3.0 transitivePeerDependencies: @@ -27662,13 +27665,13 @@ snapshots: - ioredis - uploadthing - '@walletconnect/types@2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))': + '@walletconnect/types@2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-types': 1.0.4 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) - '@walletconnect/logger': 2.1.2 + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/logger': 3.0.0 events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -27899,7 +27902,7 @@ snapshots: '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/logger': 2.1.2 '@walletconnect/sign-client': 2.21.0(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@4.1.11) '@walletconnect/types': 2.21.0 @@ -28060,7 +28063,7 @@ snapshots: '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/logger': 2.1.2 '@walletconnect/sign-client': 2.21.1(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@4.1.11) '@walletconnect/types': 2.21.1 @@ -28321,7 +28324,7 @@ snapshots: '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 @@ -28498,7 +28501,7 @@ snapshots: '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@5.0.10))) '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 @@ -28536,28 +28539,28 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76)': + '@walletconnect/utils@2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10)))(typescript@5.4.3)(zod@3.25.76)': dependencies: '@msgpack/msgpack': 3.1.2 '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.2 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@walletconnect/jsonrpc-utils': 1.0.8 - '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/logger': 3.0.0 '@walletconnect/relay-api': 1.0.11 '@walletconnect/relay-auth': 1.1.0 '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 - '@walletconnect/types': 2.21.8(@react-native-async-storage/async-storage@2.2.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) + '@walletconnect/types': 2.23.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.5(@babel/core@7.28.5)(@babel/preset-env@7.20.2(@babel/core@7.28.5))(@types/react@18.3.24)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.1.0)(utf-8-validate@5.0.10))) '@walletconnect/window-getters': 1.0.1 '@walletconnect/window-metadata': 1.0.1 blakejs: 1.2.1 bs58: 6.0.0 detect-browser: 5.3.0 - query-string: 7.1.3 + ox: 0.9.3(typescript@5.4.3)(zod@3.25.76) uint8arrays: 3.1.1 - viem: 2.31.0(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -28575,12 +28578,10 @@ snapshots: - '@vercel/functions' - '@vercel/kv' - aws4fetch - - bufferutil - db0 - ioredis - typescript - uploadthing - - utf-8-validate - zod '@walletconnect/window-getters@1.0.1': @@ -30582,8 +30583,8 @@ snapshots: '@typescript-eslint/parser': 8.43.0(eslint@8.56.0)(typescript@5.4.3) eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.56.0) eslint-plugin-react: 7.37.5(eslint@8.56.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.56.0) @@ -30602,8 +30603,8 @@ snapshots: '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.5.1)) @@ -30622,8 +30623,8 @@ snapshots: '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.5.1)) @@ -30642,7 +30643,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@8.1.1) @@ -30653,11 +30654,11 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@8.1.1) @@ -30668,22 +30669,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1(supports-color@8.1.1) - eslint: 8.56.0 - get-tsconfig: 4.10.1 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.1.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -30698,25 +30684,25 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.43.0(eslint@8.56.0)(typescript@5.4.3) eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.56.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -30749,7 +30735,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -30760,7 +30746,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@8.56.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.56.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -30778,7 +30764,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -30789,7 +30775,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -33413,6 +33399,58 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + next@14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + dependencies: + '@next/env': 14.2.32 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001754 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.1(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.32 + '@next/swc-darwin-x64': 14.2.32 + '@next/swc-linux-arm64-gnu': 14.2.32 + '@next/swc-linux-arm64-musl': 14.2.32 + '@next/swc-linux-x64-gnu': 14.2.32 + '@next/swc-linux-x64-musl': 14.2.32 + '@next/swc-win32-arm64-msvc': 14.2.32 + '@next/swc-win32-ia32-msvc': 14.2.32 + '@next/swc-win32-x64-msvc': 14.2.32 + '@playwright/test': 1.56.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@14.2.32(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 14.2.32 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001754 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.32 + '@next/swc-darwin-x64': 14.2.32 + '@next/swc-linux-arm64-gnu': 14.2.32 + '@next/swc-linux-arm64-musl': 14.2.32 + '@next/swc-linux-x64-gnu': 14.2.32 + '@next/swc-linux-x64-musl': 14.2.32 + '@next/swc-win32-arm64-msvc': 14.2.32 + '@next/swc-win32-ia32-msvc': 14.2.32 + '@next/swc-win32-x64-msvc': 14.2.32 + '@playwright/test': 1.56.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@14.2.32(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 14.2.32 @@ -33465,7 +33503,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next@15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 15.5.2 '@swc/helpers': 0.5.15 @@ -33473,7 +33511,7 @@ snapshots: postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.6(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.2.0) + styled-jsx: 5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.2.0) optionalDependencies: '@next/swc-darwin-arm64': 15.5.2 '@next/swc-darwin-x64': 15.5.2 @@ -33489,7 +33527,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.5.2(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.5.2(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.5.2 '@swc/helpers': 0.5.15 @@ -33497,7 +33535,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.2 '@next/swc-darwin-x64': 15.5.2 @@ -33513,30 +33551,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.5.4(@babel/core@7.26.9)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): - dependencies: - '@next/env': 15.5.4 - '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001754 - postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.2.0) - optionalDependencies: - '@next/swc-darwin-arm64': 15.5.4 - '@next/swc-darwin-x64': 15.5.4 - '@next/swc-linux-arm64-gnu': 15.5.4 - '@next/swc-linux-arm64-musl': 15.5.4 - '@next/swc-linux-x64-gnu': 15.5.4 - '@next/swc-linux-x64-musl': 15.5.4 - '@next/swc-win32-arm64-msvc': 15.5.4 - '@next/swc-win32-x64-msvc': 15.5.4 - '@playwright/test': 1.56.1 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - next@15.5.4(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.5.4 @@ -33545,7 +33559,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 19.1.0(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.4 '@next/swc-darwin-x64': 15.5.4 @@ -33847,21 +33861,6 @@ snapshots: transitivePeerDependencies: - zod - ox@0.7.1(typescript@5.4.3)(zod@3.25.76): - dependencies: - '@adraffy/ens-normalize': 1.11.0 - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.0 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.4.3)(zod@3.25.76) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.4.3 - transitivePeerDependencies: - - zod - ox@0.8.7(typescript@5.1.3)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.0 @@ -34154,6 +34153,22 @@ snapshots: pino-std-serializers@4.0.0: {} + pino-std-serializers@7.0.0: {} + + pino@10.0.0: + dependencies: + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + slow-redact: 0.3.2 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + pino@7.11.0: dependencies: atomic-sleep: 1.0.0 @@ -34564,6 +34579,8 @@ snapshots: process-warning@1.0.0: {} + process-warning@5.0.0: {} + process@0.11.10: {} promise-worker-transferable@1.0.4: @@ -35277,6 +35294,8 @@ snapshots: real-require@0.1.0: {} + real-require@0.2.0: {} + recast@0.21.5: dependencies: ast-types: 0.15.2 @@ -35768,6 +35787,8 @@ snapshots: slash@3.0.0: {} + slow-redact@0.3.2: {} + smart-buffer@4.2.0: {} snake-case@3.0.4: @@ -36016,28 +36037,36 @@ snapshots: style-inject@0.3.0: {} - styled-jsx@5.1.1(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.2.0): + styled-jsx@5.1.1(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.2.0): dependencies: client-only: 0.0.1 react: 18.2.0 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.26.9 babel-plugin-macros: 3.1.0 - styled-jsx@5.1.1(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.3.1): + styled-jsx@5.1.1(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.26.9 babel-plugin-macros: 3.1.0 - styled-jsx@5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.2.0): + styled-jsx@5.1.1(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.2.0): dependencies: client-only: 0.0.1 react: 18.2.0 optionalDependencies: - '@babel/core': 7.26.9 + '@babel/core': 7.28.5 + babel-plugin-macros: 3.1.0 + + styled-jsx@5.1.1(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + optionalDependencies: + '@babel/core': 7.28.5 babel-plugin-macros: 3.1.0 styled-jsx@5.1.6(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.2.0): @@ -36045,15 +36074,15 @@ snapshots: client-only: 0.0.1 react: 18.2.0 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.26.9 babel-plugin-macros: 3.1.0 - styled-jsx@5.1.6(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.26.9)(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.26.9 babel-plugin-macros: 3.1.0 styled-jsx@5.1.6(@babel/core@7.28.5)(babel-plugin-macros@3.1.0)(react@19.1.0): @@ -36230,6 +36259,10 @@ snapshots: dependencies: real-require: 0.1.0 + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + three-mesh-bvh@0.8.3(three@0.160.1): dependencies: three: 0.160.1 @@ -36349,26 +36382,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.1(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(jest-util@29.7.0)(jest@29.7.0(@types/node@24.7.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.7.1)(typescript@5.4.3)))(typescript@5.4.3): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.8 - jest: 29.7.0(@types/node@24.7.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.7.1)(typescript@5.4.3)) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.7.2 - type-fest: 4.41.0 - typescript: 5.4.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.26.9 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.9) - jest-util: 29.7.0 - ts-jest@29.4.1(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.3.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3): dependencies: bs-logger: 0.2.6 @@ -36997,23 +37010,6 @@ snapshots: - utf-8-validate - zod - viem@2.31.0(bufferutil@4.0.9)(typescript@5.4.3)(utf-8-validate@5.0.10)(zod@3.25.76): - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.0.8(typescript@5.4.3)(zod@3.25.76) - isows: 1.0.7(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.7.1(typescript@5.4.3)(zod@3.25.76) - ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) - optionalDependencies: - typescript: 5.4.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - viem@2.34.0(bufferutil@4.0.9)(typescript@5.1.3)(utf-8-validate@5.0.10)(zod@3.25.76): dependencies: '@noble/curves': 1.9.6 From e1bd68f963d6bbd9c797b1a8f077efadccdec421 Mon Sep 17 00:00:00 2001 From: Mohammed Odeh Date: Thu, 13 Nov 2025 14:50:33 -0500 Subject: [PATCH 3/5] fix race condition with WalletConnect init finishing before sessions init --- .changeset/six-laws-see.md | 9 +++++ .changeset/wild-monkeys-report.md | 6 ++++ .../src/components/auth/Wallet.tsx | 33 +++++++++++-------- .../src/components/auth/index.tsx | 8 +---- .../src/components/user/ConnectWallet.tsx | 4 +-- .../src/providers/client/Provider.tsx | 22 +++++++++---- 6 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 .changeset/six-laws-see.md create mode 100644 .changeset/wild-monkeys-report.md diff --git a/.changeset/six-laws-see.md b/.changeset/six-laws-see.md new file mode 100644 index 000000000..b59708ab2 --- /dev/null +++ b/.changeset/six-laws-see.md @@ -0,0 +1,9 @@ +--- +"@turnkey/core": minor +--- + +- Parallelized stamper and session initialization +- Separated WalletConnect initialization from client init +- Optimized `fetchWallet` by reducing redundant queries and running wallet/user fetches in parallel +- Added optional `authenticatorAddresses` param to `fetchWalletAccounts()` +- Updated to latest `@walletconnect/sign-client` for performance improvements diff --git a/.changeset/wild-monkeys-report.md b/.changeset/wild-monkeys-report.md new file mode 100644 index 000000000..0263c7c33 --- /dev/null +++ b/.changeset/wild-monkeys-report.md @@ -0,0 +1,6 @@ +--- +"@turnkey/react-wallet-kit": minor +--- + +- Fixed unnecessary re-renders by ensuring all `useCallback` hooks include only direct dependencies +- ConnectWallet and Auth model updated to show WalletConnect loading state during initialization diff --git a/packages/react-wallet-kit/src/components/auth/Wallet.tsx b/packages/react-wallet-kit/src/components/auth/Wallet.tsx index eddda4fbb..36d858d0c 100644 --- a/packages/react-wallet-kit/src/components/auth/Wallet.tsx +++ b/packages/react-wallet-kit/src/components/auth/Wallet.tsx @@ -60,8 +60,13 @@ const canDisconnect = ( ); }; +interface ExternalWalletChainSelectorProps { + providers: WalletProvider[]; + onDisconnect?: ((provider: WalletProvider) => Promise) | undefined; + onSelect: (provider: WalletProvider) => Promise; +} export function ExternalWalletChainSelector( - props: ExternalWalletSelectorProps, + props: ExternalWalletChainSelectorProps, ) { const { providers, onSelect, onDisconnect } = props; @@ -69,13 +74,15 @@ export function ExternalWalletChainSelector( const { isMobile, closeModal } = useModal(); // we find matching providers in current state - const currentProviders = providers.map((inputProvider) => - walletProviders.find( - (p) => - p.interfaceType === inputProvider.interfaceType && - p.chainInfo.namespace === inputProvider.chainInfo.namespace, - ), - ).filter((p): p is WalletProvider => p !== undefined); + const currentProviders = providers + .map((inputProvider) => + walletProviders.find( + (p) => + p.interfaceType === inputProvider.interfaceType && + p.chainInfo.namespace === inputProvider.chainInfo.namespace, + ), + ) + .filter((p): p is WalletProvider => p !== undefined); // if no providers are found then that means that the user entered this screen // while WalletConnect was still initializing, and then it failed to initialize @@ -182,18 +189,19 @@ export function ExternalWalletChainSelector( } interface ExternalWalletSelectorProps { - providers: WalletProvider[]; onDisconnect?: ((provider: WalletProvider) => Promise) | undefined; onSelect: (provider: WalletProvider) => Promise; } export function ExternalWalletSelector(props: ExternalWalletSelectorProps) { - const { providers, onDisconnect, onSelect } = props; + const { onDisconnect, onSelect } = props; + const { pushPage, popPage, isMobile } = useModal(); + const { walletProviders } = useTurnkey(); const shouldShowDisconnect = onDisconnect !== undefined; // Group providers by name - const grouped = providers.reduce>( + const grouped = walletProviders.reduce>( (acc, provider) => { const name = provider.info.name; if (!acc[name]) acc[name] = []; @@ -430,7 +438,6 @@ function QRCodeDisplay(props: QRCodeDisplayProps) { return (
- {/* @ts-expect-error: qrcode.react uses a different React type version */} { diff --git a/packages/react-wallet-kit/src/components/auth/index.tsx b/packages/react-wallet-kit/src/components/auth/index.tsx index 5a10537bc..eb5d30da3 100644 --- a/packages/react-wallet-kit/src/components/auth/index.tsx +++ b/packages/react-wallet-kit/src/components/auth/index.tsx @@ -44,7 +44,6 @@ export function AuthComponent({ const { config, clientState, - walletProviders, handleGoogleOauth, handleAppleOauth, handleFacebookOauth, @@ -325,12 +324,7 @@ export function AuthComponent({ try { pushPage({ key: "Select wallet provider", - content: ( - - ), + content: , }); } catch (error) { throw new Error(`Error fetching wallet providers: ${error}`); diff --git a/packages/react-wallet-kit/src/components/user/ConnectWallet.tsx b/packages/react-wallet-kit/src/components/user/ConnectWallet.tsx index 293c82a7c..01d12be0a 100644 --- a/packages/react-wallet-kit/src/components/user/ConnectWallet.tsx +++ b/packages/react-wallet-kit/src/components/user/ConnectWallet.tsx @@ -16,12 +16,11 @@ import { import { isWalletConnect } from "../../utils/utils"; interface ConnectWalletModalProps { - providers: WalletProvider[]; successPageDuration?: number | undefined; onSuccess: (type: "connect" | "disconnect", account: WalletAccount) => void; } export function ConnectWalletModal(props: ConnectWalletModalProps) { - const { providers, successPageDuration, onSuccess } = props; + const { successPageDuration, onSuccess } = props; const { pushPage, closeModal } = useModal(); const { wallets, connectWalletAccount, disconnectWalletAccount } = useTurnkey(); @@ -132,7 +131,6 @@ export function ConnectWalletModal(props: ConnectWalletModalProps) { }; return ( diff --git a/packages/react-wallet-kit/src/providers/client/Provider.tsx b/packages/react-wallet-kit/src/providers/client/Provider.tsx index 182ce0ff7..be9dbbd1d 100644 --- a/packages/react-wallet-kit/src/providers/client/Provider.tsx +++ b/packages/react-wallet-kit/src/providers/client/Provider.tsx @@ -843,7 +843,19 @@ export const ClientProvider: React.FC = ({ return; } setSession(allLocalStorageSessions[activeSessionKey]); - await Promise.all([refreshUser(), refreshWallets()]); + + // we use `fetchWallets()` instead of `refreshWallets()` here to avoid a race condition + // specifically, if WalletConnect finishes initializing before this promise resolves, + // `refreshWallets()` could overwrite the WalletConnect wallet state with an outdated + // list of wallets that doesn’t yet include the WalletConnect wallets + const [, wallets] = await Promise.all([refreshUser(), fetchWallets()]); + + // the prev wallets should only ever be WalletConnect wallets + if (wallets) { + setWallets((prev) => [...prev, ...wallets]); + } + + console.log("finished fetching wallets inside initializeSessions"); return; } @@ -1013,8 +1025,9 @@ export const ClientProvider: React.FC = ({ // if we have an active session, we need to restore any possibly connected // WalletConnect wallets since its now initialized - if (session) { - const wcProviders = providers.filter( + const currentSession = await getSession(); + if (currentSession) { + const wcProviders = providers?.filter( (p) => p.interfaceType === WalletInterfaceType.WalletConnect, ); @@ -5277,14 +5290,11 @@ export const ClientProvider: React.FC = ({ ); } - const providers = await fetchWalletProviders(); - return new Promise((resolve, reject) => { pushPage({ key: "Connect wallet", content: ( Date: Fri, 21 Nov 2025 10:56:44 -0500 Subject: [PATCH 4/5] resolve comments --- .changeset/rude-signs-like.md | 5 + .changeset/six-laws-see.md | 1 + packages/core/scripts/codegen.js | 40 +- packages/core/src/__clients__/core.ts | 46 +- .../core/src/__generated__/sdk-client-base.ts | 396 +++++++----------- .../src/__wallet__/wallet-connect/base.ts | 26 ++ packages/core/src/__wallet__/web/manager.ts | 9 +- .../src/components/auth/Wallet.tsx | 126 +++--- .../src/providers/client/Provider.tsx | 9 +- .../src/providers/modal/Provider.tsx | 4 + packages/react-wallet-kit/src/utils/utils.ts | 3 - packages/sdk-types/src/index.ts | 1 + 12 files changed, 319 insertions(+), 347 deletions(-) create mode 100644 .changeset/rude-signs-like.md diff --git a/.changeset/rude-signs-like.md b/.changeset/rude-signs-like.md new file mode 100644 index 000000000..099e20237 --- /dev/null +++ b/.changeset/rude-signs-like.md @@ -0,0 +1,5 @@ +--- +"@turnkey/sdk-types": minor +--- + +Added `WALLET_CONNECT_INITIALIZATION_ERROR` error code diff --git a/.changeset/six-laws-see.md b/.changeset/six-laws-see.md index b59708ab2..1d38bdec0 100644 --- a/.changeset/six-laws-see.md +++ b/.changeset/six-laws-see.md @@ -2,6 +2,7 @@ "@turnkey/core": minor --- +- Fixed `stamp*` methods for query endpoints in `httpClient` incorrectly formatting request body - Parallelized stamper and session initialization - Separated WalletConnect initialization from client init - Optimized `fetchWallet` by reducing redundant queries and running wallet/user fetches in parallel diff --git a/packages/core/scripts/codegen.js b/packages/core/scripts/codegen.js index 24512a400..408469938 100644 --- a/packages/core/scripts/codegen.js +++ b/packages/core/scripts/codegen.js @@ -369,7 +369,7 @@ const generateSDKClientFromSwagger = async ( const inputType = `T${operationNameWithoutNamespace}Body`; const responseType = `T${operationNameWithoutNamespace}Response`; - // For query methods + // for query methods, we use flat body structure if (methodType === "query") { codeBuffer.push( `\n\t${methodName} = async (input: SdkTypes.${inputType}${ @@ -434,8 +434,38 @@ const generateSDKClientFromSwagger = async ( VERSIONED_ACTIVITY_TYPES[unversionedActivityType]; // generate a stamping method for each method - codeBuffer.push( - `\n\tstamp${operationNameWithoutNamespace} = async (input: SdkTypes.${inputType}, stampWith?: StamperType): Promise => { + + if (methodType === "noop") { + // we skip stamp method generation for noop methods + continue; + } else if (methodType === "query") { + // for query methods, we use flat body structure + codeBuffer.push( + `\n\tstamp${operationNameWithoutNamespace} = async (input: SdkTypes.${inputType}, stampWith?: StamperType): Promise => { + const activeStamper = this.getStamper(stampWith); + if (!activeStamper) { + return undefined; + } + + const fullUrl = this.config.apiBaseUrl + "${endpointPath}"; + const body = { + ...input, + organizationId: input.organizationId + }; + + const stringifiedBody = JSON.stringify(body); + const stamp = await activeStamper.stamp(stringifiedBody); + return { + body: stringifiedBody, + stamp: stamp, + url: fullUrl, + }; + }`, + ); + } else { + // for activity and activityDecision methods, use parameters wrapper and type field + codeBuffer.push( + `\n\tstamp${operationNameWithoutNamespace} = async (input: SdkTypes.${inputType}, stampWith?: StamperType): Promise => { const activeStamper = this.getStamper(stampWith); if (!activeStamper) { return undefined; @@ -454,7 +484,6 @@ const generateSDKClientFromSwagger = async ( type: "${versionedActivityType ?? unversionedActivityType}" }; - const stringifiedBody = JSON.stringify(bodyWithType); const stamp = await activeStamper.stamp(stringifiedBody); return { @@ -463,7 +492,8 @@ const generateSDKClientFromSwagger = async ( url: fullUrl, }; }`, - ); + ); + } } for (const endpointPath in authProxySwaggerSpec.paths) { diff --git a/packages/core/src/__clients__/core.ts b/packages/core/src/__clients__/core.ts index 1d33e008b..918a4f392 100644 --- a/packages/core/src/__clients__/core.ts +++ b/packages/core/src/__clients__/core.ts @@ -18,6 +18,8 @@ import { type v1CreatePolicyIntentV3, type v1BootProof, ProxyTSignupResponse, + TGetWalletsResponse, + TGetUserResponse, } from "@turnkey/sdk-types"; import { DEFAULT_SESSION_EXPIRATION_IN_SECONDS, @@ -1966,11 +1968,22 @@ export class TurnkeyClient { | Promise<{ ethereum: string[]; solana: string[] }> | undefined; if (organizationId && userId && this.walletManager?.connector) { - userPromise = this.fetchUser({ - userId, - organizationId, + const signedUserRequest = await this.httpClient.stampGetUser( + { + userId, + organizationId, + }, stampWith, - }).then(getAuthenticatorAddresses); + ); + if (!signedUserRequest) { + throw new TurnkeyError( + "Failed to stamp user request", + TurnkeyErrorCodes.INVALID_REQUEST, + ); + } + userPromise = sendSignedRequest( + signedUserRequest, + ).then((response) => getAuthenticatorAddresses(response.user)); } // if connectedOnly is true, we skip fetching embedded wallets @@ -1989,18 +2002,29 @@ export class TurnkeyClient { ); } + // we stamp the wallet request first + // this is done to avoid concurrent passkey prompts + const signedWalletsRequest = await this.httpClient.stampGetWallets( + { + organizationId, + }, + stampWith, + ); + + if (!signedWalletsRequest) { + throw new TurnkeyError( + "Failed to stamp wallet request", + TurnkeyErrorCodes.INVALID_REQUEST, + ); + } + const [accounts, walletsRes] = await Promise.all([ fetchAllWalletAccountsWithCursor( this.httpClient, organizationId, stampWith, ), - this.httpClient.getWallets( - { - organizationId, - }, - stampWith, - ), + sendSignedRequest(signedWalletsRequest), ]); // create a map of walletId to EmbeddedWallet for easy lookup @@ -2726,7 +2750,7 @@ export class TurnkeyClient { ); } - return userResponse.user as v1User; + return userResponse.user; }, { errorMessage: "Failed to fetch user", diff --git a/packages/core/src/__generated__/sdk-client-base.ts b/packages/core/src/__generated__/sdk-client-base.ts index 6ba64efa0..77f5d1960 100644 --- a/packages/core/src/__generated__/sdk-client-base.ts +++ b/packages/core/src/__generated__/sdk-client-base.ts @@ -277,16 +277,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_activity"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_ACTIVITY", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -322,16 +319,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_api_key"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_API_KEY", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -367,16 +361,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_api_keys"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_API_KEYS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -412,16 +403,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_attestation"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_ATTESTATION_DOCUMENT", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -457,17 +445,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_authenticator"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_AUTHENTICATOR", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -503,17 +488,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_authenticators"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_AUTHENTICATORS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -549,16 +531,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_boot_proof"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_BOOT_PROOF", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -594,17 +573,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_latest_boot_proof"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_LATEST_BOOT_PROOF", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -640,17 +616,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_oauth2_credential"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_OAUTH2CREDENTIAL", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -686,17 +659,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_oauth_providers"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_OAUTH_PROVIDERS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -732,17 +702,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_onramp_transaction_status"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_ON_RAMP_TRANSACTION_STATUS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -778,17 +745,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_organization"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_ORGANIZATION", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -824,17 +788,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_organization_configs"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_ORGANIZATION_CONFIGS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -870,16 +831,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_policy"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_POLICY", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -915,17 +873,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_policy_evaluations"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_POLICY_EVALUATIONS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -961,16 +916,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_private_key"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_PRIVATE_KEY", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1006,17 +958,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_smart_contract_interface"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_SMART_CONTRACT_INTERFACE", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1052,16 +1001,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_user"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_USER", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1097,16 +1043,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_wallet"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_WALLET", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1142,17 +1085,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/get_wallet_account"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_WALLET_ACCOUNT", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1188,16 +1128,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_activities"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_ACTIVITIES", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1233,16 +1170,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_app_proofs"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_APP_PROOFS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1278,17 +1212,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_fiat_on_ramp_credentials"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_LIST_FIAT_ON_RAMP_CREDENTIALS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1324,17 +1255,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_oauth2_credentials"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_LIST_OAUTH2CREDENTIALS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1370,16 +1298,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_policies"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_POLICIES", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1415,17 +1340,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_private_key_tags"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_LIST_PRIVATE_KEY_TAGS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1461,17 +1383,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_private_keys"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_PRIVATE_KEYS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1507,18 +1426,15 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_smart_contract_interfaces"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_SMART_CONTRACT_INTERFACES", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1554,16 +1470,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_suborgs"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_SUB_ORG_IDS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1599,16 +1512,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_user_tags"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_LIST_USER_TAGS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1644,16 +1554,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_users"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_USERS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1689,17 +1596,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_verified_suborgs"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_VERIFIED_SUB_ORG_IDS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1735,17 +1639,14 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_wallet_accounts"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_WALLET_ACCOUNTS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1781,16 +1682,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/list_wallets"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_WALLETS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -1826,16 +1724,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/public/v1/query/whoami"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_GET_WHOAMI", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, @@ -5751,16 +5646,13 @@ export class TurnkeySDKClientBase { return undefined; } - const { organizationId, ...parameters } = input; - const fullUrl = this.config.apiBaseUrl + "/tkhq/api/v1/test_rate_limits"; - const bodyWithType = { - parameters, - organizationId, - type: "ACTIVITY_TYPE_TEST_RATE_LIMITS", + const body = { + ...input, + organizationId: input.organizationId, }; - const stringifiedBody = JSON.stringify(bodyWithType); + const stringifiedBody = JSON.stringify(body); const stamp = await activeStamper.stamp(stringifiedBody); return { body: stringifiedBody, diff --git a/packages/core/src/__wallet__/wallet-connect/base.ts b/packages/core/src/__wallet__/wallet-connect/base.ts index fa2375c1e..38aa8c8ad 100644 --- a/packages/core/src/__wallet__/wallet-connect/base.ts +++ b/packages/core/src/__wallet__/wallet-connect/base.ts @@ -40,6 +40,7 @@ export class WalletConnectWallet implements WalletConnectInterface { private uri?: string; private isRegeneratingUri = false; private isInitialized = false; + private initAbortController: AbortController | undefined; private changeListeners = new Set< (event?: WalletConnectChangeEvent) => void @@ -145,6 +146,11 @@ export class WalletConnectWallet implements WalletConnectInterface { * @throws {Error} If no namespaces were configured in constructor. */ async init(): Promise { + // we create a new abort controller for this initialization + // that way if the WalletManager wants to abort it (if this takes too long) + // then it can do so without leaving this init promise in the background + this.initAbortController = new AbortController(); + try { if ( this.ethereumNamespaces.length === 0 && @@ -178,6 +184,26 @@ export class WalletConnectWallet implements WalletConnectInterface { // we emit a failed event this.notifyChange({ type: "failed", error }); throw error; + } finally { + this.initAbortController = undefined; + } + } + + /** + * Aborts the ongoing initialization if one is in progress. + * Emits a failed event with the abort error. + * + * @param error - Optional error to include in the failed event. Defaults to abort message. + */ + abortInit(error?: unknown): void { + if (this.initAbortController) { + this.initAbortController.abort(); + + // we emit failed event so listeners are notified + this.notifyChange({ + type: "failed", + error: error || new Error("WalletConnect initialization was aborted"), + }); } } diff --git a/packages/core/src/__wallet__/web/manager.ts b/packages/core/src/__wallet__/web/manager.ts index 51cee7d33..1948665a6 100644 --- a/packages/core/src/__wallet__/web/manager.ts +++ b/packages/core/src/__wallet__/web/manager.ts @@ -82,11 +82,10 @@ export class WebWalletManager { this.initializers.push(() => withTimeout(wcUnified.init(), 15000, "WalletConnect wallet").catch( (err) => { - // WalletConnect can be a bit unreliable, so instead of throwing an error - // we handle failures silently to avoid blocking the rest of the client - // from initializing. If setup fails, we also remove WalletConnect - // from the available wallets list - console.error("Failed to init WalletConnect wallet:", err); + // WalletConnect can be a bit unreliable, we don't want to load forever + // so we limit the initialization time to 15 seconds. After that we emit a + // failure event and remove WalletConnect from the available wallets + wcUnified.abortInit(err); this.removeWalletInterface(WalletInterfaceType.WalletConnect); }, ), diff --git a/packages/react-wallet-kit/src/components/auth/Wallet.tsx b/packages/react-wallet-kit/src/components/auth/Wallet.tsx index 36d858d0c..6af6d9bc6 100644 --- a/packages/react-wallet-kit/src/components/auth/Wallet.tsx +++ b/packages/react-wallet-kit/src/components/auth/Wallet.tsx @@ -1,5 +1,6 @@ import { ActionButton, BaseButton } from "../design/Buttons"; import { EthereumLogo, SolanaLogo } from "../design/Svg"; +import { Spinner } from "../design/Spinners"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheck, @@ -71,7 +72,7 @@ export function ExternalWalletChainSelector( const { providers, onSelect, onDisconnect } = props; const { walletProviders } = useTurnkey(); - const { isMobile, closeModal } = useModal(); + const { isMobile, popPage } = useModal(); // we find matching providers in current state const currentProviders = providers @@ -88,9 +89,9 @@ export function ExternalWalletChainSelector( // while WalletConnect was still initializing, and then it failed to initialize useEffect(() => { if (currentProviders.length === 0) { - closeModal(); + popPage(); } - }, [currentProviders.length, closeModal]); + }, [currentProviders.length, popPage]); const shouldShowDisconnect = onDisconnect !== undefined; @@ -429,12 +430,11 @@ export function ConnectedIndicator(props: ConnectedIndicatorProps) { interface QRCodeDisplayProps { uri: string; icon: string; - isBlurred?: boolean; - showOverlay?: boolean; + isLoading?: boolean; } function QRCodeDisplay(props: QRCodeDisplayProps) { - const { uri, icon, isBlurred = false, showOverlay = false } = props; + const { uri, icon, isLoading } = props; return (
@@ -442,7 +442,7 @@ function QRCodeDisplay(props: QRCodeDisplayProps) { className={clsx( "block border border-modal-background-dark/20 dark:border-modal-background-light/20", "shadow-[0_0_42px] shadow-primary-light/50 dark:shadow-[0_0_42px] dark:shadow-primary-dark/50", - isBlurred && "blur-sm", + isLoading && "blur-sm", )} value={uri} imageSettings={{ @@ -453,9 +453,9 @@ function QRCodeDisplay(props: QRCodeDisplayProps) { }} size={200} /> - {showOverlay && ( + {isLoading && (
-
+
)}
@@ -477,7 +477,7 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { successPageDuration, } = props; - const { pushPage, closeModal, isMobile } = useModal(); + const { pushPage, popPages, closeModal, isMobile } = useModal(); const { walletProviders } = useTurnkey(); const [isDisconnecting, setIsDisconnecting] = useState(false); @@ -502,9 +502,11 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { // while WalletConnect was still initializing, and then it failed to initialize useEffect(() => { if (!provider) { - closeModal(); + // we have to go back two pages here since thats the screen + // we get wallet providers from state + popPages(2); } - }, [provider, closeModal]); + }, [provider, popPages]); const connectedAccount = provider?.connectedAddresses?.[0] ?? null; @@ -512,8 +514,10 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { useEffect(() => { if (provider) { latestProviderRef.current = provider; + // we don't try to connect if WalletConnect is still initializing or we are already connecting if (!isConnecting && !provider.isLoading) { + setShowConnectedScreen(provider.connectedAddresses?.length > 0); runAction(provider); } } @@ -657,7 +661,7 @@ export function WalletConnectScreen(props: WalletConnectScreenProps) { )}
- ) : provider?.isLoading ? ( + ) : (
- {/* this is a skeleton for the copy button to maintain layout */} -
+ + Copy link -
- Initializing WalletConnect... -
-
- Preparing your connection. This will only take a moment -
-
- ) : ( -
- {provider?.uri && ( - <> - - + - Copy link - -
- - {showCopied && ( - - )} -
-
- - )} + /> + {showCopied && ( + + )} +
+
- Use your phone + {provider?.isLoading + ? "Initializing WalletConnect..." + : "Use your phone"}
- Scan this QR code with your WalletConnect-compatible wallet to - connect + {provider?.isLoading + ? "Preparing your connection. This will only take a moment." + : "Scan this QR code with your WalletConnect-compatible wallet to connect"}
)} diff --git a/packages/react-wallet-kit/src/providers/client/Provider.tsx b/packages/react-wallet-kit/src/providers/client/Provider.tsx index be9dbbd1d..176d42f08 100644 --- a/packages/react-wallet-kit/src/providers/client/Provider.tsx +++ b/packages/react-wallet-kit/src/providers/client/Provider.tsx @@ -1014,8 +1014,15 @@ export const ClientProvider: React.FC = ({ // if WalletConnect initialization failed, refresh providers // (the failed provider will be removed from the list) if (evt?.type === "failed") { - console.error("WalletConnect initialization failed:", evt.error); debouncedFetchWalletProviders(); + callbacks?.onError?.( + new TurnkeyError( + `WalletConnect initialization failed: ${evt.error || "Unknown error"}`, + TurnkeyErrorCodes.WALLET_CONNECT_INITIALIZATION_ERROR, + evt.error, + ), + ); + return; } diff --git a/packages/react-wallet-kit/src/providers/modal/Provider.tsx b/packages/react-wallet-kit/src/providers/modal/Provider.tsx index ba2bb4a49..aac4de2f2 100644 --- a/packages/react-wallet-kit/src/providers/modal/Provider.tsx +++ b/packages/react-wallet-kit/src/providers/modal/Provider.tsx @@ -17,6 +17,7 @@ export type ModalContextType = { openSheet: (page: ModalPage) => void; pushPage: (page: ModalPage) => void; popPage: () => void; + popPages: (count: number) => void; closeModal: () => void; closeSheet: () => void; modalStack: ModalPage[]; @@ -39,6 +40,8 @@ export function ModalProvider({ children }: { children: ReactNode }) { const pushPage = (page: ModalPage) => setModalStack((prev) => [...prev, page]); const popPage = () => setModalStack((prev) => prev.slice(0, -1)); + const popPages = (count: number) => + setModalStack((prev) => prev.slice(0, Math.max(0, prev.length - count))); const closeModal = () => { setModalStack([]); setSheet(null); @@ -52,6 +55,7 @@ export function ModalProvider({ children }: { children: ReactNode }) { openSheet, pushPage, popPage, + popPages, closeModal, closeSheet, modalStack, diff --git a/packages/react-wallet-kit/src/utils/utils.ts b/packages/react-wallet-kit/src/utils/utils.ts index aa7e977a2..586906aad 100644 --- a/packages/react-wallet-kit/src/utils/utils.ts +++ b/packages/react-wallet-kit/src/utils/utils.ts @@ -104,9 +104,6 @@ export async function withTurnkeyErrorHandling( // this should never happen. If it does, it means an SDK function is making // a session-based API call without providing a `sessionExpireFn()` for handling // SESSION_EXPIRED errors in `withTurnkeyErrorHandling()` - console.error( - "SESSION_EXPIRED encountered but no sessionExpireFn provided. Contact Turnkey support.", - ); throw new TurnkeyError( "SESSION_EXPIRED received without sessionExpireFn handler", TurnkeyErrorCodes.INTERNAL_ERROR, diff --git a/packages/sdk-types/src/index.ts b/packages/sdk-types/src/index.ts index 9b3dbfa3c..3a3a4a692 100644 --- a/packages/sdk-types/src/index.ts +++ b/packages/sdk-types/src/index.ts @@ -52,6 +52,7 @@ export enum TurnkeyErrorCodes { CREATE_PASSKEY_ERROR = "CREATE_PASSKEY_ERROR", SELECT_PASSKEY_CANCELLED = "SELECT_PASSKEY_CANCELLED", CONNECT_WALLET_CANCELLED = "CONNECT_WALLET_CANCELLED", + WALLET_CONNECT_INITIALIZATION_ERROR = "WALLET_CONNECT_INITIALIZATION_ERROR", WALLET_CONNECT_EXPIRED = "WALLET_CONNECT_EXPIRED", PASSKEY_SIGNUP_AUTH_ERROR = "PASSKEY_SIGNUP_AUTH_ERROR", PASSKEY_LOGIN_AUTH_ERROR = "PASSKEY_LOGIN_AUTH_ERROR", From 558949c083c3cd90bee739f47d80ca5232d45cc3 Mon Sep 17 00:00:00 2001 From: Mohammed Odeh Date: Fri, 21 Nov 2025 11:04:16 -0500 Subject: [PATCH 5/5] rebased --- .../with-sdk-js/e2e/auth-component.spec.ts | 4 +- .../src/components/auth/Wallet.tsx | 1 + pnpm-lock.yaml | 240 ++++++++---------- 3 files changed, 116 insertions(+), 129 deletions(-) diff --git a/examples/with-sdk-js/e2e/auth-component.spec.ts b/examples/with-sdk-js/e2e/auth-component.spec.ts index 44e88c0ad..e52fafb71 100644 --- a/examples/with-sdk-js/e2e/auth-component.spec.ts +++ b/examples/with-sdk-js/e2e/auth-component.spec.ts @@ -140,6 +140,8 @@ test.describe("auth with wallet", async () => { await page .getByTestId(walletKitSelectors.authComponent.walletAuthButton) .click(); - await expect(page.getByText("Select wallet provider")).toBeVisible(); + await expect( + page.getByText(/Select wallet provider|Select chain/), + ).toBeVisible(); }); }); diff --git a/packages/react-wallet-kit/src/components/auth/Wallet.tsx b/packages/react-wallet-kit/src/components/auth/Wallet.tsx index 6af6d9bc6..669aecd4a 100644 --- a/packages/react-wallet-kit/src/components/auth/Wallet.tsx +++ b/packages/react-wallet-kit/src/components/auth/Wallet.tsx @@ -438,6 +438,7 @@ function QRCodeDisplay(props: QRCodeDisplayProps) { return (
+ {/* @ts-expect-error: qrcode.react uses a different React type version */}