From 573f2faa0d0731cc390e6d9150c753c1c56b208c Mon Sep 17 00:00:00 2001 From: Alex Bakoushin Date: Tue, 30 Sep 2025 18:50:08 +0200 Subject: [PATCH 1/3] namespace chainIds --- .../universal-provider/src/utils/caip25.ts | 12 ++++- .../universal-provider/test/utils.spec.ts | 50 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/providers/universal-provider/src/utils/caip25.ts b/providers/universal-provider/src/utils/caip25.ts index 3a17b8ed33..8283933154 100644 --- a/providers/universal-provider/src/utils/caip25.ts +++ b/providers/universal-provider/src/utils/caip25.ts @@ -44,7 +44,7 @@ export const extractCapabilitiesFromSession = ( address: string, chainIds: string[], ) => { - const { sessionProperties = {}, scopedProperties = {} } = session; + const { sessionProperties = {}, scopedProperties = {}, namespaces = {} } = session; const result: Record = {}; if (!isValidObject(scopedProperties) && !isValidObject(sessionProperties)) { @@ -54,7 +54,15 @@ export const extractCapabilitiesFromSession = ( // get all capabilities from sessionProperties as they apply to all chains/addresses const globalCapabilities = getCapabilitiesFromObject(sessionProperties); - for (const chain of chainIds) { + const namespaceChainIds = + namespaces[EIP155_PREFIX]?.chains + ?.map((chain) => decimalToHex(chain.split(":")[1])) + .filter(Boolean) ?? []; + + // use namespace chainIds if no chainIds are provided + const targetChainIds = chainIds.length > 0 ? chainIds : namespaceChainIds; + + for (const chain of targetChainIds) { const chainId = hexToDecimal(chain); if (!chainId) { continue; diff --git a/providers/universal-provider/test/utils.spec.ts b/providers/universal-provider/test/utils.spec.ts index eb8e019715..82f09e4c20 100644 --- a/providers/universal-provider/test/utils.spec.ts +++ b/providers/universal-provider/test/utils.spec.ts @@ -389,4 +389,54 @@ describe("UniversalProvider utils", function () { }, }); }); + + it("should extract capabilities from session. Case 9", function () { + const session = { + namespaces: { + eip155: { + chains: ["eip155:1", "eip155:137", "eip155:84532"], + }, + }, + scopedProperties: { + "eip155:1": { + atomic: { + status: "unsupported", + }, + }, + "eip155:137": { + "eip155:137:0x0910e12C68d02B561a34569E1367c9AAb42bd811": { + atomic: { + status: "supported", + }, + }, + }, + "eip155:84532": { + "eip155:84532:0x0910e12C68d02B561a34569E1367c9AAb42bd810": { + atomic: { + status: "supported", + }, + }, + }, + }, + } as unknown as SessionTypes.Struct; + + const capabilities = extractCapabilitiesFromSession( + session, + "0x0910e12C68d02B561a34569E1367c9AAb42bd811", + [], + ); + + expect(capabilities).toEqual({ + "0x1": { + atomic: { + status: "unsupported", + }, + }, + "0x89": { + atomic: { + status: "supported", + }, + }, + }); + }); }); From 803f1d128d4ea2a9a7c862127b5c1f571d2e7595 Mon Sep 17 00:00:00 2001 From: Alex Bakoushin Date: Thu, 6 Nov 2025 14:23:13 +0100 Subject: [PATCH 2/3] address feedback --- .../universal-provider/src/utils/caip25.ts | 32 +++++----- .../universal-provider/test/utils.spec.ts | 63 +++++++++++++++++++ 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/providers/universal-provider/src/utils/caip25.ts b/providers/universal-provider/src/utils/caip25.ts index 8283933154..6412f69f9b 100644 --- a/providers/universal-provider/src/utils/caip25.ts +++ b/providers/universal-provider/src/utils/caip25.ts @@ -1,5 +1,5 @@ import { SessionTypes } from "@walletconnect/types"; -import { isValidObject } from "@walletconnect/utils"; +import { isValidObject, parseAccountId } from "@walletconnect/utils"; import { isValidJSONObject } from "./misc"; @@ -54,12 +54,15 @@ export const extractCapabilitiesFromSession = ( // get all capabilities from sessionProperties as they apply to all chains/addresses const globalCapabilities = getCapabilitiesFromObject(sessionProperties); - const namespaceChainIds = - namespaces[EIP155_PREFIX]?.chains - ?.map((chain) => decimalToHex(chain.split(":")[1])) - .filter(Boolean) ?? []; + const namespaceChainIds: string[] = []; + for (const account of namespaces[EIP155_PREFIX]?.accounts ?? []) { + const params = parseAccountId(account); + if (params.address === address) { + namespaceChainIds.push(decimalToHex(params.reference)); + } + } - // use namespace chainIds if no chainIds are provided + // fallback to namespace chainIds if no chainIds are provided const targetChainIds = chainIds.length > 0 ? chainIds : namespaceChainIds; for (const chain of targetChainIds) { @@ -68,19 +71,14 @@ export const extractCapabilitiesFromSession = ( continue; } - result[decimalToHex(chainId)] = globalCapabilities; - const chainSpecific = scopedProperties?.[`${EIP155_PREFIX}:${chainId}`]; + const addressSpecific = chainSpecific?.[`${EIP155_PREFIX}:${chainId}:${address}`]; - if (chainSpecific) { - const addressSpecific = chainSpecific?.[`${EIP155_PREFIX}:${chainId}:${address}`]; - - // use the address specific capabilities if they exist, otherwise use the chain specific capabilities - result[decimalToHex(chainId)] = { - ...result[decimalToHex(chainId)], - ...getCapabilitiesFromObject(addressSpecific || chainSpecific), - }; - } + result[decimalToHex(chainId)] = { + ...globalCapabilities, + ...(chainSpecific ? getCapabilitiesFromObject(chainSpecific) : {}), + ...(addressSpecific ? getCapabilitiesFromObject(addressSpecific) : {}), + }; } // remove any chains that have no capabilities diff --git a/providers/universal-provider/test/utils.spec.ts b/providers/universal-provider/test/utils.spec.ts index 82f09e4c20..755bcfbd57 100644 --- a/providers/universal-provider/test/utils.spec.ts +++ b/providers/universal-provider/test/utils.spec.ts @@ -395,6 +395,11 @@ describe("UniversalProvider utils", function () { namespaces: { eip155: { chains: ["eip155:1", "eip155:137", "eip155:84532"], + accounts: [ + "eip155:1:0x0910e12C68d02B561a34569E1367c9AAb42bd811", + "eip155:137:0x0910e12C68d02B561a34569E1367c9AAb42bd811", + "eip155:84532:0x0910e12C68d02B561a34569E1367c9AAb42bd810", + ], }, }, scopedProperties: { @@ -404,6 +409,9 @@ describe("UniversalProvider utils", function () { }, }, "eip155:137": { + paymasterService: { + supported: true, + }, "eip155:137:0x0910e12C68d02B561a34569E1367c9AAb42bd811": { atomic: { status: "supported", @@ -433,10 +441,65 @@ describe("UniversalProvider utils", function () { }, }, "0x89": { + paymasterService: { + supported: true, + }, atomic: { status: "supported", }, }, }); }); + + it("should extract capabilities from session. Case 10", function () { + const session = { + namespaces: { + eip155: { + chains: ["eip155:1", "eip155:137", "eip155:84532"], + accounts: [ + "eip155:137:0x0910e12C68d02B561a34569E1367c9AAb42bd811", + "eip155:84532:0x0910e12C68d02B561a34569E1367c9AAb42bd810", + ], + }, + }, + scopedProperties: { + "eip155:1": { + atomic: { + status: "unsupported", + }, + }, + "eip155:137": { + paymasterService: { + supported: true, + }, + "eip155:137:0x0910e12C68d02B561a34569E1367c9AAb42bd810": { + atomic: { + status: "supported", + }, + }, + }, + "eip155:84532": { + "eip155:84532:0x0910e12C68d02B561a34569E1367c9AAb42bd810": { + atomic: { + status: "supported", + }, + }, + }, + }, + } as unknown as SessionTypes.Struct; + + const capabilities = extractCapabilitiesFromSession( + session, + "0x0910e12C68d02B561a34569E1367c9AAb42bd811", + [], + ); + + expect(capabilities).toEqual({ + "0x89": { + paymasterService: { + supported: true, + }, + }, + }); + }); }); From a221e8b035d2f57afb58c95184284f47d3875b22 Mon Sep 17 00:00:00 2001 From: Alex Bakoushin Date: Fri, 7 Nov 2025 13:34:28 +0100 Subject: [PATCH 3/3] ensure no dupes --- providers/universal-provider/src/utils/caip25.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/providers/universal-provider/src/utils/caip25.ts b/providers/universal-provider/src/utils/caip25.ts index 788d0f4f74..ce3849c42d 100644 --- a/providers/universal-provider/src/utils/caip25.ts +++ b/providers/universal-provider/src/utils/caip25.ts @@ -54,16 +54,16 @@ export const extractCapabilitiesFromSession = ( // get all capabilities from sessionProperties as they apply to all chains/addresses const globalCapabilities = getCapabilitiesFromObject(sessionProperties); - const namespaceChainIds: string[] = []; + const namespaceChainIds = new Set(); for (const account of namespaces[EIP155_PREFIX]?.accounts ?? []) { const params = parseAccountId(account); if (params.address === address) { - namespaceChainIds.push(decimalToHex(params.reference)); + namespaceChainIds.add(decimalToHex(params.reference)); } } // fallback to namespace chainIds if no chainIds are provided - const targetChainIds = chainIds.length > 0 ? chainIds : namespaceChainIds; + const targetChainIds = chainIds.length > 0 ? chainIds : Array.from(namespaceChainIds); for (const chain of targetChainIds) { const chainId = hexToDecimal(chain);