@meshconnect/uwc-wallet-connect-connector 0.4.13 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"network-switch-service.d.ts","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAIxD;;GAEG;AACH,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,iBAAiB;gBADjB,mBAAmB,EAAE,mBAAmB,EACxC,iBAAiB,EAAE,iBAAiB;IAG9C;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAgEpE"}
1
+ {"version":3,"file":"network-switch-service.d.ts","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAIxD;;GAEG;AACH,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,iBAAiB;gBADjB,mBAAmB,EAAE,mBAAmB,EACxC,iBAAiB,EAAE,iBAAiB;IAG9C;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAmFpE"}
@@ -25,7 +25,25 @@ export class NetworkSwitchService {
25
25
  // Check if the requested network is available in the session
26
26
  const networkChain = network.id;
27
27
  const namespace = network.namespace;
28
- if (!session.namespaces[namespace]?.chains?.includes(networkChain)) {
28
+ const approvedChains = session.namespaces[namespace]?.chains ?? [];
29
+ const approvedAccounts = session.namespaces[namespace]?.accounts ?? [];
30
+ // CAIP-25: accounts encode `chainId:address`, so they implicitly
31
+ // declare chain approval. Some wallets (e.g. ZenGo) ship an empty
32
+ // `chains` array even when accounts cover every requested chain.
33
+ const chainsFromAccounts = new Set();
34
+ for (const account of approvedAccounts) {
35
+ try {
36
+ chainsFromAccounts.add(parseAccount(account).networkId);
37
+ }
38
+ catch {
39
+ // Skip malformed account strings
40
+ }
41
+ }
42
+ const effectiveChains = new Set([
43
+ ...approvedChains,
44
+ ...chainsFromAccounts
45
+ ]);
46
+ if (!effectiveChains.has(networkChain)) {
29
47
  throw new Error(`Network ${networkChain} is not available in the current session`);
30
48
  }
31
49
  // For WalletConnect, we can't directly switch networks
@@ -1 +1 @@
1
- {"version":3,"file":"network-switch-service.js","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEjD;;GAEG;AACH,MAAM,OAAO,oBAAoB;IAErB;IACA;IAFV,YACU,mBAAwC,EACxC,iBAAoC;QADpC,wBAAmB,GAAnB,mBAAmB,CAAqB;QACxC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAC3C,CAAC;IAEJ;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,OAAgB;QAClC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,CAAA;YACnD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;YAC7D,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,CAAA;YAEzD,6DAA6D;YAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,EAAE,CAAA;YAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;YAEnC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBACnE,MAAM,IAAI,KAAK,CACb,WAAW,YAAY,0CAA0C,CAClE,CAAA;YACH,CAAC;YAED,uDAAuD;YACvD,yCAAyC;YACzC,iDAAiD;YACjD,uDAAuD;YAEvD,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAA;YAC9D,MAAM,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAC9C,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CACjC,CAAA;YAED,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,gCAAgC,YAAY,EAAE,CAAC,CAAA;YACjE,CAAC;YAED,sCAAsC;YACtC,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,eAAe,CAAC,CAAA;YAEjD,kEAAkE;YAClE,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;YAChD,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;YAEhD,mEAAmE;YACnE,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;gBAC3B,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,IAAI,CAAC;wBAChB,KAAK,EAAE,OAAO,CAAC,KAAK;wBACpB,KAAK,EAAE;4BACL,IAAI,EAAE,cAAc;4BACpB,IAAI,EAAE,YAAY;yBACnB;wBACD,OAAO,EAAE,YAAY;qBACtB,CAAC,CAAA;gBACJ,CAAC;gBAAC,MAAM,CAAC;oBACP,0CAA0C;gBAC5C,CAAC;YACH,CAAC;YAED,OAAO;gBACL,SAAS,EAAE,OAAO,CAAC,EAAE;gBACrB,OAAO;aACR,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,CAAC,KAAK,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"network-switch-service.js","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshconnect/uwc-wallet-connect-connector",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
4
4
  "description": "WalletConnect connector for Universal Wallet Connector",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import type { SessionTypes } from '@walletconnect/types'
3
+ import { NetworkSwitchService } from './network-switch-service'
4
+ import type { ConnectionService } from './connection-service'
5
+ import type { WalletConnectClient } from './walletconnect-client'
6
+ import { polygonNetwork } from '@meshconnect/uwc-constants'
7
+
8
+ interface NamespaceShape {
9
+ chains: string[]
10
+ accounts: string[]
11
+ methods: string[]
12
+ events: string[]
13
+ }
14
+
15
+ function buildSession(
16
+ namespaces: Record<string, NamespaceShape>
17
+ ): SessionTypes.Struct {
18
+ return {
19
+ topic: 'test-topic',
20
+ pairingTopic: 'pairing-topic',
21
+ relay: { protocol: 'irn' },
22
+ expiry: Math.floor(Date.now() / 1000) + 86400,
23
+ acknowledged: true,
24
+ controller: 'test-controller',
25
+ requiredNamespaces: {},
26
+ optionalNamespaces: {},
27
+ sessionProperties: undefined,
28
+ self: {
29
+ publicKey: 'self-key',
30
+ metadata: { name: 'dApp', description: '', url: '', icons: [] }
31
+ },
32
+ peer: {
33
+ publicKey: 'peer-key',
34
+ metadata: { name: 'Wallet', description: '', url: '', icons: [] }
35
+ },
36
+ namespaces
37
+ } as unknown as SessionTypes.Struct
38
+ }
39
+
40
+ describe('NetworkSwitchService', () => {
41
+ let service: NetworkSwitchService
42
+ let mockEmit: ReturnType<typeof vi.fn>
43
+ let mockGetSession: ReturnType<typeof vi.fn>
44
+ let mockSetActiveNetwork: ReturnType<typeof vi.fn>
45
+ let mockSetActiveAddress: ReturnType<typeof vi.fn>
46
+
47
+ beforeEach(() => {
48
+ vi.clearAllMocks()
49
+ mockEmit = vi.fn().mockResolvedValue(undefined)
50
+ mockGetSession = vi.fn()
51
+ mockSetActiveNetwork = vi.fn()
52
+ mockSetActiveAddress = vi.fn()
53
+
54
+ const mockWcClient = {
55
+ getClient: vi.fn().mockResolvedValue({ emit: mockEmit })
56
+ } as unknown as WalletConnectClient
57
+ const mockConnectionService = {
58
+ getSession: mockGetSession,
59
+ setActiveNetwork: mockSetActiveNetwork,
60
+ setActiveAddress: mockSetActiveAddress
61
+ } as unknown as ConnectionService
62
+
63
+ service = new NetworkSwitchService(mockWcClient, mockConnectionService)
64
+ })
65
+
66
+ describe('switchNetwork', () => {
67
+ it('succeeds when chain is in chains AND there is a matching account (spec-compliant wallets)', async () => {
68
+ mockGetSession.mockReturnValue(
69
+ buildSession({
70
+ eip155: {
71
+ chains: ['eip155:1', 'eip155:137'],
72
+ accounts: [
73
+ 'eip155:1:0xabc0000000000000000000000000000000000001',
74
+ 'eip155:137:0xabc0000000000000000000000000000000000001'
75
+ ],
76
+ methods: ['eth_sendTransaction'],
77
+ events: ['chainChanged']
78
+ }
79
+ })
80
+ )
81
+
82
+ const result = await service.switchNetwork(polygonNetwork)
83
+
84
+ expect(result).toEqual({
85
+ networkId: 'eip155:137',
86
+ address: '0xabc0000000000000000000000000000000000001'
87
+ })
88
+ expect(mockSetActiveNetwork).toHaveBeenCalledWith(polygonNetwork)
89
+ expect(mockSetActiveAddress).toHaveBeenCalledWith(
90
+ '0xabc0000000000000000000000000000000000001'
91
+ )
92
+ expect(mockEmit).toHaveBeenCalledWith(
93
+ expect.objectContaining({
94
+ chainId: 'eip155:137',
95
+ event: { name: 'chainChanged', data: 'eip155:137' }
96
+ })
97
+ )
98
+ })
99
+
100
+ // ZenGo's WC v2 quirk:
101
+ // approval namespace ships with `chains: []` even though `accounts` covers
102
+ // every requested chain. Per WC v2 sign-namespaces spec the wallet SHOULD
103
+ // populate `chains`, but `accounts` already encodes (chainId:address) so
104
+ // it's the source of truth. UWC must accept this shape — ZenGo users were
105
+ // otherwise unable to switch to any chain in their session.
106
+ // Source: live console capture, 2026-04-17, react-example + ZenGo iOS
107
+ // peer.metadata.name: "Zengo Wallet"
108
+ it('succeeds when chains is empty but accounts cover the requested chain (ZenGo WC v2 quirk)', async () => {
109
+ mockGetSession.mockReturnValue(
110
+ buildSession({
111
+ eip155: {
112
+ chains: [],
113
+ accounts: [
114
+ 'eip155:1:0xabc0000000000000000000000000000000000002',
115
+ 'eip155:137:0xabc0000000000000000000000000000000000002',
116
+ 'eip155:8453:0xabc0000000000000000000000000000000000002'
117
+ ],
118
+ methods: ['eth_sendTransaction'],
119
+ events: ['chainChanged']
120
+ }
121
+ })
122
+ )
123
+
124
+ const result = await service.switchNetwork(polygonNetwork)
125
+
126
+ expect(result).toEqual({
127
+ networkId: 'eip155:137',
128
+ address: '0xabc0000000000000000000000000000000000002'
129
+ })
130
+ expect(mockSetActiveNetwork).toHaveBeenCalledWith(polygonNetwork)
131
+ })
132
+
133
+ it('throws when chain is in neither chains nor accounts', async () => {
134
+ mockGetSession.mockReturnValue(
135
+ buildSession({
136
+ eip155: {
137
+ chains: ['eip155:1'],
138
+ accounts: ['eip155:1:0xabc0000000000000000000000000000000000003'],
139
+ methods: ['eth_sendTransaction'],
140
+ events: ['chainChanged']
141
+ }
142
+ })
143
+ )
144
+
145
+ await expect(service.switchNetwork(polygonNetwork)).rejects.toThrow(
146
+ /not available in the current session/
147
+ )
148
+ expect(mockSetActiveNetwork).not.toHaveBeenCalled()
149
+ })
150
+
151
+ it('throws when there is no active session', async () => {
152
+ mockGetSession.mockReturnValue(null)
153
+
154
+ await expect(service.switchNetwork(polygonNetwork)).rejects.toThrow(
155
+ /No active session/
156
+ )
157
+ })
158
+
159
+ it('throws when the eip155 namespace is missing entirely', async () => {
160
+ mockGetSession.mockReturnValue(buildSession({}))
161
+
162
+ await expect(service.switchNetwork(polygonNetwork)).rejects.toThrow(
163
+ /not available in the current session/
164
+ )
165
+ })
166
+
167
+ // Inverse of the ZenGo quirk: if a wallet declares the chain in `chains`
168
+ // but ships zero accounts for it, the session is unusable for that chain.
169
+ // The first guard sees the chain in `chains` and lets it through, but the
170
+ // second guard (find matching account) catches the inconsistency with a
171
+ // clear message naming the chain.
172
+ it('throws when chain is declared in chains but no account exists for it', async () => {
173
+ mockGetSession.mockReturnValue(
174
+ buildSession({
175
+ eip155: {
176
+ chains: ['eip155:1', 'eip155:137'],
177
+ accounts: ['eip155:1:0xabc0000000000000000000000000000000000004'],
178
+ methods: ['eth_sendTransaction'],
179
+ events: ['chainChanged']
180
+ }
181
+ })
182
+ )
183
+
184
+ await expect(service.switchNetwork(polygonNetwork)).rejects.toThrow(
185
+ /No account found for network eip155:137/
186
+ )
187
+ expect(mockSetActiveNetwork).not.toHaveBeenCalled()
188
+ })
189
+ })
190
+ })
@@ -29,7 +29,26 @@ export class NetworkSwitchService {
29
29
  const networkChain = network.id
30
30
  const namespace = network.namespace
31
31
 
32
- if (!session.namespaces[namespace]?.chains?.includes(networkChain)) {
32
+ const approvedChains = session.namespaces[namespace]?.chains ?? []
33
+ const approvedAccounts = session.namespaces[namespace]?.accounts ?? []
34
+
35
+ // CAIP-25: accounts encode `chainId:address`, so they implicitly
36
+ // declare chain approval. Some wallets (e.g. ZenGo) ship an empty
37
+ // `chains` array even when accounts cover every requested chain.
38
+ const chainsFromAccounts = new Set<string>()
39
+ for (const account of approvedAccounts) {
40
+ try {
41
+ chainsFromAccounts.add(parseAccount(account).networkId)
42
+ } catch {
43
+ // Skip malformed account strings
44
+ }
45
+ }
46
+ const effectiveChains = new Set<string>([
47
+ ...approvedChains,
48
+ ...chainsFromAccounts
49
+ ])
50
+
51
+ if (!effectiveChains.has(networkChain)) {
33
52
  throw new Error(
34
53
  `Network ${networkChain} is not available in the current session`
35
54
  )