Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/metamask-partial-grant.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 18 additions & 6 deletions packages/wallets/src/metamask/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
20 changes: 19 additions & 1 deletion packages/wallets/test/metamask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>[] = [];
sessionData = { sessionScopes: { eip155: { accounts: [`eip155:1:${ETHEREUM_ADDRESS}`] } } };

await metamaskWallet.connectMetamask.connectWallet({
addChain: (chainWallet) => addChainCalls.push(chainWallet as Record<string, unknown>),
})([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<string, unknown>[] = [];
sessionData = { sessionScopes: {} };

await expect(
metamaskWallet.connectMetamask.connectWallet({
addChain: (chainWallet) => addChainCalls.push(chainWallet as Record<string, unknown>),
Expand Down
Loading