@meshconnect/uwc-core 0.7.4 → 0.7.5-snapshot.c90b3e8

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