From 081c56992c408d36cc80ca2c991eba37687abaf2 Mon Sep 17 00:00:00 2001 From: towan Date: Thu, 30 Jul 2026 15:20:14 +0400 Subject: [PATCH] fix(metamask): connect the granted subset on partial multichain session grants MetaMask can approve fewer scopes than requested (no Solana account, an EVM network not added). The connector failed the entire connect on the first ungranted scope; now it connects the granted chains (still atomic for that set) and throws wallet_chain_not_supported only when nothing was granted. Found by UI nightly validation (widget requests 14 chains). Co-Authored-By: Claude Fable 5 --- .changeset/metamask-partial-grant.md | 5 +++++ packages/wallets/src/metamask/index.ts | 24 ++++++++++++++++++------ packages/wallets/test/metamask.test.ts | 20 +++++++++++++++++++- 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 .changeset/metamask-partial-grant.md diff --git a/.changeset/metamask-partial-grant.md b/.changeset/metamask-partial-grant.md new file mode 100644 index 0000000..13620fa --- /dev/null +++ b/.changeset/metamask-partial-grant.md @@ -0,0 +1,5 @@ +--- +"@swapkit/wallets": patch +--- + +MetaMask multichain connect now connects the granted subset of requested chains instead of failing the whole connect when the wallet approves only some scopes; it throws only when nothing was granted. diff --git a/packages/wallets/src/metamask/index.ts b/packages/wallets/src/metamask/index.ts index 660709b..47d093c 100644 --- a/packages/wallets/src/metamask/index.ts +++ b/packages/wallets/src/metamask/index.ts @@ -168,13 +168,25 @@ export const metamaskWallet = createWallet({ throw new SwapKitError("core_wallet_connection_not_found", { wallet: WalletOption.METAMASK }); } - const chainWallets = await Promise.all( - chainScopes.map(async ({ chain, scope }) => { - const address = findAddressForScope(session, scope); - if (!address) { - throw new SwapKitError("wallet_chain_not_supported", { chain, scope, wallet: WalletOption.METAMASK }); - } + // MetaMask may grant fewer scopes than requested (no Solana account, an EVM + // network not added/approved). Connect the granted subset and skip the rest — + // consumers see exactly the chains that arrived via addChain. Only a fully + // empty grant is an error. + const granted = chainScopes.flatMap(({ chain, scope }) => { + const address = findAddressForScope(session, scope); + return address ? [{ address, chain, scope }] : []; + }); + if (granted.length === 0) { + throw new SwapKitError("wallet_chain_not_supported", { + chains: filteredChains, + scopes, + wallet: WalletOption.METAMASK, + }); + } + + const chainWallets = await Promise.all( + granted.map(async ({ address, chain, scope }) => { const disconnect = () => client.disconnect([scope]); if (isEVMChain(chain)) { diff --git a/packages/wallets/test/metamask.test.ts b/packages/wallets/test/metamask.test.ts index bacaa82..a8f5c2e 100644 --- a/packages/wallets/test/metamask.test.ts +++ b/packages/wallets/test/metamask.test.ts @@ -139,11 +139,29 @@ describe("metamask multichain wallet", () => { expect(connectionError).toHaveProperty("errorKey", "core_wallet_connection_not_found"); }); - test("does not add partially granted chains", async () => { + test("connects the granted subset when the wallet approves only some scopes", async () => { const { metamaskWallet } = await import("../src/metamask"); const addChainCalls: Record[] = []; sessionData = { sessionScopes: { eip155: { accounts: [`eip155:1:${ETHEREUM_ADDRESS}`] } } }; + await metamaskWallet.connectMetamask.connectWallet({ + addChain: (chainWallet) => addChainCalls.push(chainWallet as Record), + })([Chain.Ethereum, Chain.Solana], { + dapp: { name: "SwapKit Test" }, + supportedNetworks: { + "eip155:1": "https://ethereum.example/rpc", + [SOLANA_MAINNET_CAIP2]: "https://solana.example/rpc", + }, + }); + + expect(addChainCalls.map(({ chain }) => chain)).toEqual([Chain.Ethereum]); + }); + + test("throws only when the wallet grants none of the requested scopes", async () => { + const { metamaskWallet } = await import("../src/metamask"); + const addChainCalls: Record[] = []; + sessionData = { sessionScopes: {} }; + await expect( metamaskWallet.connectMetamask.connectWallet({ addChain: (chainWallet) => addChainCalls.push(chainWallet as Record),