@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.
- package/dist/services/network-switch-service.d.ts.map +1 -1
- package/dist/services/network-switch-service.js +19 -1
- package/dist/services/network-switch-service.js.map +1 -1
- package/package.json +1 -1
- package/src/services/network-switch-service.test.ts +190 -0
- package/src/services/network-switch-service.ts +20 -1
|
@@ -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;
|
|
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
|
-
|
|
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,
|
|
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
|
@@ -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
|
-
|
|
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
|
)
|