@meshconnect/uwc-core 2.5.6 → 2.5.8

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.
@@ -17,6 +17,8 @@ export declare class NetworkSwitchService {
17
17
  isWaitingForUserApproval: boolean;
18
18
  };
19
19
  switchNetwork(networkId: NetworkId, options?: ServiceOptions): Promise<void>;
20
+ /** Shared terminal for a switch that landed — real or no-op'd — on `network`. */
21
+ private emitNetworkSwitched;
20
22
  private checkIfApprovalRequired;
21
23
  }
22
24
  //# sourceMappingURL=network-switch-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"network-switch-service.d.ts","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,cAAc,EAMf,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG1D,qBAAa,oBAAoB;IAK7B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,YAAY;IAEpB,OAAO,CAAC,uBAAuB;IAVjC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,wBAAwB,CAAQ;IAExC,YACU,QAAQ,EAAE,OAAO,EAAE,EACnB,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,EAC1C,sBAAsB,EAAE,OAAO,EAC/B,YAAY,EAAE,YAAY,EAE1B,uBAAuB,EAAE,MAAM,EACrC;IAEJ,eAAe;QAEX,SAAS;QACT,wBAAwB;MAE3B;IAEK,aAAa,CACjB,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAsKf;IAED,OAAO,CAAC,uBAAuB;CAyChC"}
1
+ {"version":3,"file":"network-switch-service.d.ts","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,cAAc,EAOf,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAI1D,qBAAa,oBAAoB;IAK7B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,YAAY;IAEpB,OAAO,CAAC,uBAAuB;IAVjC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,wBAAwB,CAAQ;IAExC,YACU,QAAQ,EAAE,OAAO,EAAE,EACnB,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,EAC1C,sBAAsB,EAAE,OAAO,EAC/B,YAAY,EAAE,YAAY,EAE1B,uBAAuB,EAAE,MAAM,EACrC;IAEJ,eAAe;QAEX,SAAS;QACT,wBAAwB;MAE3B;IAEK,aAAa,CACjB,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CA0Nf;IAED,iFAAiF;IACjF,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,uBAAuB;CAyChC"}
@@ -1,4 +1,5 @@
1
1
  import { instrumentHandoff } from '../observability/instrument-handoff';
2
+ import { isAbortError, isUserRejection } from '../utils/to-wallet-error';
2
3
  export class NetworkSwitchService {
3
4
  networks;
4
5
  sessionManager;
@@ -94,30 +95,86 @@ export class NetworkSwitchService {
94
95
  // so `awaitingWallet` marks the real wallet boundary; pre-wallet validation
95
96
  // failures throw above and never enter the funnel.
96
97
  // Pass the appropriate provider based on connection mode.
97
- const result = await instrumentHandoff({
98
- eventManager: this.eventManager,
99
- timeoutMs: this.walletResponseTimeoutMs,
100
- signal: options?.signal
101
- }, {
102
- operation: 'switchNetwork',
103
- connectionMode: session.connectionMode,
104
- walletId: currentWallet.id,
105
- namespace: network.namespace,
106
- chainId: network.id,
107
- targetAlreadyActive
108
- }, async () => {
109
- if ((session.connectionMode === 'injected' ||
110
- session.connectionMode === 'tonConnect') &&
111
- provider) {
112
- return await activeConnector.switchNetwork(network, provider);
113
- }
114
- else if (session.connectionMode === 'walletConnect' && provider) {
115
- return await activeConnector.switchNetwork(network, provider);
116
- }
117
- else {
118
- return await activeConnector.switchNetwork(network);
98
+ let result;
99
+ try {
100
+ result = await instrumentHandoff({
101
+ eventManager: this.eventManager,
102
+ timeoutMs: this.walletResponseTimeoutMs,
103
+ signal: options?.signal
104
+ }, {
105
+ operation: 'switchNetwork',
106
+ connectionMode: session.connectionMode,
107
+ walletId: currentWallet.id,
108
+ namespace: network.namespace,
109
+ chainId: network.id,
110
+ targetAlreadyActive
111
+ }, async () => {
112
+ if ((session.connectionMode === 'injected' ||
113
+ session.connectionMode === 'tonConnect') &&
114
+ provider) {
115
+ return await activeConnector.switchNetwork(network, provider);
116
+ }
117
+ else if (session.connectionMode === 'walletConnect' && provider) {
118
+ return await activeConnector.switchNetwork(network, provider);
119
+ }
120
+ else {
121
+ return await activeConnector.switchNetwork(network);
122
+ }
123
+ });
124
+ }
125
+ catch (error) {
126
+ // A caller-driven cancellation is not a wallet outcome to second-guess —
127
+ // let it propagate untouched, same as instrumentHandoff's own check.
128
+ // Otherwise an abort mid-flight on an already-active target would be
129
+ // swallowed into a silent success instead of the cancellation the
130
+ // caller asked for.
131
+ if (isAbortError(error) || options?.signal?.aborted)
132
+ throw error;
133
+ // An explicit "no" is not a race to paper over: prod data (PRG-714
134
+ // review) shows this exact targetAlreadyActive+rejection window has
135
+ // carried real user declines (errorCode 4001, "Not Allowed"), not
136
+ // just stale-confirmation races. Swallowing one into success would
137
+ // have the caller believe the switch — and a following
138
+ // sendTransaction — was approved. isUserRejection is transport-
139
+ // agnostic (WalletConnectorError{type:'rejected'} or EIP-1193
140
+ // 4001/ACTION_REJECTED), so injected/tonConnect/walletConnect are all
141
+ // covered, unlike a code-4902 narrowing.
142
+ if (isUserRejection(error))
143
+ throw error;
144
+ // PRG-714: some injected providers (MetaMask) race their own
145
+ // `wallet_addEthereumChain` confirmation against this confirmatory
146
+ // switch — the chain is already active per our session, but the
147
+ // provider answers with a stale 4902 "unrecognized chain" rejection.
148
+ // The session is the source of truth here, not that reply: trust it
149
+ // was already active and no-op to success instead of failing an
150
+ // otherwise-successful switch. Not narrowed to error code 4902 —
151
+ // tonConnect/walletConnect connectors don't speak EIP-1193 error
152
+ // codes, and the same race can surface there under a different shape.
153
+ //
154
+ // Re-read the session HERE rather than trust the `targetAlreadyActive`
155
+ // captured before the wallet call: it can move while we were
156
+ // awaiting the wallet (another switch lands, `accountsChanged` updates
157
+ // `activeAddress` — see connection-service.ts), and the captured
158
+ // snapshot would answer "was this a no-op when we started" instead of
159
+ // the question that actually matters here, "is it still a no-op now".
160
+ //
161
+ // Known tradeoff: `session.activeNetwork` is set only by a prior
162
+ // successful `connect`/`switchNetwork` (see `SessionManager`) — it is
163
+ // NOT synced from the live `chainChanged` telemetry event, so it can
164
+ // go stale if the wallet changes chain by some path outside UWC.
165
+ // That's an existing property of this session model (every other
166
+ // `activeNetwork` read in `connection-service.ts` trusts it the same
167
+ // way); a rejection this short-circuit swallows in that stale window
168
+ // would mask a genuine failure instead of confirming a no-op. Fixing
169
+ // that needs live chain-state sync, which is a bigger, separate
170
+ // decision than this hotfix.
171
+ const currentSession = this.sessionManager.getSession();
172
+ if (currentSession.activeNetwork?.id === networkId) {
173
+ this.emitNetworkSwitched(network, currentSession.activeAddress ?? undefined);
174
+ return;
119
175
  }
120
- });
176
+ throw error;
177
+ }
121
178
  options?.signal?.throwIfAborted();
122
179
  // Filter available addresses to only include those for configured networks
123
180
  const filteredAvailableAddresses = result.availableAddresses
@@ -131,13 +188,7 @@ export class NetworkSwitchService {
131
188
  availableAddresses: filteredAvailableAddresses
132
189
  })
133
190
  });
134
- this.eventManager.emit('networkSwitched', {
135
- network,
136
- address: result.address
137
- });
138
- this.eventManager.emit('sessionChanged', {
139
- session: this.sessionManager.getSession()
140
- });
191
+ this.emitNetworkSwitched(network, result.address);
141
192
  }
142
193
  finally {
143
194
  // Clear loading states
@@ -149,6 +200,16 @@ export class NetworkSwitchService {
149
200
  });
150
201
  }
151
202
  }
203
+ /** Shared terminal for a switch that landed — real or no-op'd — on `network`. */
204
+ emitNetworkSwitched(network, address) {
205
+ this.eventManager.emit('networkSwitched', {
206
+ network,
207
+ ...(address && { address })
208
+ });
209
+ this.eventManager.emit('sessionChanged', {
210
+ session: this.sessionManager.getSession()
211
+ });
212
+ }
152
213
  checkIfApprovalRequired(wallet, targetNetwork, currentNetwork) {
153
214
  if (!currentNetwork)
154
215
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"network-switch-service.js","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"network-switch-service.js","sourceRoot":"","sources":["../../src/services/network-switch-service.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const UWC_CORE_VERSION = "2.5.6";
1
+ export declare const UWC_CORE_VERSION = "2.5.8";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs — do not edit.
2
- export const UWC_CORE_VERSION = '2.5.6';
2
+ export const UWC_CORE_VERSION = '2.5.8';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshconnect/uwc-core",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
4
4
  "description": "Core functionality for Universal Wallet Connector",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,10 +16,10 @@
16
16
  "src"
17
17
  ],
18
18
  "dependencies": {
19
- "@meshconnect/uwc-injected-connector": "0.24.6",
19
+ "@meshconnect/uwc-injected-connector": "0.24.7",
20
20
  "@meshconnect/uwc-ton-connector": "0.9.4",
21
21
  "@meshconnect/uwc-types": "0.28.2",
22
- "@meshconnect/uwc-wallet-connect-connector": "0.14.11"
22
+ "@meshconnect/uwc-wallet-connect-connector": "0.14.12"
23
23
  },
24
24
  "devDependencies": {
25
25
  "typescript": "^7.0.2"
@@ -267,4 +267,236 @@ describe('NetworkSwitchService', () => {
267
267
  }
268
268
  ])
269
269
  })
270
+
271
+ it('PRG-714: does not fail a switch to the already-active chain when the wallet rejects the confirmatory call', async () => {
272
+ sessionManager.updateSession({
273
+ connectionMode: 'injected',
274
+ activeNetwork: networks[1], // eip155:137 — the wallet already reports this chain active
275
+ activeAddress: '0xabc',
276
+ activeWallet: {
277
+ id: 'metamask',
278
+ name: 'MetaMask',
279
+ metadata: {
280
+ icon: 'test',
281
+ downloadUrl: 'test',
282
+ supportedChains: ['eip155:1', 'eip155:137']
283
+ },
284
+ extensionInjectedProvider: {
285
+ supportedNetworkIds: ['eip155:1', 'eip155:137']
286
+ }
287
+ } as any
288
+ })
289
+ // MetaMask already fired chainChanged for eip155:137 (the add-chain sheet
290
+ // succeeded), but the confirmatory switchNetwork call races that
291
+ // confirmation and gets a stale "unrecognized chain" rejection back.
292
+ mockConnector.switchNetwork = vi
293
+ .fn()
294
+ .mockRejectedValue(
295
+ Object.assign(
296
+ new Error(
297
+ 'Unrecognized chain ID "0x89". Try adding the chain using wallet_addEthereumChain first.'
298
+ ),
299
+ { code: 4902 }
300
+ )
301
+ )
302
+ const switched: Array<{ network: unknown; address?: string }> = []
303
+ const sessionChanges: unknown[] = []
304
+ eventManager.on('networkSwitched', d => switched.push(d))
305
+ eventManager.on('sessionChanged', d => sessionChanges.push(d))
306
+
307
+ await expect(
308
+ networkSwitchService.switchNetwork('eip155:137')
309
+ ).resolves.toBeUndefined()
310
+
311
+ expect(switched).toEqual([
312
+ {
313
+ network: expect.objectContaining({ id: 'eip155:137' }),
314
+ address: '0xabc'
315
+ }
316
+ ])
317
+ // The short-circuit emits the same sessionChanged terminal as a real
318
+ // switch, carrying the (unchanged) session — callers that only listen for
319
+ // sessionChanged must still see this no-op complete.
320
+ expect(sessionChanges).toEqual([
321
+ {
322
+ session: expect.objectContaining({
323
+ activeNetwork: expect.objectContaining({ id: 'eip155:137' }),
324
+ activeAddress: '0xabc'
325
+ })
326
+ }
327
+ ])
328
+ })
329
+
330
+ it('PRG-714: rethrows the wallet rejection when the target chain is NOT already active', async () => {
331
+ sessionManager.updateSession({
332
+ connectionMode: 'injected',
333
+ activeNetwork: networks[0], // eip155:1 — a genuinely different chain from the target
334
+ activeAddress: '0xabc',
335
+ activeWallet: {
336
+ id: 'metamask',
337
+ name: 'MetaMask',
338
+ metadata: {
339
+ icon: 'test',
340
+ downloadUrl: 'test',
341
+ supportedChains: ['eip155:1', 'eip155:137']
342
+ },
343
+ extensionInjectedProvider: {
344
+ supportedNetworkIds: ['eip155:1', 'eip155:137']
345
+ }
346
+ } as any
347
+ })
348
+ const rejection = new Error('User rejected the request')
349
+ mockConnector.switchNetwork = vi.fn().mockRejectedValue(rejection)
350
+ const switched = vi.fn()
351
+ eventManager.on('networkSwitched', switched)
352
+
353
+ // targetAlreadyActive is false here — a real switch that fails must still
354
+ // fail. The short-circuit must not fire outside its one narrow case.
355
+ await expect(networkSwitchService.switchNetwork('eip155:137')).rejects.toBe(
356
+ rejection
357
+ )
358
+ expect(switched).not.toHaveBeenCalled()
359
+ })
360
+
361
+ it('PRG-714: rethrows an explicit user rejection even when the target chain is already active', async () => {
362
+ sessionManager.updateSession({
363
+ connectionMode: 'injected',
364
+ activeNetwork: networks[1], // eip155:137 — already active
365
+ activeAddress: '0xabc',
366
+ activeWallet: {
367
+ id: 'rabby',
368
+ name: 'Rabby',
369
+ metadata: {
370
+ icon: 'test',
371
+ downloadUrl: 'test',
372
+ supportedChains: ['eip155:1', 'eip155:137']
373
+ },
374
+ extensionInjectedProvider: {
375
+ supportedNetworkIds: ['eip155:1', 'eip155:137']
376
+ }
377
+ } as any
378
+ })
379
+ // Prod data (PRG-714 review): this exact targetAlreadyActive+rejection
380
+ // window has carried real EIP-1193 4001 declines, not just stale-4902
381
+ // races. An explicit "no" must still fail the switch.
382
+ const rejection = Object.assign(new Error('Not Allowed'), { code: 4001 })
383
+ mockConnector.switchNetwork = vi.fn().mockRejectedValue(rejection)
384
+ const switched = vi.fn()
385
+ eventManager.on('networkSwitched', switched)
386
+
387
+ await expect(networkSwitchService.switchNetwork('eip155:137')).rejects.toBe(
388
+ rejection
389
+ )
390
+ expect(switched).not.toHaveBeenCalled()
391
+ })
392
+
393
+ it('PRG-714: re-checks session state at catch time instead of trusting the pre-call snapshot', async () => {
394
+ sessionManager.updateSession({
395
+ connectionMode: 'injected',
396
+ activeNetwork: networks[1], // eip155:137 — active when the call STARTS
397
+ activeAddress: '0xabc',
398
+ activeWallet: {
399
+ id: 'metamask',
400
+ name: 'MetaMask',
401
+ metadata: {
402
+ icon: 'test',
403
+ downloadUrl: 'test',
404
+ supportedChains: ['eip155:1', 'eip155:137']
405
+ },
406
+ extensionInjectedProvider: {
407
+ supportedNetworkIds: ['eip155:1', 'eip155:137']
408
+ }
409
+ } as any
410
+ })
411
+ const rejection = new Error('Provider crashed mid-switch')
412
+ // Simulates a second operation (another switch, a wallet-side chain
413
+ // change) landing WHILE this call is awaiting the wallet: the session
414
+ // moves off the target before the rejection is even thrown. The stale
415
+ // `targetAlreadyActive` captured at the top of switchNetwork would say
416
+ // "still true" — the short-circuit must not trust that snapshot.
417
+ mockConnector.switchNetwork = vi.fn().mockImplementation(async () => {
418
+ sessionManager.updateSession({ activeNetwork: networks[0] }) // eip155:1
419
+ throw rejection
420
+ })
421
+ const switched = vi.fn()
422
+ eventManager.on('networkSwitched', switched)
423
+
424
+ await expect(networkSwitchService.switchNetwork('eip155:137')).rejects.toBe(
425
+ rejection
426
+ )
427
+ expect(switched).not.toHaveBeenCalled()
428
+ })
429
+
430
+ it('PRG-714: omits address from networkSwitched when the already-active session has none', async () => {
431
+ sessionManager.updateSession({
432
+ connectionMode: 'injected',
433
+ activeNetwork: networks[1], // eip155:137 — already active, but no address on file
434
+ activeAddress: null,
435
+ activeWallet: {
436
+ id: 'metamask',
437
+ name: 'MetaMask',
438
+ metadata: {
439
+ icon: 'test',
440
+ downloadUrl: 'test',
441
+ supportedChains: ['eip155:1', 'eip155:137']
442
+ },
443
+ extensionInjectedProvider: {
444
+ supportedNetworkIds: ['eip155:1', 'eip155:137']
445
+ }
446
+ } as any
447
+ })
448
+ mockConnector.switchNetwork = vi
449
+ .fn()
450
+ .mockRejectedValue(
451
+ Object.assign(new Error('Unrecognized chain ID'), { code: 4902 })
452
+ )
453
+ const switched: Array<{ network: unknown; address?: string }> = []
454
+ eventManager.on('networkSwitched', d => switched.push(d))
455
+
456
+ await expect(
457
+ networkSwitchService.switchNetwork('eip155:137')
458
+ ).resolves.toBeUndefined()
459
+
460
+ expect(switched).toEqual([
461
+ { network: expect.objectContaining({ id: 'eip155:137' }) }
462
+ ])
463
+ expect(switched[0]).not.toHaveProperty('address')
464
+ })
465
+
466
+ it('PRG-714 regression: does not swallow a caller abort into success on an already-active target', async () => {
467
+ sessionManager.updateSession({
468
+ connectionMode: 'injected',
469
+ activeNetwork: networks[1], // eip155:137 — already active, same as the no-op case above
470
+ activeAddress: '0xabc',
471
+ activeWallet: {
472
+ id: 'metamask',
473
+ name: 'MetaMask',
474
+ metadata: {
475
+ icon: 'test',
476
+ downloadUrl: 'test',
477
+ supportedChains: ['eip155:1', 'eip155:137']
478
+ },
479
+ extensionInjectedProvider: {
480
+ supportedNetworkIds: ['eip155:1', 'eip155:137']
481
+ }
482
+ } as any
483
+ })
484
+ // A caller-driven cancellation, not a wallet outcome — must propagate as
485
+ // the abort it is, not get reinterpreted as "target already active, so
486
+ // this must have succeeded".
487
+ const abortError = Object.assign(new Error('The operation was aborted.'), {
488
+ name: 'AbortError'
489
+ })
490
+ mockConnector.switchNetwork = vi.fn().mockRejectedValue(abortError)
491
+ const switched = vi.fn()
492
+ eventManager.on('networkSwitched', switched)
493
+
494
+ await expect(
495
+ networkSwitchService.switchNetwork('eip155:137', {
496
+ signal: new AbortController().signal
497
+ })
498
+ ).rejects.toBe(abortError)
499
+
500
+ expect(switched).not.toHaveBeenCalled()
501
+ })
270
502
  })
@@ -4,6 +4,7 @@ import type {
4
4
  Connector,
5
5
  ConnectionMode,
6
6
  SupportedProvider,
7
+ SwitchNetworkResult,
7
8
  WalletMetadata,
8
9
  ExtensionInjectedProvider,
9
10
  IntegratedBrowserInjectedProvider,
@@ -13,6 +14,7 @@ import type { SessionManager } from '../managers/session-manager'
13
14
  import type { EventManager } from '../managers/event-manager'
14
15
  import type { ServiceOptions } from './connection-service'
15
16
  import { instrumentHandoff } from '../observability/instrument-handoff'
17
+ import { isAbortError, isUserRejection } from '../utils/to-wallet-error'
16
18
 
17
19
  export class NetworkSwitchService {
18
20
  private isLoading = false
@@ -133,42 +135,100 @@ export class NetworkSwitchService {
133
135
  // so `awaitingWallet` marks the real wallet boundary; pre-wallet validation
134
136
  // failures throw above and never enter the funnel.
135
137
  // Pass the appropriate provider based on connection mode.
136
- const result = await instrumentHandoff(
137
- {
138
- eventManager: this.eventManager,
139
- timeoutMs: this.walletResponseTimeoutMs,
140
- signal: options?.signal
141
- },
142
- {
143
- operation: 'switchNetwork',
144
- connectionMode: session.connectionMode,
145
- walletId: currentWallet.id,
146
- namespace: network.namespace,
147
- chainId: network.id,
148
- targetAlreadyActive
149
- },
150
- async () => {
151
- if (
152
- (session.connectionMode === 'injected' ||
153
- session.connectionMode === 'tonConnect') &&
154
- provider
155
- ) {
156
- return await activeConnector.switchNetwork(
157
- network,
158
- provider as
159
- | ExtensionInjectedProvider
160
- | IntegratedBrowserInjectedProvider
161
- )
162
- } else if (session.connectionMode === 'walletConnect' && provider) {
163
- return await activeConnector.switchNetwork(
164
- network,
165
- provider as WalletConnectProvider
166
- )
167
- } else {
168
- return await activeConnector.switchNetwork(network)
138
+ let result: SwitchNetworkResult
139
+ try {
140
+ result = await instrumentHandoff(
141
+ {
142
+ eventManager: this.eventManager,
143
+ timeoutMs: this.walletResponseTimeoutMs,
144
+ signal: options?.signal
145
+ },
146
+ {
147
+ operation: 'switchNetwork',
148
+ connectionMode: session.connectionMode,
149
+ walletId: currentWallet.id,
150
+ namespace: network.namespace,
151
+ chainId: network.id,
152
+ targetAlreadyActive
153
+ },
154
+ async () => {
155
+ if (
156
+ (session.connectionMode === 'injected' ||
157
+ session.connectionMode === 'tonConnect') &&
158
+ provider
159
+ ) {
160
+ return await activeConnector.switchNetwork(
161
+ network,
162
+ provider as
163
+ | ExtensionInjectedProvider
164
+ | IntegratedBrowserInjectedProvider
165
+ )
166
+ } else if (session.connectionMode === 'walletConnect' && provider) {
167
+ return await activeConnector.switchNetwork(
168
+ network,
169
+ provider as WalletConnectProvider
170
+ )
171
+ } else {
172
+ return await activeConnector.switchNetwork(network)
173
+ }
169
174
  }
175
+ )
176
+ } catch (error) {
177
+ // A caller-driven cancellation is not a wallet outcome to second-guess —
178
+ // let it propagate untouched, same as instrumentHandoff's own check.
179
+ // Otherwise an abort mid-flight on an already-active target would be
180
+ // swallowed into a silent success instead of the cancellation the
181
+ // caller asked for.
182
+ if (isAbortError(error) || options?.signal?.aborted) throw error
183
+
184
+ // An explicit "no" is not a race to paper over: prod data (PRG-714
185
+ // review) shows this exact targetAlreadyActive+rejection window has
186
+ // carried real user declines (errorCode 4001, "Not Allowed"), not
187
+ // just stale-confirmation races. Swallowing one into success would
188
+ // have the caller believe the switch — and a following
189
+ // sendTransaction — was approved. isUserRejection is transport-
190
+ // agnostic (WalletConnectorError{type:'rejected'} or EIP-1193
191
+ // 4001/ACTION_REJECTED), so injected/tonConnect/walletConnect are all
192
+ // covered, unlike a code-4902 narrowing.
193
+ if (isUserRejection(error)) throw error
194
+
195
+ // PRG-714: some injected providers (MetaMask) race their own
196
+ // `wallet_addEthereumChain` confirmation against this confirmatory
197
+ // switch — the chain is already active per our session, but the
198
+ // provider answers with a stale 4902 "unrecognized chain" rejection.
199
+ // The session is the source of truth here, not that reply: trust it
200
+ // was already active and no-op to success instead of failing an
201
+ // otherwise-successful switch. Not narrowed to error code 4902 —
202
+ // tonConnect/walletConnect connectors don't speak EIP-1193 error
203
+ // codes, and the same race can surface there under a different shape.
204
+ //
205
+ // Re-read the session HERE rather than trust the `targetAlreadyActive`
206
+ // captured before the wallet call: it can move while we were
207
+ // awaiting the wallet (another switch lands, `accountsChanged` updates
208
+ // `activeAddress` — see connection-service.ts), and the captured
209
+ // snapshot would answer "was this a no-op when we started" instead of
210
+ // the question that actually matters here, "is it still a no-op now".
211
+ //
212
+ // Known tradeoff: `session.activeNetwork` is set only by a prior
213
+ // successful `connect`/`switchNetwork` (see `SessionManager`) — it is
214
+ // NOT synced from the live `chainChanged` telemetry event, so it can
215
+ // go stale if the wallet changes chain by some path outside UWC.
216
+ // That's an existing property of this session model (every other
217
+ // `activeNetwork` read in `connection-service.ts` trusts it the same
218
+ // way); a rejection this short-circuit swallows in that stale window
219
+ // would mask a genuine failure instead of confirming a no-op. Fixing
220
+ // that needs live chain-state sync, which is a bigger, separate
221
+ // decision than this hotfix.
222
+ const currentSession = this.sessionManager.getSession()
223
+ if (currentSession.activeNetwork?.id === networkId) {
224
+ this.emitNetworkSwitched(
225
+ network,
226
+ currentSession.activeAddress ?? undefined
227
+ )
228
+ return
170
229
  }
171
- )
230
+ throw error
231
+ }
172
232
 
173
233
  options?.signal?.throwIfAborted()
174
234
 
@@ -188,13 +248,7 @@ export class NetworkSwitchService {
188
248
  })
189
249
  })
190
250
 
191
- this.eventManager.emit('networkSwitched', {
192
- network,
193
- address: result.address
194
- })
195
- this.eventManager.emit('sessionChanged', {
196
- session: this.sessionManager.getSession()
197
- })
251
+ this.emitNetworkSwitched(network, result.address)
198
252
  } finally {
199
253
  // Clear loading states
200
254
  this.isLoading = false
@@ -206,6 +260,20 @@ export class NetworkSwitchService {
206
260
  }
207
261
  }
208
262
 
263
+ /** Shared terminal for a switch that landed — real or no-op'd — on `network`. */
264
+ private emitNetworkSwitched(
265
+ network: Network,
266
+ address: string | undefined
267
+ ): void {
268
+ this.eventManager.emit('networkSwitched', {
269
+ network,
270
+ ...(address && { address })
271
+ })
272
+ this.eventManager.emit('sessionChanged', {
273
+ session: this.sessionManager.getSession()
274
+ })
275
+ }
276
+
209
277
  private checkIfApprovalRequired(
210
278
  wallet: WalletMetadata,
211
279
  targetNetwork: Network,
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs — do not edit.
2
- export const UWC_CORE_VERSION = '2.5.6'
2
+ export const UWC_CORE_VERSION = '2.5.8'