@meshconnect/uwc-core 0.9.0 → 0.9.1-snapshot.95b8dbe

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 (50) hide show
  1. package/README.md +924 -0
  2. package/dist/events.d.ts +71 -0
  3. package/dist/events.d.ts.map +1 -0
  4. package/dist/events.js +2 -0
  5. package/dist/events.js.map +1 -0
  6. package/dist/index.d.ts +2 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/managers/event-manager.d.ts +22 -3
  11. package/dist/managers/event-manager.d.ts.map +1 -1
  12. package/dist/managers/event-manager.js +63 -7
  13. package/dist/managers/event-manager.js.map +1 -1
  14. package/dist/services/connection-service.d.ts +5 -2
  15. package/dist/services/connection-service.d.ts.map +1 -1
  16. package/dist/services/connection-service.js +19 -7
  17. package/dist/services/connection-service.js.map +1 -1
  18. package/dist/services/network-switch-service.d.ts +2 -1
  19. package/dist/services/network-switch-service.d.ts.map +1 -1
  20. package/dist/services/network-switch-service.js +15 -3
  21. package/dist/services/network-switch-service.js.map +1 -1
  22. package/dist/services/signature-service.d.ts +3 -1
  23. package/dist/services/signature-service.d.ts.map +1 -1
  24. package/dist/services/signature-service.js +10 -5
  25. package/dist/services/signature-service.js.map +1 -1
  26. package/dist/services/transaction-service.d.ts +3 -1
  27. package/dist/services/transaction-service.d.ts.map +1 -1
  28. package/dist/services/transaction-service.js +10 -5
  29. package/dist/services/transaction-service.js.map +1 -1
  30. package/dist/services/wallet-capabilities-service.d.ts +2 -1
  31. package/dist/services/wallet-capabilities-service.d.ts.map +1 -1
  32. package/dist/services/wallet-capabilities-service.js +9 -2
  33. package/dist/services/wallet-capabilities-service.js.map +1 -1
  34. package/dist/universal-wallet-connector.d.ts +52 -6
  35. package/dist/universal-wallet-connector.d.ts.map +1 -1
  36. package/dist/universal-wallet-connector.js +271 -177
  37. package/dist/universal-wallet-connector.js.map +1 -1
  38. package/package.json +5 -5
  39. package/src/events.ts +73 -0
  40. package/src/index.ts +11 -3
  41. package/src/managers/event-manager.test.ts +70 -0
  42. package/src/managers/event-manager.ts +80 -9
  43. package/src/services/connection-service.test.ts +13 -19
  44. package/src/services/connection-service.ts +34 -7
  45. package/src/services/network-switch-service.ts +22 -3
  46. package/src/services/signature-service.ts +13 -5
  47. package/src/services/transaction-service.ts +13 -5
  48. package/src/services/wallet-capabilities-service.ts +14 -2
  49. package/src/universal-wallet-connector.test.ts +87 -3
  50. package/src/universal-wallet-connector.ts +395 -204
@@ -17,8 +17,10 @@ import type {
17
17
  NetworkRpcMap,
18
18
  EVMCapabilities,
19
19
  SignatureType,
20
- EIP712TypedData
20
+ EIP712TypedData,
21
+ WalletError
21
22
  } from '@meshconnect/uwc-types'
23
+ import { WalletConnectorError } from '@meshconnect/uwc-types'
22
24
  import { InjectedConnector } from '@meshconnect/uwc-injected-connector'
23
25
  import { WalletConnectConnector } from '@meshconnect/uwc-wallet-connect-connector'
24
26
  import { TonConnectConnector } from '@meshconnect/uwc-ton-connector'
@@ -30,8 +32,28 @@ import { SignatureService } from './services/signature-service'
30
32
  import { TransactionService } from './services/transaction-service'
31
33
  import { WalletCapabilitiesService } from './services/wallet-capabilities-service'
32
34
  import { createNetworkRpcMap } from './utils/network-rpc-utils'
35
+ import type { UWCEventName, UWCEventListener, UWCOperation } from './events'
36
+
37
+ /**
38
+ * Configuration object form of the UniversalWalletConnector constructor.
39
+ * Prefer this over the positional constructor in new code.
40
+ */
41
+ export interface UWCConfig {
42
+ networks: Network[]
43
+ wallets?: WalletMetadata[]
44
+ usingIntegratedBrowser?: boolean
45
+ walletConnectConfig?: WalletConnectConfig
46
+ tonConnectConfig?: TonConnectConfig
47
+ }
48
+
49
+ /** Common options for user-initiated async operations. */
50
+ export interface OperationOptions {
51
+ /** Abort the operation if the signal fires. State mutations are skipped after abort. */
52
+ signal?: AbortSignal
53
+ }
33
54
 
34
55
  export class UniversalWalletConnector {
56
+ private static instance: UniversalWalletConnector | null = null
35
57
  private static instanceCount = 0
36
58
  private static hasWarnedAboutMultipleInstances = false
37
59
 
@@ -49,17 +71,78 @@ export class UniversalWalletConnector {
49
71
  private detectionComplete = false
50
72
  private networkRpcMap: NetworkRpcMap
51
73
 
74
+ /**
75
+ * Recommended entry point: returns the shared instance, creating it on the
76
+ * first call. Subsequent calls ignore the `config` argument — use
77
+ * `resetInstance()` first if you need to reinitialise (e.g. in tests).
78
+ */
79
+ static getInstance(config?: UWCConfig): UniversalWalletConnector {
80
+ if (UniversalWalletConnector.instance) {
81
+ return UniversalWalletConnector.instance
82
+ }
83
+ if (!config) {
84
+ throw new Error(
85
+ 'UniversalWalletConnector.getInstance() was called before an instance existed. Pass a config on the first call.'
86
+ )
87
+ }
88
+ return new UniversalWalletConnector(config)
89
+ }
90
+
91
+ /** Clear the shared instance reference. Primarily for tests and full-logout flows. */
92
+ static resetInstance(): void {
93
+ UniversalWalletConnector.instance = null
94
+ UniversalWalletConnector.instanceCount = 0
95
+ UniversalWalletConnector.hasWarnedAboutMultipleInstances = false
96
+ }
97
+
98
+ constructor(config: UWCConfig)
52
99
  constructor(
53
100
  networks: Network[],
101
+ wallets?: WalletMetadata[],
102
+ usingIntegratedBrowser?: boolean,
103
+ walletConnectConfig?: WalletConnectConfig,
104
+ tonConnectConfig?: TonConnectConfig
105
+ )
106
+ constructor(
107
+ networksOrConfig: Network[] | UWCConfig,
54
108
  wallets: WalletMetadata[] = [],
55
109
  usingIntegratedBrowser = false,
56
110
  walletConnectConfig?: WalletConnectConfig,
57
111
  tonConnectConfig?: TonConnectConfig
58
112
  ) {
59
- // Track instance creation
60
- UniversalWalletConnector.instanceCount++
113
+ if (!networksOrConfig) {
114
+ throw new Error(
115
+ 'Universal Wallet Connector must be initialized with at least one network'
116
+ )
117
+ }
118
+
119
+ let config: UWCConfig
120
+ if (Array.isArray(networksOrConfig)) {
121
+ config = {
122
+ networks: networksOrConfig,
123
+ wallets,
124
+ usingIntegratedBrowser
125
+ }
126
+ if (walletConnectConfig) {
127
+ config.walletConnectConfig = walletConnectConfig
128
+ }
129
+ if (tonConnectConfig) {
130
+ config.tonConnectConfig = tonConnectConfig
131
+ }
132
+ } else {
133
+ config = networksOrConfig
134
+ }
135
+
136
+ const {
137
+ networks,
138
+ wallets: configuredWallets = [],
139
+ usingIntegratedBrowser: configuredUsingIntegratedBrowser = false,
140
+ walletConnectConfig: configuredWalletConnectConfig,
141
+ tonConnectConfig: configuredTonConnectConfig
142
+ } = config
61
143
 
62
- // Warn about multiple instances
144
+ // Track instance creation and warn on duplicates
145
+ UniversalWalletConnector.instanceCount++
63
146
  if (
64
147
  UniversalWalletConnector.instanceCount > 1 &&
65
148
  !UniversalWalletConnector.hasWarnedAboutMultipleInstances
@@ -69,7 +152,7 @@ export class UniversalWalletConnector {
69
152
  `⚠️ WARNING: Multiple instances of UniversalWalletConnector detected (${UniversalWalletConnector.instanceCount} instances). ` +
70
153
  'This can lead to state inconsistencies and unexpected behavior. ' +
71
154
  'Please ensure you create only one instance and reuse it throughout your application. ' +
72
- 'Consider using a singleton pattern or a context provider. ' +
155
+ 'Use UniversalWalletConnector.getInstance(config) instead of `new` to guarantee a single instance. ' +
73
156
  'You can ignore this warning if you are using React Strict Mode, which intentionally mounts components twice in development to help identify side effects.'
74
157
  )
75
158
  UniversalWalletConnector.hasWarnedAboutMultipleInstances = true
@@ -80,14 +163,14 @@ export class UniversalWalletConnector {
80
163
  'Universal Wallet Connector must be initialized with at least one network'
81
164
  )
82
165
  }
83
- if (!wallets) {
166
+ if (!configuredWallets) {
84
167
  throw new Error(
85
168
  'Universal Wallet Connector must be initialized with at least one wallet'
86
169
  )
87
170
  }
88
171
 
89
- this.wallets = wallets
90
- this.usingIntegratedBrowser = usingIntegratedBrowser
172
+ this.wallets = configuredWallets
173
+ this.usingIntegratedBrowser = configuredUsingIntegratedBrowser
91
174
 
92
175
  // Create RPC map from networks
93
176
  this.networkRpcMap = createNetworkRpcMap(networks)
@@ -97,22 +180,25 @@ export class UniversalWalletConnector {
97
180
  this.injectedConnector = new InjectedConnector(
98
181
  this.networkRpcMap,
99
182
  this.wallets,
100
- tonConnectConfig
183
+ configuredTonConnectConfig
101
184
  )
102
185
  this.connectors.set('injected', this.injectedConnector)
103
186
 
104
- if (tonConnectConfig) {
187
+ if (configuredTonConnectConfig) {
105
188
  this.connectors.set(
106
189
  'tonConnect',
107
- new TonConnectConnector(tonConnectConfig)
190
+ new TonConnectConnector(configuredTonConnectConfig)
108
191
  )
109
192
  }
110
193
 
111
194
  // Only add WalletConnect connector if config is provided
112
- if (walletConnectConfig) {
195
+ if (configuredWalletConnectConfig) {
113
196
  this.connectors.set(
114
197
  'walletConnect',
115
- new WalletConnectConnector(walletConnectConfig, this.networkRpcMap)
198
+ new WalletConnectConnector(
199
+ configuredWalletConnectConfig,
200
+ this.networkRpcMap
201
+ )
116
202
  )
117
203
  }
118
204
 
@@ -123,14 +209,14 @@ export class UniversalWalletConnector {
123
209
  this.wallets,
124
210
  this.sessionManager,
125
211
  this.connectors,
126
- usingIntegratedBrowser,
212
+ configuredUsingIntegratedBrowser,
127
213
  this.eventManager
128
214
  )
129
215
  this.networkSwitchService = new NetworkSwitchService(
130
216
  networks,
131
217
  this.sessionManager,
132
218
  this.connectors,
133
- usingIntegratedBrowser,
219
+ configuredUsingIntegratedBrowser,
134
220
  this.eventManager
135
221
  )
136
222
  this.signatureService = new SignatureService(
@@ -148,6 +234,12 @@ export class UniversalWalletConnector {
148
234
  this.eventManager
149
235
  )
150
236
 
237
+ // Register as the shared instance so getInstance() works
238
+ // (only set if no instance exists, to avoid overwriting an earlier one)
239
+ if (!UniversalWalletConnector.instance) {
240
+ UniversalWalletConnector.instance = this
241
+ }
242
+
151
243
  // Initialize detected wallets after services are created
152
244
  this.initializeDetectedWallets()
153
245
  }
@@ -172,151 +264,35 @@ export class UniversalWalletConnector {
172
264
  }
173
265
  })
174
266
 
175
- // Check available wallets for each namespace across all connectors
267
+ // Run every namespace × connector detection concurrently.
268
+ // The namespace loop used to be sequential and `await`-ed each call,
269
+ // which meant a 100ms EIP-6963 native timeout + any bridge polling
270
+ // added up. With Promise.all the total time is the slowest single
271
+ // call, not the sum.
272
+ const detectionTasks: Promise<void>[] = []
176
273
  for (const namespace of namespaces) {
177
274
  for (const [, connector] of this.connectors) {
178
- // Check if connector supports getAvailableWallets
179
- if (typeof connector.getAvailableWallets === 'function') {
180
- try {
181
- const detectedWallets = await connector.getAvailableWallets(
182
- namespace as Namespace,
183
- this.wallets
275
+ if (typeof connector.getAvailableWallets !== 'function') continue
276
+ detectionTasks.push(
277
+ Promise.resolve()
278
+ .then(() =>
279
+ connector.getAvailableWallets!(
280
+ namespace as Namespace,
281
+ this.wallets
282
+ )
184
283
  )
185
-
186
- // Update wallets with detected information
187
- detectedWallets.forEach(detected => {
188
- // Find matching wallet based on namespace-specific naming
189
- let wallet: WalletMetadata | undefined
190
-
191
- if (namespace === 'eip155') {
192
- // For EIP155, match by eip155Name
193
- wallet = this.wallets.find(w => {
194
- const provider = this.usingIntegratedBrowser
195
- ? w.integratedBrowserInjectedProvider
196
- : w.extensionInjectedProvider
197
- return (
198
- provider?.namespaceMetaData?.eip155?.eip155Name?.toLowerCase() ===
199
- detected.name.toLowerCase()
200
- )
201
- })
202
- } else if (namespace === 'solana') {
203
- // For Solana, match by walletStandardName
204
- wallet = this.wallets.find(w => {
205
- const provider = this.usingIntegratedBrowser
206
- ? w.integratedBrowserInjectedProvider
207
- : w.extensionInjectedProvider
208
- return (
209
- provider?.namespaceMetaData?.solana?.walletStandardName?.toLowerCase() ===
210
- detected.name.toLowerCase()
211
- )
212
- })
213
- } else if (namespace === 'tron') {
214
- // For Tron, match by injectedId path prefix
215
- const tronDetected = detected as DetectedTronWalletInfo
216
- wallet = this.wallets.find(w => {
217
- const provider = this.usingIntegratedBrowser
218
- ? w.integratedBrowserInjectedProvider
219
- : w.extensionInjectedProvider
220
- const injectedId =
221
- provider?.namespaceMetaData?.tron?.injectedId
222
- return (
223
- injectedId &&
224
- tronDetected.uuid ===
225
- `tron-${injectedId}`.toLowerCase().replace(/\./g, '-')
226
- )
227
- })
228
- } else if (namespace === 'tvm') {
229
- // For TON, match by jsBridgeKey
230
- const tonDetected = detected as DetectedTonWalletInfo
231
- wallet = this.wallets.find(w => {
232
- const provider = this.usingIntegratedBrowser
233
- ? w.integratedBrowserInjectedProvider
234
- : w.extensionInjectedProvider
235
- return (
236
- provider?.namespaceMetaData?.tvm?.jsBridgeKey ===
237
- tonDetected.jsBridgeKey
238
- )
239
- })
240
- } else {
241
- // For other namespaces, fall back to wallet name
242
- wallet = this.wallets.find(
243
- w => w.name.toLowerCase() === detected.name.toLowerCase()
244
- )
245
- }
246
-
247
- if (wallet) {
248
- // Determine which provider to update based on usingIntegratedBrowser
249
- const provider = this.usingIntegratedBrowser
250
- ? wallet.integratedBrowserInjectedProvider
251
- : wallet.extensionInjectedProvider
252
-
253
- // Update the appropriate namespace metadata
254
- if (provider?.namespaceMetaData) {
255
- const nsMetadata =
256
- provider.namespaceMetaData[namespace as Namespace]
257
- if (nsMetadata && namespace === 'eip155') {
258
- // For EIP155, we have specific fields
259
- const eip155Metadata = nsMetadata as {
260
- installed?: boolean
261
- detectedWallet?: DetectedEIP6963WalletInfo
262
- }
263
- eip155Metadata.installed = true
264
- const eip155Detected =
265
- detected as DetectedEIP6963WalletInfo
266
- eip155Metadata.detectedWallet = {
267
- uuid: eip155Detected.uuid,
268
- name: eip155Detected.name,
269
- icon: eip155Detected.icon,
270
- rdns: eip155Detected.rdns
271
- }
272
- } else if (namespace === 'solana') {
273
- // For Solana, we have different fields
274
- const solanaMetadata = nsMetadata as {
275
- installed?: boolean
276
- detectedWallet?: DetectedSolanaWalletInfo
277
- }
278
- solanaMetadata.installed = true
279
- const solanaDetected =
280
- detected as DetectedSolanaWalletInfo
281
- solanaMetadata.detectedWallet = {
282
- uuid: solanaDetected.uuid,
283
- name: solanaDetected.name,
284
- features: solanaDetected.features
285
- }
286
- } else if (namespace === 'tron') {
287
- const tronMetadata = nsMetadata as {
288
- installed?: boolean
289
- detectedWallet?: DetectedTronWalletInfo
290
- }
291
- tronMetadata.installed = true
292
- const tronDetected = detected as DetectedTronWalletInfo
293
- tronMetadata.detectedWallet = {
294
- uuid: tronDetected.uuid,
295
- name: tronDetected.name
296
- }
297
- } else if (namespace === 'tvm') {
298
- const tonMetadata = nsMetadata as {
299
- installed?: boolean
300
- detectedWallet?: DetectedTonWalletInfo
301
- }
302
- tonMetadata.installed = true
303
- const tonDetected = detected as DetectedTonWalletInfo
304
- tonMetadata.detectedWallet = {
305
- uuid: tonDetected.uuid,
306
- name: tonDetected.name,
307
- icon: tonDetected.icon,
308
- jsBridgeKey: tonDetected.jsBridgeKey
309
- }
310
- }
311
- }
284
+ .then(detected => {
285
+ for (const d of detected) {
286
+ this.applyDetectedWallet(namespace as Namespace, d)
312
287
  }
313
288
  })
314
- } catch {
315
- // Silently handle errors for individual connector/namespace combinations
316
- }
317
- }
289
+ .catch(() => {
290
+ // Silently handle errors for individual connector/namespace combinations
291
+ })
292
+ )
318
293
  }
319
294
  }
295
+ await Promise.all(detectionTasks)
320
296
 
321
297
  // Notify the connection service to update its internal wallets reference
322
298
  this.connectionService.updateWallets(this.wallets)
@@ -324,12 +300,131 @@ export class UniversalWalletConnector {
324
300
  // Mark detection as complete
325
301
  this.detectionComplete = true
326
302
 
327
- // Notify listeners about the update
328
- this.eventManager.notify()
329
- } catch {
330
- // Silently handle error during initialization
303
+ this.eventManager.emit('walletsDetected', { wallets: this.wallets })
304
+ this.eventManager.emit('ready', undefined)
305
+ } catch (error) {
331
306
  this.detectionComplete = true
332
- this.eventManager.notify()
307
+ this.emitError(error, 'initialize')
308
+ this.eventManager.emit('ready', undefined)
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Merge a single detected wallet (from one namespace × connector run) into
314
+ * the configured wallet metadata. Factored out so detection tasks can run
315
+ * in parallel without racing on the merge logic.
316
+ */
317
+ private applyDetectedWallet(
318
+ namespace: Namespace,
319
+ detected: {
320
+ uuid: string
321
+ name: string
322
+ icon?: string
323
+ rdns?: string
324
+ features?: string[]
325
+ jsBridgeKey?: string
326
+ }
327
+ ): void {
328
+ let wallet: WalletMetadata | undefined
329
+
330
+ const pickProvider = (w: WalletMetadata) =>
331
+ this.usingIntegratedBrowser
332
+ ? w.integratedBrowserInjectedProvider
333
+ : w.extensionInjectedProvider
334
+
335
+ if (namespace === 'eip155') {
336
+ wallet = this.wallets.find(
337
+ w =>
338
+ pickProvider(
339
+ w
340
+ )?.namespaceMetaData?.eip155?.eip155Name?.toLowerCase() ===
341
+ detected.name.toLowerCase()
342
+ )
343
+ } else if (namespace === 'solana') {
344
+ wallet = this.wallets.find(
345
+ w =>
346
+ pickProvider(
347
+ w
348
+ )?.namespaceMetaData?.solana?.walletStandardName?.toLowerCase() ===
349
+ detected.name.toLowerCase()
350
+ )
351
+ } else if (namespace === 'tron') {
352
+ const tronDetected = detected as DetectedTronWalletInfo
353
+ wallet = this.wallets.find(w => {
354
+ const injectedId = pickProvider(w)?.namespaceMetaData?.tron?.injectedId
355
+ return (
356
+ injectedId &&
357
+ tronDetected.uuid ===
358
+ `tron-${injectedId}`.toLowerCase().replace(/\./g, '-')
359
+ )
360
+ })
361
+ } else if (namespace === 'tvm') {
362
+ const tonDetected = detected as DetectedTonWalletInfo
363
+ wallet = this.wallets.find(
364
+ w =>
365
+ pickProvider(w)?.namespaceMetaData?.tvm?.jsBridgeKey ===
366
+ tonDetected.jsBridgeKey
367
+ )
368
+ } else {
369
+ wallet = this.wallets.find(
370
+ w => w.name.toLowerCase() === detected.name.toLowerCase()
371
+ )
372
+ }
373
+
374
+ if (!wallet) return
375
+
376
+ const provider = pickProvider(wallet)
377
+ if (!provider?.namespaceMetaData) return
378
+
379
+ const nsMetadata = provider.namespaceMetaData[namespace]
380
+ if (!nsMetadata) return
381
+
382
+ if (namespace === 'eip155') {
383
+ const m = nsMetadata as {
384
+ installed?: boolean
385
+ detectedWallet?: DetectedEIP6963WalletInfo
386
+ }
387
+ const d = detected as DetectedEIP6963WalletInfo
388
+ m.installed = true
389
+ m.detectedWallet = {
390
+ uuid: d.uuid,
391
+ name: d.name,
392
+ icon: d.icon,
393
+ rdns: d.rdns
394
+ }
395
+ } else if (namespace === 'solana') {
396
+ const m = nsMetadata as {
397
+ installed?: boolean
398
+ detectedWallet?: DetectedSolanaWalletInfo
399
+ }
400
+ const d = detected as DetectedSolanaWalletInfo
401
+ m.installed = true
402
+ m.detectedWallet = {
403
+ uuid: d.uuid,
404
+ name: d.name,
405
+ features: d.features
406
+ }
407
+ } else if (namespace === 'tron') {
408
+ const m = nsMetadata as {
409
+ installed?: boolean
410
+ detectedWallet?: DetectedTronWalletInfo
411
+ }
412
+ const d = detected as DetectedTronWalletInfo
413
+ m.installed = true
414
+ m.detectedWallet = { uuid: d.uuid, name: d.name }
415
+ } else if (namespace === 'tvm') {
416
+ const m = nsMetadata as {
417
+ installed?: boolean
418
+ detectedWallet?: DetectedTonWalletInfo
419
+ }
420
+ const d = detected as DetectedTonWalletInfo
421
+ m.installed = true
422
+ m.detectedWallet = {
423
+ uuid: d.uuid,
424
+ name: d.name,
425
+ icon: d.icon,
426
+ jsBridgeKey: d.jsBridgeKey
427
+ }
333
428
  }
334
429
  }
335
430
 
@@ -341,6 +436,32 @@ export class UniversalWalletConnector {
341
436
  return this.detectionComplete
342
437
  }
343
438
 
439
+ // ---- Event subscription ----
440
+
441
+ /** Subscribe to a typed event. Returns an unsubscribe function. */
442
+ on<K extends UWCEventName>(
443
+ event: K,
444
+ listener: UWCEventListener<K>
445
+ ): () => void {
446
+ return this.eventManager.on(event, listener)
447
+ }
448
+
449
+ /** Subscribe once; the listener is removed after the first dispatch. */
450
+ once<K extends UWCEventName>(
451
+ event: K,
452
+ listener: UWCEventListener<K>
453
+ ): () => void {
454
+ return this.eventManager.once(event, listener)
455
+ }
456
+
457
+ off<K extends UWCEventName>(event: K, listener: UWCEventListener<K>): void {
458
+ this.eventManager.off(event, listener)
459
+ }
460
+
461
+ /**
462
+ * @deprecated Prefer `on(eventName, listener)` for targeted updates.
463
+ * Subscribes to the catch-all `change` event.
464
+ */
344
465
  subscribe(listener: () => void): () => void {
345
466
  return this.eventManager.subscribe(listener)
346
467
  }
@@ -362,24 +483,45 @@ export class UniversalWalletConnector {
362
483
  async connect(
363
484
  connectionMode: ConnectionMode,
364
485
  walletId: string,
365
- networkId?: NetworkId
486
+ networkId?: NetworkId,
487
+ options?: OperationOptions
366
488
  ): Promise<void> {
367
- await this.connectionService.connect(connectionMode, walletId, networkId)
368
- this.eventManager.notify()
489
+ try {
490
+ await this.connectionService.connect(
491
+ connectionMode,
492
+ walletId,
493
+ networkId,
494
+ options
495
+ )
496
+ } catch (error) {
497
+ this.emitError(error, 'connect')
498
+ throw error
499
+ }
369
500
  }
370
501
 
371
502
  getConnectionURI(): string | undefined {
372
503
  return this.connectionService.getConnectionURI()
373
504
  }
374
505
 
375
- async disconnect(): Promise<void> {
376
- await this.connectionService.disconnect()
377
- this.eventManager.notify()
506
+ async disconnect(options?: OperationOptions): Promise<void> {
507
+ try {
508
+ await this.connectionService.disconnect(options)
509
+ } catch (error) {
510
+ this.emitError(error, 'disconnect')
511
+ throw error
512
+ }
378
513
  }
379
514
 
380
- async switchNetwork(networkId: NetworkId): Promise<void> {
381
- await this.networkSwitchService.switchNetwork(networkId)
382
- this.eventManager.notify()
515
+ async switchNetwork(
516
+ networkId: NetworkId,
517
+ options?: OperationOptions
518
+ ): Promise<void> {
519
+ try {
520
+ await this.networkSwitchService.switchNetwork(networkId, options)
521
+ } catch (error) {
522
+ this.emitError(error, 'switchNetwork')
523
+ throw error
524
+ }
383
525
  }
384
526
 
385
527
  getNetworkSwitchLoadingState() {
@@ -389,9 +531,13 @@ export class UniversalWalletConnector {
389
531
  /**
390
532
  * Sign a message with the connected wallet
391
533
  * @param message The message to sign
534
+ * @param options Optional cancellation signal
392
535
  * @returns A promise that resolves to the signature
393
536
  */
394
- async signMessage(message: string): Promise<SignatureType> {
537
+ async signMessage(
538
+ message: string,
539
+ options?: OperationOptions
540
+ ): Promise<SignatureType> {
395
541
  const session = this.sessionManager.getSession()
396
542
 
397
543
  if (!session.activeWallet) {
@@ -402,23 +548,32 @@ export class UniversalWalletConnector {
402
548
  throw new Error('No active connection')
403
549
  }
404
550
 
405
- // Get the provider from the active wallet based on connection mode
406
- let provider
407
- if (session.connectionMode === 'injected') {
408
- provider = this.usingIntegratedBrowser
409
- ? session.activeWallet.integratedBrowserInjectedProvider
410
- : session.activeWallet.extensionInjectedProvider
411
- } else if (session.connectionMode === 'walletConnect') {
412
- provider = session.activeWallet.walletConnectProvider
413
- } else if (session.connectionMode === 'tonConnect') {
414
- return await this.signatureService.signMessage(message)
415
- }
551
+ try {
552
+ // Get the provider from the active wallet based on connection mode
553
+ let provider
554
+ if (session.connectionMode === 'injected') {
555
+ provider = this.usingIntegratedBrowser
556
+ ? session.activeWallet.integratedBrowserInjectedProvider
557
+ : session.activeWallet.extensionInjectedProvider
558
+ } else if (session.connectionMode === 'walletConnect') {
559
+ provider = session.activeWallet.walletConnectProvider
560
+ } else if (session.connectionMode === 'tonConnect') {
561
+ return await this.signatureService.signMessage(
562
+ message,
563
+ undefined,
564
+ options
565
+ )
566
+ }
416
567
 
417
- if (!provider) {
418
- throw new Error('No provider available for the active wallet')
419
- }
568
+ if (!provider) {
569
+ throw new Error('No provider available for the active wallet')
570
+ }
420
571
 
421
- return await this.signatureService.signMessage(message, provider)
572
+ return await this.signatureService.signMessage(message, provider, options)
573
+ } catch (error) {
574
+ this.emitError(error, 'signMessage')
575
+ throw error
576
+ }
422
577
  }
423
578
 
424
579
  /**
@@ -463,10 +618,12 @@ export class UniversalWalletConnector {
463
618
  /**
464
619
  * Send a transaction with the connected wallet
465
620
  * @param request The transaction request parameters
621
+ * @param options Optional cancellation signal
466
622
  * @returns A promise that resolves to the transaction result
467
623
  */
468
624
  async sendTransaction(
469
- request: TransactionRequest
625
+ request: TransactionRequest,
626
+ options?: OperationOptions
470
627
  ): Promise<TransactionResult> {
471
628
  const session = this.sessionManager.getSession()
472
629
 
@@ -478,23 +635,36 @@ export class UniversalWalletConnector {
478
635
  throw new Error('No active connection')
479
636
  }
480
637
 
481
- // Get the provider from the active wallet based on connection mode
482
- let provider
483
- if (session.connectionMode === 'injected') {
484
- provider = this.usingIntegratedBrowser
485
- ? session.activeWallet.integratedBrowserInjectedProvider
486
- : session.activeWallet.extensionInjectedProvider
487
- } else if (session.connectionMode === 'walletConnect') {
488
- provider = session.activeWallet.walletConnectProvider
489
- } else if (session.connectionMode === 'tonConnect') {
490
- return await this.transactionService.sendTransaction(request)
491
- }
638
+ try {
639
+ // Get the provider from the active wallet based on connection mode
640
+ let provider
641
+ if (session.connectionMode === 'injected') {
642
+ provider = this.usingIntegratedBrowser
643
+ ? session.activeWallet.integratedBrowserInjectedProvider
644
+ : session.activeWallet.extensionInjectedProvider
645
+ } else if (session.connectionMode === 'walletConnect') {
646
+ provider = session.activeWallet.walletConnectProvider
647
+ } else if (session.connectionMode === 'tonConnect') {
648
+ return await this.transactionService.sendTransaction(
649
+ request,
650
+ undefined,
651
+ options
652
+ )
653
+ }
492
654
 
493
- if (!provider) {
494
- throw new Error('No provider available for the active wallet')
495
- }
655
+ if (!provider) {
656
+ throw new Error('No provider available for the active wallet')
657
+ }
496
658
 
497
- return await this.transactionService.sendTransaction(request, provider)
659
+ return await this.transactionService.sendTransaction(
660
+ request,
661
+ provider,
662
+ options
663
+ )
664
+ } catch (error) {
665
+ this.emitError(error, 'sendTransaction')
666
+ throw error
667
+ }
498
668
  }
499
669
 
500
670
  /**
@@ -528,16 +698,24 @@ export class UniversalWalletConnector {
528
698
  * Fetch wallet capabilities for the connected wallet
529
699
  * @param address The wallet address to fetch capabilities for
530
700
  * @param activeNetwork Optional network to set as the active network for capabilities
701
+ * @param options Optional cancellation signal
531
702
  * @returns A promise that resolves when capabilities are fetched and updated
532
703
  */
533
704
  async getWalletCapabilities(
534
705
  address: string,
535
- activeNetwork?: Network
706
+ activeNetwork?: Network,
707
+ options?: OperationOptions
536
708
  ): Promise<void> {
537
- return await this.walletCapabilitiesService.getWalletCapabilities(
538
- address,
539
- activeNetwork
540
- )
709
+ try {
710
+ return await this.walletCapabilitiesService.getWalletCapabilities(
711
+ address,
712
+ activeNetwork,
713
+ options
714
+ )
715
+ } catch (error) {
716
+ this.emitError(error, 'getWalletCapabilities')
717
+ throw error
718
+ }
541
719
  }
542
720
 
543
721
  /**
@@ -599,4 +777,17 @@ export class UniversalWalletConnector {
599
777
  const session = this.sessionManager.getSession()
600
778
  return session.activeWalletCapabilities
601
779
  }
780
+
781
+ // ---- private helpers ----
782
+
783
+ private emitError(error: unknown, operation: UWCOperation): void {
784
+ const walletError: WalletError =
785
+ error instanceof WalletConnectorError
786
+ ? { type: error.type, message: error.message }
787
+ : {
788
+ type: 'unknown',
789
+ message: error instanceof Error ? error.message : String(error)
790
+ }
791
+ this.eventManager.emit('error', { error: walletError, operation })
792
+ }
602
793
  }