@meshconnect/uwc-core 2.5.16 → 2.7.0-snapshot.c392d3d

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.
Files changed (55) hide show
  1. package/README.md +11 -2
  2. package/dist/events.d.ts +20 -0
  3. package/dist/events.d.ts.map +1 -1
  4. package/dist/events.js.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/observability/instrument-handoff.d.ts +5 -0
  10. package/dist/observability/instrument-handoff.d.ts.map +1 -1
  11. package/dist/observability/instrument-handoff.js +12 -0
  12. package/dist/observability/instrument-handoff.js.map +1 -1
  13. package/dist/observability/telemetry.d.ts +4 -0
  14. package/dist/observability/telemetry.d.ts.map +1 -1
  15. package/dist/observability/telemetry.js +31 -10
  16. package/dist/observability/telemetry.js.map +1 -1
  17. package/dist/services/connection-service.d.ts +11 -2
  18. package/dist/services/connection-service.d.ts.map +1 -1
  19. package/dist/services/connection-service.js +39 -12
  20. package/dist/services/connection-service.js.map +1 -1
  21. package/dist/services/index.d.ts +1 -0
  22. package/dist/services/index.d.ts.map +1 -1
  23. package/dist/services/index.js +1 -0
  24. package/dist/services/index.js.map +1 -1
  25. package/dist/services/network-selection.d.ts +34 -0
  26. package/dist/services/network-selection.d.ts.map +1 -0
  27. package/dist/services/network-selection.js +54 -0
  28. package/dist/services/network-selection.js.map +1 -0
  29. package/dist/universal-wallet-connector.d.ts +15 -1
  30. package/dist/universal-wallet-connector.d.ts.map +1 -1
  31. package/dist/universal-wallet-connector.js +5 -3
  32. package/dist/universal-wallet-connector.js.map +1 -1
  33. package/dist/utils/to-wallet-error.d.ts.map +1 -1
  34. package/dist/utils/to-wallet-error.js +13 -2
  35. package/dist/utils/to-wallet-error.js.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.d.ts.map +1 -1
  38. package/dist/version.js +1 -1
  39. package/dist/version.js.map +1 -1
  40. package/package.json +5 -5
  41. package/src/events.ts +20 -0
  42. package/src/index.ts +4 -0
  43. package/src/observability/instrument-handoff.ts +17 -0
  44. package/src/observability/telemetry.test.ts +83 -0
  45. package/src/observability/telemetry.ts +36 -10
  46. package/src/services/connection-service.test.ts +159 -7
  47. package/src/services/connection-service.ts +57 -14
  48. package/src/services/index.ts +1 -0
  49. package/src/services/network-selection.test.ts +152 -0
  50. package/src/services/network-selection.ts +86 -0
  51. package/src/services/transaction-service.test.ts +25 -0
  52. package/src/universal-wallet-connector.ts +20 -3
  53. package/src/utils/to-wallet-error.test.ts +29 -1
  54. package/src/utils/to-wallet-error.ts +12 -1
  55. package/src/version.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,WAAW,CAAA"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,UAAU,CAAA"}
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.16';
2
+ export const UWC_CORE_VERSION = '2.7.0';
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAA"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshconnect/uwc-core",
3
- "version": "2.5.16",
3
+ "version": "2.7.0-snapshot.c392d3d",
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.26.3",
20
- "@meshconnect/uwc-ton-connector": "0.9.9",
21
- "@meshconnect/uwc-types": "0.30.1",
22
- "@meshconnect/uwc-wallet-connect-connector": "0.14.19"
19
+ "@meshconnect/uwc-injected-connector": "0.27.1-snapshot.c392d3d",
20
+ "@meshconnect/uwc-ton-connector": "0.9.11-snapshot.c392d3d",
21
+ "@meshconnect/uwc-types": "0.32.0-snapshot.c392d3d",
22
+ "@meshconnect/uwc-wallet-connect-connector": "0.14.21-snapshot.c392d3d"
23
23
  },
24
24
  "devDependencies": {
25
25
  "typescript": "^7.0.2"
package/src/events.ts CHANGED
@@ -251,6 +251,10 @@ export interface UWCEventMap {
251
251
  connectAttemptId?: string | undefined
252
252
  /** switchNetwork only: target chain already the active one (no-op switch). */
253
253
  targetAlreadyActive?: boolean | undefined
254
+ /** connect only: a `silentOnly` attempt (no wallet UI allowed). */
255
+ silentOnly?: boolean | undefined
256
+ /** connect only: how the connect network was chosen. */
257
+ networkSelection?: 'explicit' | 'detected' | 'catalogOrder' | undefined
254
258
  }
255
259
 
256
260
  /** The wallet resolved the op successfully. Closes a handoff. */
@@ -270,6 +274,10 @@ export interface UWCEventMap {
270
274
  connectAttemptId?: string | undefined
271
275
  /** switchNetwork only: target chain already the active one (no-op switch). */
272
276
  targetAlreadyActive?: boolean | undefined
277
+ /** connect only: a `silentOnly` attempt (no wallet UI allowed). */
278
+ silentOnly?: boolean | undefined
279
+ /** connect only: how the connect network was chosen. */
280
+ networkSelection?: 'explicit' | 'detected' | 'catalogOrder' | undefined
273
281
  /** Backgrounding during this handoff: cumulative hidden time (ms) + transition count. */
274
282
  backgroundedMs?: number | undefined
275
283
  backgroundCount?: number | undefined
@@ -303,6 +311,10 @@ export interface UWCEventMap {
303
311
  /** Per-connect identifier shared by all events in this connect flow. */
304
312
  connectAttemptId?: string | undefined
305
313
  targetAlreadyActive?: boolean | undefined
314
+ /** connect only: a `silentOnly` attempt (no wallet UI allowed). */
315
+ silentOnly?: boolean | undefined
316
+ /** connect only: how the connect network was chosen. */
317
+ networkSelection?: 'explicit' | 'detected' | 'catalogOrder' | undefined
306
318
  /** Stringified error code (EIP-1193 numeric / string), when present. */
307
319
  errorCode?: string | undefined
308
320
  failureKind?: FailureKind | undefined
@@ -328,6 +340,10 @@ export interface UWCEventMap {
328
340
  /** Per-connect identifier shared by all events in this connect flow. */
329
341
  connectAttemptId?: string | undefined
330
342
  targetAlreadyActive?: boolean | undefined
343
+ /** connect only: a `silentOnly` attempt (no wallet UI allowed). */
344
+ silentOnly?: boolean | undefined
345
+ /** connect only: how the connect network was chosen. */
346
+ networkSelection?: 'explicit' | 'detected' | 'catalogOrder' | undefined
331
347
  /** Stringified error code (EIP-1193 numeric / string), when present. */
332
348
  errorCode?: string | undefined
333
349
  failureKind?: FailureKind | undefined
@@ -358,6 +374,10 @@ export interface UWCEventMap {
358
374
  connectAttemptId?: string | undefined
359
375
  /** switchNetwork only: target chain already the active one (no-op switch). */
360
376
  targetAlreadyActive?: boolean | undefined
377
+ /** connect only: a `silentOnly` attempt (no wallet UI allowed). */
378
+ silentOnly?: boolean | undefined
379
+ /** connect only: how the connect network was chosen. */
380
+ networkSelection?: 'explicit' | 'detected' | 'catalogOrder' | undefined
361
381
  /** Backgrounding so far (marker fires mid-flight): hidden time (ms) + transition count. */
362
382
  backgroundedMs?: number | undefined
363
383
  backgroundCount?: number | undefined
package/src/index.ts CHANGED
@@ -21,3 +21,7 @@ export {
21
21
  type UWCTelemetryEvent
22
22
  } from './observability/telemetry'
23
23
  export { toBaseUnits } from './utils/to-base-units'
24
+ export {
25
+ DEFAULT_NAMESPACE_PRIORITY,
26
+ type NetworkSelection
27
+ } from './services/network-selection'
@@ -1,3 +1,4 @@
1
+ import type { NetworkSelection } from '../services/network-selection'
1
2
  import {
2
3
  BatchConfirmedWithoutReceiptError,
3
4
  type ConnectionMode,
@@ -29,6 +30,10 @@ export interface HandoffContext {
29
30
  connectAttemptId?: string | undefined
30
31
  /** switchNetwork only: target chain already the active one (no-op switch). */
31
32
  targetAlreadyActive?: boolean | undefined
33
+ /** connect only: the attempt was `silentOnly` (see `ConnectOptions.silentOnly`). */
34
+ silentOnly?: boolean | undefined
35
+ /** connect only: how the connect network was chosen (see `NetworkSelection`). */
36
+ networkSelection?: NetworkSelection | undefined
32
37
  }
33
38
 
34
39
  /** Build handoff routing facets from the active session (no address). */
@@ -151,6 +156,12 @@ export async function instrumentHandoff<T>(
151
156
  }),
152
157
  ...(context.targetAlreadyActive !== undefined && {
153
158
  targetAlreadyActive: context.targetAlreadyActive
159
+ }),
160
+ ...(context.silentOnly !== undefined && {
161
+ silentOnly: context.silentOnly
162
+ }),
163
+ ...(context.networkSelection !== undefined && {
164
+ networkSelection: context.networkSelection
154
165
  })
155
166
  })
156
167
 
@@ -196,6 +207,12 @@ export async function instrumentHandoff<T>(
196
207
  }),
197
208
  ...(context.targetAlreadyActive !== undefined && {
198
209
  targetAlreadyActive: context.targetAlreadyActive
210
+ }),
211
+ ...(context.silentOnly !== undefined && {
212
+ silentOnly: context.silentOnly
213
+ }),
214
+ ...(context.networkSelection !== undefined && {
215
+ networkSelection: context.networkSelection
199
216
  })
200
217
  }
201
218
 
@@ -867,6 +867,89 @@ describe('normalizeEvent', () => {
867
867
  expect(out.connectAttemptId).toBe('attempt-1')
868
868
  })
869
869
 
870
+ it('forwards a scrubbed providerReason on walletFailed', () => {
871
+ const out = normalizeEvent(
872
+ 'walletFailed',
873
+ {
874
+ handoffId: 'h-1',
875
+ operation: 'sendTransaction',
876
+ durationMs: 1,
877
+ error: {
878
+ type: 'unknown',
879
+ message: 'An unknown RPC error occurred.',
880
+ providerReason:
881
+ 'cancelled for 0x1234567890123456789012345678901234567890'
882
+ },
883
+ connectionMode: 'injected',
884
+ timedOut: false
885
+ },
886
+ SDK_SESSION,
887
+ NOW
888
+ )
889
+ expect(out.error?.providerReason).toContain('cancelled for ')
890
+ expect(out.error?.providerReason).not.toContain(
891
+ '1234567890123456789012345678901234567890'
892
+ )
893
+ })
894
+
895
+ it('redacts an address in a long providerReason before capping it', () => {
896
+ const address = '0x1234567890123456789012345678901234567890'
897
+ const out = normalizeEvent(
898
+ 'walletFailed',
899
+ {
900
+ handoffId: 'h-1',
901
+ operation: 'sendTransaction',
902
+ durationMs: 1,
903
+ error: {
904
+ type: 'unknown',
905
+ message: 'An unknown RPC error occurred.',
906
+ // The address straddles the 500-character cap.
907
+ providerReason: `${'x'.repeat(480)} ${address}`
908
+ },
909
+ connectionMode: 'injected',
910
+ timedOut: false
911
+ },
912
+ SDK_SESSION,
913
+ NOW
914
+ )
915
+ expect(out.error?.providerReason).not.toContain('0x1234567890')
916
+ expect(out.error?.providerReason).toContain('0x[redacted-hex]')
917
+ })
918
+
919
+ it('omits providerReason from the record when the error has none', () => {
920
+ const out = normalizeEvent(
921
+ 'walletFailed',
922
+ {
923
+ handoffId: 'h-1',
924
+ operation: 'sendTransaction',
925
+ durationMs: 1,
926
+ error: { type: 'unknown', message: 'failed' },
927
+ connectionMode: 'injected',
928
+ timedOut: false
929
+ },
930
+ SDK_SESSION,
931
+ NOW
932
+ )
933
+ expect(out.error).toEqual({ type: 'unknown', message: 'failed' })
934
+ })
935
+
936
+ it('forwards providerReason on the error event too', () => {
937
+ const out = normalizeEvent(
938
+ 'error',
939
+ {
940
+ error: {
941
+ type: 'unknown',
942
+ message: 'failed',
943
+ providerReason: 'cancelled'
944
+ },
945
+ operation: 'sendTransaction'
946
+ },
947
+ SDK_SESSION,
948
+ NOW
949
+ )
950
+ expect(out.error?.providerReason).toBe('cancelled')
951
+ })
952
+
870
953
  it('does not expose a stray connectAttemptId on data-bearing non-connect events', () => {
871
954
  const out = normalizeEvent(
872
955
  'error',
@@ -52,6 +52,20 @@ function capNetworkMismatch(
52
52
  }
53
53
  }
54
54
 
55
+ /**
56
+ * Egress shape for a wallet error. `providerReason` is wallet-controlled text, so
57
+ * it gets the same scrubber as `message` and is omitted when absent.
58
+ */
59
+ function toTelemetryError(error: WalletError): WalletError {
60
+ return {
61
+ type: error.type,
62
+ message: scrubMessage(error.message),
63
+ ...(error.providerReason
64
+ ? { providerReason: scrubMessage(error.providerReason) }
65
+ : {})
66
+ }
67
+ }
68
+
55
69
  /**
56
70
  * A normalized, PII-safe telemetry record derived from a `UWCEventMap` event.
57
71
  *
@@ -107,6 +121,10 @@ export interface UWCTelemetryEvent {
107
121
  timeoutMs?: number | undefined
108
122
  /** switchNetwork only: target chain was already the active one (no-op switch). */
109
123
  targetAlreadyActive?: boolean | undefined
124
+ /** connect only: a `silentOnly` attempt — tells a silent restore from a tapped connect. */
125
+ silentOnly?: boolean | undefined
126
+ /** connect only: how the connect network was chosen — explicit, detected, or catalog order. */
127
+ networkSelection?: 'explicit' | 'detected' | 'catalogOrder' | undefined
110
128
  /** Non-success outcomes: stringified error code when reliably known. */
111
129
  errorCode?: string | undefined
112
130
  /** Non-success outcomes: coarse cause bucket. `unknown` when not classifiable. */
@@ -432,7 +450,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
432
450
  return {
433
451
  ...base,
434
452
  operation: d.operation,
435
- error: { type: d.error.type, message: scrubMessage(d.error.message) },
453
+ error: toTelemetryError(d.error),
436
454
  // Telemetry-only classification — undefined for ordinary errors, set for
437
455
  // pre-wallet validation failures (e.g. no_common_network) that never
438
456
  // reach the handoff funnel.
@@ -456,7 +474,9 @@ function buildTelemetryEvent<K extends UWCEventName>(
456
474
  connectAttemptId: getConnectAttemptId(d.operation, d.connectAttemptId),
457
475
  walletFlowType: d.walletFlowType,
458
476
  platform: d.platform,
459
- targetAlreadyActive: d.targetAlreadyActive
477
+ targetAlreadyActive: d.targetAlreadyActive,
478
+ silentOnly: d.silentOnly,
479
+ networkSelection: d.networkSelection
460
480
  }
461
481
  }
462
482
  case 'walletSucceeded': {
@@ -474,6 +494,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
474
494
  timedOut: d.timedOut,
475
495
  timeoutMs: d.timeoutMs,
476
496
  targetAlreadyActive: d.targetAlreadyActive,
497
+ silentOnly: d.silentOnly,
498
+ networkSelection: d.networkSelection,
477
499
  backgroundedMs: d.backgroundedMs,
478
500
  backgroundCount: d.backgroundCount,
479
501
  relayDropCount: d.relayDropCount,
@@ -495,12 +517,12 @@ function buildTelemetryEvent<K extends UWCEventName>(
495
517
  timedOut: d.timedOut,
496
518
  timeoutMs: d.timeoutMs,
497
519
  targetAlreadyActive: d.targetAlreadyActive,
520
+ silentOnly: d.silentOnly,
521
+ networkSelection: d.networkSelection,
498
522
  errorCode: d.errorCode,
499
523
  failureKind: d.failureKind,
500
524
  source: d.source,
501
- error: d.error
502
- ? { type: d.error.type, message: scrubMessage(d.error.message) }
503
- : undefined,
525
+ error: d.error ? toTelemetryError(d.error) : undefined,
504
526
  backgroundedMs: d.backgroundedMs,
505
527
  backgroundCount: d.backgroundCount,
506
528
  relayDropCount: d.relayDropCount
@@ -520,6 +542,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
520
542
  durationMs: d.durationMs,
521
543
  timeoutMs: d.timeoutMs,
522
544
  targetAlreadyActive: d.targetAlreadyActive,
545
+ silentOnly: d.silentOnly,
546
+ networkSelection: d.networkSelection,
523
547
  backgroundedMs: d.backgroundedMs,
524
548
  backgroundCount: d.backgroundCount,
525
549
  relayDropCount: d.relayDropCount
@@ -555,10 +579,12 @@ function buildTelemetryEvent<K extends UWCEventName>(
555
579
  timedOut: d.timedOut,
556
580
  timeoutMs: d.timeoutMs,
557
581
  targetAlreadyActive: d.targetAlreadyActive,
582
+ silentOnly: d.silentOnly,
583
+ networkSelection: d.networkSelection,
558
584
  errorCode: d.errorCode,
559
585
  failureKind: d.failureKind,
560
586
  source: d.source,
561
- error: { type: d.error.type, message: scrubMessage(d.error.message) },
587
+ error: toTelemetryError(d.error),
562
588
  backgroundedMs: d.backgroundedMs,
563
589
  backgroundCount: d.backgroundCount,
564
590
  relayDropCount: d.relayDropCount
@@ -602,7 +628,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
602
628
  ...base,
603
629
  connectionMode: d.connectionMode,
604
630
  namespace: d.namespace,
605
- error: { type: d.error.type, message: scrubMessage(d.error.message) }
631
+ error: toTelemetryError(d.error)
606
632
  }
607
633
  }
608
634
  case 'secondarySolanaConnectSkipped': {
@@ -644,7 +670,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
644
670
  namespace: d.namespace,
645
671
  source: 'bridge',
646
672
  failureKind: 'bridge_error',
647
- error: { type: d.error.type, message: scrubMessage(d.error.message) }
673
+ error: toTelemetryError(d.error)
648
674
  }
649
675
  }
650
676
  case 'relayError': {
@@ -656,7 +682,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
656
682
  connectionMode: 'walletConnect',
657
683
  source: 'provider',
658
684
  failureKind: 'network_error',
659
- error: { type: d.error.type, message: scrubMessage(d.error.message) }
685
+ error: toTelemetryError(d.error)
660
686
  }
661
687
  }
662
688
  case 'relayErrorCapped':
@@ -677,7 +703,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
677
703
  connectionMode: 'walletConnect',
678
704
  source: 'provider',
679
705
  failureKind: 'client_init_failed',
680
- error: { type: d.error.type, message: scrubMessage(d.error.message) }
706
+ error: toTelemetryError(d.error)
681
707
  }
682
708
  }
683
709
  case 'walletAccountChanged':
@@ -12,7 +12,10 @@ import type {
12
12
  ConnectionMode,
13
13
  Connector
14
14
  } from '@meshconnect/uwc-types'
15
- import { NoCommonNetworkError } from '@meshconnect/uwc-types'
15
+ import {
16
+ NoCommonNetworkError,
17
+ WalletNotAuthorizedError
18
+ } from '@meshconnect/uwc-types'
16
19
  import type { WalletLifecycleEvent } from '@meshconnect/uwc-types'
17
20
 
18
21
  describe('ConnectionService', () => {
@@ -111,11 +114,11 @@ describe('ConnectionService', () => {
111
114
  const mockProvider = {
112
115
  supportedNetworkIds: ['eip155:1']
113
116
  }
114
- const network = connectionService['validateAndDetermineNetwork'](
115
- 'eip155:1',
116
- mockProvider as any
117
- )
117
+ const { network, selection } = connectionService[
118
+ 'validateAndDetermineNetwork'
119
+ ]('eip155:1', mockProvider as any, 'injected')
118
120
  expect(network).toEqual(networks[0])
121
+ expect(selection).toBe('explicit')
119
122
  })
120
123
 
121
124
  it('should throw error for unknown network', () => {
@@ -125,7 +128,8 @@ describe('ConnectionService', () => {
125
128
  expect(() => {
126
129
  connectionService['validateAndDetermineNetwork'](
127
130
  'eip155:8453',
128
- mockProvider as any
131
+ mockProvider as any,
132
+ 'injected'
129
133
  )
130
134
  }).toThrow('Network eip155:8453 is not in the configured networks')
131
135
  })
@@ -140,7 +144,8 @@ describe('ConnectionService', () => {
140
144
  try {
141
145
  connectionService['validateAndDetermineNetwork'](
142
146
  undefined,
143
- mockProvider as any
147
+ mockProvider as any,
148
+ 'injected'
144
149
  )
145
150
  } catch (e) {
146
151
  thrown = e
@@ -211,6 +216,115 @@ describe('ConnectionService', () => {
211
216
  expect(connectionService['activeConnector']).toBe(mockConnector)
212
217
  })
213
218
 
219
+ // ONC-1975: silentOnly is a promise never to open wallet UI.
220
+ it('forwards silentOnly to the injected connector', async () => {
221
+ const provider = { supportedNetworkIds: ['eip155:1'] }
222
+ wallets[0].extensionInjectedProvider = provider as any
223
+ mockConnector.connect = vi.fn().mockResolvedValue({
224
+ networkId: 'eip155:1',
225
+ address: '0x1234',
226
+ availableAddresses: [{ address: '0x1234', networkId: 'eip155:1' }]
227
+ })
228
+
229
+ await connectionService.connect('injected', 'metamask', 'eip155:1', {
230
+ silentOnly: true
231
+ })
232
+
233
+ expect(mockConnector.connect).toHaveBeenCalledWith(
234
+ networks[0],
235
+ provider,
236
+ {
237
+ silentOnly: true
238
+ }
239
+ )
240
+ })
241
+
242
+ it('tags the connect funnel with silentOnly, so a silent restore is not counted as a tap', async () => {
243
+ const provider = { supportedNetworkIds: ['eip155:1'] }
244
+ wallets[0].extensionInjectedProvider = provider as any
245
+ mockConnector.connect = vi.fn().mockResolvedValue({
246
+ networkId: 'eip155:1',
247
+ address: '0x1234',
248
+ availableAddresses: [{ address: '0x1234', networkId: 'eip155:1' }]
249
+ })
250
+ const awaiting = vi.fn()
251
+ const succeeded = vi.fn()
252
+ eventManager.on('awaitingWallet', awaiting)
253
+ eventManager.on('walletSucceeded', succeeded)
254
+
255
+ await connectionService.connect('injected', 'metamask', 'eip155:1', {
256
+ silentOnly: true
257
+ })
258
+ await connectionService.connect('injected', 'metamask', 'eip155:1')
259
+
260
+ expect(awaiting.mock.calls.map(([e]) => e.silentOnly)).toEqual([
261
+ true,
262
+ false
263
+ ])
264
+ expect(succeeded.mock.calls.map(([e]) => e.silentOnly)).toEqual([
265
+ true,
266
+ false
267
+ ])
268
+ })
269
+
270
+ it('passes no connect options when neither silentOnly nor requiredAddresses is set', async () => {
271
+ const provider = { supportedNetworkIds: ['eip155:1'] }
272
+ wallets[0].extensionInjectedProvider = provider as any
273
+ mockConnector.connect = vi.fn().mockResolvedValue({
274
+ networkId: 'eip155:1',
275
+ address: '0x1234',
276
+ availableAddresses: [{ address: '0x1234', networkId: 'eip155:1' }]
277
+ })
278
+
279
+ await connectionService.connect('injected', 'metamask', 'eip155:1', {
280
+ silentOnly: false
281
+ })
282
+
283
+ expect(mockConnector.connect).toHaveBeenCalledWith(
284
+ networks[0],
285
+ provider,
286
+ undefined
287
+ )
288
+ })
289
+
290
+ it('rejects silentOnly for a non-injected mode before the attempt starts', async () => {
291
+ const walletConnectConnector: Connector = {
292
+ connect: vi.fn(),
293
+ disconnect: vi.fn(),
294
+ signMessage: vi.fn(),
295
+ sendTransaction: vi.fn(),
296
+ switchNetwork: vi.fn()
297
+ }
298
+ connectors.set('walletConnect', walletConnectConnector)
299
+ wallets[0].walletConnectProvider = {
300
+ supportedNetworkIds: ['eip155:1']
301
+ } as any
302
+ const connectingListener = vi.fn()
303
+ const awaiting = vi.fn()
304
+ eventManager.on('connecting', connectingListener)
305
+ eventManager.on('awaitingWallet', awaiting)
306
+
307
+ await expect(
308
+ connectionService.connect('walletConnect', 'metamask', 'eip155:1', {
309
+ silentOnly: true
310
+ })
311
+ ).rejects.toThrow(
312
+ "silentOnly is not supported for connection mode 'walletConnect'"
313
+ )
314
+ // A caller bug, not the expected `not_authorized` miss.
315
+ await expect(
316
+ connectionService.connect('walletConnect', 'metamask', 'eip155:1', {
317
+ silentOnly: true
318
+ })
319
+ ).rejects.not.toBeInstanceOf(WalletNotAuthorizedError)
320
+
321
+ // A validation failure: never reaches the wallet, never enters the funnel.
322
+ expect(walletConnectConnector.connect).not.toHaveBeenCalled()
323
+ expect(connectingListener).not.toHaveBeenCalled()
324
+ expect(awaiting).not.toHaveBeenCalled()
325
+ expect(connectionService['activeConnector']).toBeNull()
326
+ })
327
+
214
328
  it('does not emit "connecting" when validation throws (unknown wallet)', async () => {
215
329
  const connectingListener = vi.fn()
216
330
  eventManager.on('connecting', connectingListener)
@@ -264,6 +378,44 @@ describe('ConnectionService', () => {
264
378
  expect(awaiting.namespace).toBe('eip155')
265
379
  expect(awaiting.chainId).toBe('eip155:1')
266
380
  expect(awaiting.handoffId).toBe(events[1].data.handoffId)
381
+ expect(awaiting.networkSelection).toBe('explicit')
382
+ })
383
+
384
+ it('auto-picks a detected namespace over an undetected first network (ONC-2110)', async () => {
385
+ networks.push({
386
+ ...networks[0],
387
+ id: 'tron:0x2b6653dc',
388
+ internalId: 2,
389
+ name: 'Tron',
390
+ namespace: 'tron',
391
+ networkType: 'tron'
392
+ } as Network)
393
+ // Trust mobile: catalog lists Tron first, but only EVM was detected.
394
+ wallets[0].extensionInjectedProvider = {
395
+ supportedNetworkIds: ['tron:0x2b6653dc', 'eip155:1'],
396
+ namespaceMetaData: {
397
+ tron: { injectedId: 'trustWallet.tronLink', installed: false },
398
+ eip155: { eip155Name: 'Trust Wallet', installed: true }
399
+ }
400
+ } as any
401
+ const awaiting = vi.fn()
402
+ eventManager.on('awaitingWallet', awaiting)
403
+ mockConnector.connect = vi.fn().mockResolvedValue({
404
+ networkId: 'eip155:1',
405
+ address: '0x1234',
406
+ availableAddresses: [{ address: '0x1234', networkId: 'eip155:1' }]
407
+ })
408
+
409
+ await connectionService.connect('injected', 'metamask')
410
+
411
+ expect(awaiting).toHaveBeenCalledWith(
412
+ expect.objectContaining({
413
+ chainId: 'eip155:1',
414
+ namespace: 'eip155',
415
+ networkSelection: 'detected'
416
+ })
417
+ )
418
+ expect(sessionManager.getSession().activeNetwork?.id).toBe('eip155:1')
267
419
  })
268
420
 
269
421
  it('emits one stable attempt ID per connect and a different ID for the next connect', async () => {