From fdba75df09c1a5372920d271cf6b9430b1eb6069 Mon Sep 17 00:00:00 2001 From: broody Date: Wed, 20 May 2026 19:31:07 -1000 Subject: [PATCH 1/3] fix(keychain): avoid redundant external wallet chain switch onExternalConnect always requested a wallet_switchStarknetChain even when the wallet was already on the target chain, causing wallets like Ready to prompt a network switch on every connect (and to default the switch dialog to the opposite network). Only switch when the wallet's current chain differs from the controller's target chain. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/hooks/starterpack/external-wallet.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/keychain/src/hooks/starterpack/external-wallet.ts b/packages/keychain/src/hooks/starterpack/external-wallet.ts index 6a0817d1f5..9e3ecbac0c 100644 --- a/packages/keychain/src/hooks/starterpack/external-wallet.ts +++ b/packages/keychain/src/hooks/starterpack/external-wallet.ts @@ -1,8 +1,18 @@ import { useState, useCallback } from "react"; import { ExternalPlatform, ExternalWallet } from "@cartridge/controller"; +import { shortString } from "starknet"; import { useWallets } from "@/hooks/wallets"; import { useConnection } from "../connection"; +function normalizeChainId(chainId: string | number): string { + if (typeof chainId === "number") { + return `0x${chainId.toString(16)}`.toLowerCase(); + } + return ( + chainId.startsWith("0x") ? chainId : shortString.encodeShortString(chainId) + ).toLowerCase(); +} + export interface UseExternalWalletOptions { onError?: (error: Error) => void; } @@ -77,12 +87,22 @@ export function useExternalWallet({ "Braavos does not support `wallet_switchStarknetChain`", ); } else { - const res = await switchChain(wallet.type, chainId.toString()); - if (!res) { - const error = new Error( - `${wallet.name} failed to switch chain (${chainId})`, - ); - throw error; + // Only request a chain switch when the wallet isn't already on the + // target chain. Switching unconditionally makes wallets (e.g. Ready) + // prompt every time, even when no change is needed. + const target = normalizeChainId(chainId); + const current = wallet.chainId + ? normalizeChainId(wallet.chainId) + : undefined; + + if (current !== target) { + const res = await switchChain(wallet.type, chainId.toString()); + if (!res) { + const error = new Error( + `${wallet.name} failed to switch chain (${chainId})`, + ); + throw error; + } } } } From 1fcc196beb2bfaf951544d40c80ad5c626ade9e0 Mon Sep 17 00:00:00 2001 From: broody Date: Wed, 20 May 2026 21:25:27 -1000 Subject: [PATCH 2/3] fix(keychain): derive isMainnet from live chainId, not stale controller ref isMainnet was computed from controller.chainId() with a [controller] dependency, so it only recomputed when the controller object identity changed. After an in-place network switch (or when chainId() wasn't ready at first set) it went stale, leaving isMainnet wrong regardless of the actual connected network. This made the external-wallet flow resolve the wrong target chain (e.g. Sepolia while the controller is on Mainnet) and prompt a needless network switch. Derive isMainnet from the RPC-derived chainId state, which tracks rpcUrl/network changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/keychain/src/hooks/connection.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/keychain/src/hooks/connection.ts b/packages/keychain/src/hooks/connection.ts index d6ae7f5cb9..ec5958c37d 100644 --- a/packages/keychain/src/hooks/connection.ts +++ b/packages/keychain/src/hooks/connection.ts @@ -659,8 +659,13 @@ export function useConnectionValue() { }, [controller?.username, chainId, controller]); useEffect(() => { - setIsMainnet(controller?.chainId() === constants.StarknetChainId.SN_MAIN); - }, [controller]); + // Prefer the RPC-derived chainId state since it tracks network switches; + // controller.chainId() is only re-read when the controller object identity + // changes, so it can go stale (e.g. after an in-place network switch), + // leaving isMainnet wrong regardless of the actual connected network. + const currentChainId = chainId ?? controller?.chainId(); + setIsMainnet(currentChainId === constants.StarknetChainId.SN_MAIN); + }, [chainId, controller]); // Load config when preset is provided useEffect(() => { From 65f2aaf96edc3653175ea104906ce867f0e2a9a9 Mon Sep 17 00:00:00 2001 From: broody Date: Thu, 21 May 2026 08:15:05 -1000 Subject: [PATCH 3/3] chore(keychain): TEMP [chain-debug] logging for external wallet switch Temporary console logging to diagnose the Ready wallet network-switch prompts (isMainnet resolution, resolved target chainId, and the switch decision in onExternalConnect). To be removed once diagnosed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../purchase/checkout/onchain/wallet-drawer.tsx | 7 +++++++ .../keychain/src/components/purchase/wallet/wallet.tsx | 7 +++++++ packages/keychain/src/hooks/connection.ts | 7 +++++++ .../keychain/src/hooks/starterpack/external-wallet.ts | 10 ++++++++++ 4 files changed, 31 insertions(+) diff --git a/packages/keychain/src/components/purchase/checkout/onchain/wallet-drawer.tsx b/packages/keychain/src/components/purchase/checkout/onchain/wallet-drawer.tsx index 57b2cfe31c..996def69bf 100644 --- a/packages/keychain/src/components/purchase/checkout/onchain/wallet-drawer.tsx +++ b/packages/keychain/src/components/purchase/checkout/onchain/wallet-drawer.tsx @@ -131,6 +131,13 @@ export function WalletSelectionDrawer({ (chain) => chain.isMainnet === isMainnet, )?.chainId; + console.log("[chain-debug] wallet-drawer resolve chainId", { + isMainnet, + platform: selectedNetwork.platform, + chains: selectedNetwork.chains, + resolvedChainId: chainId, + }); + if (chainId) { newChainIds.set(selectedNetwork.platform, chainId); } diff --git a/packages/keychain/src/components/purchase/wallet/wallet.tsx b/packages/keychain/src/components/purchase/wallet/wallet.tsx index a61ab8c17f..18b7dee18c 100644 --- a/packages/keychain/src/components/purchase/wallet/wallet.tsx +++ b/packages/keychain/src/components/purchase/wallet/wallet.tsx @@ -89,6 +89,13 @@ export function SelectWallet() { (chain) => chain.isMainnet === isMainnet, )?.chainId; + console.log("[chain-debug] wallet.tsx resolve chainId", { + isMainnet, + platform: network.platform, + chains: network.chains, + resolvedChainId: chainId, + }); + if (chainId) { newChainIds.set(network.platform, chainId); } diff --git a/packages/keychain/src/hooks/connection.ts b/packages/keychain/src/hooks/connection.ts index ec5958c37d..96b0fa88ed 100644 --- a/packages/keychain/src/hooks/connection.ts +++ b/packages/keychain/src/hooks/connection.ts @@ -664,6 +664,13 @@ export function useConnectionValue() { // changes, so it can go stale (e.g. after an in-place network switch), // leaving isMainnet wrong regardless of the actual connected network. const currentChainId = chainId ?? controller?.chainId(); + console.log("[chain-debug] isMainnet effect", { + chainIdState: chainId, + controllerChainId: controller?.chainId(), + currentChainId, + SN_MAIN: constants.StarknetChainId.SN_MAIN, + isMainnet: currentChainId === constants.StarknetChainId.SN_MAIN, + }); setIsMainnet(currentChainId === constants.StarknetChainId.SN_MAIN); }, [chainId, controller]); diff --git a/packages/keychain/src/hooks/starterpack/external-wallet.ts b/packages/keychain/src/hooks/starterpack/external-wallet.ts index 9e3ecbac0c..2bf76b3eb6 100644 --- a/packages/keychain/src/hooks/starterpack/external-wallet.ts +++ b/packages/keychain/src/hooks/starterpack/external-wallet.ts @@ -95,6 +95,16 @@ export function useExternalWallet({ ? normalizeChainId(wallet.chainId) : undefined; + console.log("[chain-debug] onExternalConnect switch decision", { + walletType: wallet.type, + walletChainIdRaw: wallet.chainId, + targetChainIdRaw: chainId, + normalizedCurrent: current, + normalizedTarget: target, + controllerChainId: controller?.chainId(), + willSwitch: current !== target, + }); + if (current !== target) { const res = await switchChain(wallet.type, chainId.toString()); if (!res) {