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),