@meshconnect/uwc-core 0.2.0

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 (60) hide show
  1. package/dist/connection-manager.d.ts +18 -0
  2. package/dist/connection-manager.d.ts.map +1 -0
  3. package/dist/connection-manager.js +54 -0
  4. package/dist/connection-manager.js.map +1 -0
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +4 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/managers/event-manager.d.ts +10 -0
  10. package/dist/managers/event-manager.d.ts.map +1 -0
  11. package/dist/managers/event-manager.js +19 -0
  12. package/dist/managers/event-manager.js.map +1 -0
  13. package/dist/managers/index.d.ts +3 -0
  14. package/dist/managers/index.d.ts.map +1 -0
  15. package/dist/managers/index.js +3 -0
  16. package/dist/managers/index.js.map +1 -0
  17. package/dist/managers/session-manager.d.ts +11 -0
  18. package/dist/managers/session-manager.d.ts.map +1 -0
  19. package/dist/managers/session-manager.js +36 -0
  20. package/dist/managers/session-manager.js.map +1 -0
  21. package/dist/services/connection-service.d.ts +24 -0
  22. package/dist/services/connection-service.d.ts.map +1 -0
  23. package/dist/services/connection-service.js +176 -0
  24. package/dist/services/connection-service.js.map +1 -0
  25. package/dist/services/index.d.ts +4 -0
  26. package/dist/services/index.d.ts.map +1 -0
  27. package/dist/services/index.js +4 -0
  28. package/dist/services/index.js.map +1 -0
  29. package/dist/services/network-switch-service.d.ts +20 -0
  30. package/dist/services/network-switch-service.d.ts.map +1 -0
  31. package/dist/services/network-switch-service.js +130 -0
  32. package/dist/services/network-switch-service.js.map +1 -0
  33. package/dist/services/signature-service.d.ts +18 -0
  34. package/dist/services/signature-service.d.ts.map +1 -0
  35. package/dist/services/signature-service.js +53 -0
  36. package/dist/services/signature-service.js.map +1 -0
  37. package/dist/services/transaction-service.d.ts +18 -0
  38. package/dist/services/transaction-service.d.ts.map +1 -0
  39. package/dist/services/transaction-service.js +53 -0
  40. package/dist/services/transaction-service.js.map +1 -0
  41. package/dist/universal-wallet-connector.d.ts +56 -0
  42. package/dist/universal-wallet-connector.d.ts.map +1 -0
  43. package/dist/universal-wallet-connector.js +302 -0
  44. package/dist/universal-wallet-connector.js.map +1 -0
  45. package/dist/utils/network-rpc-utils.d.ts +10 -0
  46. package/dist/utils/network-rpc-utils.d.ts.map +1 -0
  47. package/dist/utils/network-rpc-utils.js +20 -0
  48. package/dist/utils/network-rpc-utils.js.map +1 -0
  49. package/package.json +36 -0
  50. package/src/index.ts +3 -0
  51. package/src/managers/event-manager.ts +25 -0
  52. package/src/managers/index.ts +2 -0
  53. package/src/managers/session-manager.ts +43 -0
  54. package/src/services/connection-service.ts +248 -0
  55. package/src/services/index.ts +3 -0
  56. package/src/services/network-switch-service.ts +182 -0
  57. package/src/services/signature-service.ts +77 -0
  58. package/src/services/transaction-service.ts +79 -0
  59. package/src/universal-wallet-connector.ts +426 -0
  60. package/src/utils/network-rpc-utils.ts +27 -0
@@ -0,0 +1,426 @@
1
+ import type {
2
+ UniversalWalletConnectorState,
3
+ Network,
4
+ WalletMetadata,
5
+ NetworkId,
6
+ ConnectionMode,
7
+ WalletConnectConfig,
8
+ Connector,
9
+ Namespace,
10
+ DetectedEIP6963WalletInfo,
11
+ DetectedSolanaWalletInfo,
12
+ TransactionRequest,
13
+ TransactionResult,
14
+ NetworkRpcMap
15
+ } from '@meshconnect/uwc-types'
16
+ import { InjectedConnector } from '@meshconnect/uwc-injected-connector'
17
+ import { WalletConnectConnector } from '@meshconnect/uwc-wallet-connect-connector'
18
+ import { SessionManager } from './managers/session-manager'
19
+ import { EventManager } from './managers/event-manager'
20
+ import { ConnectionService } from './services/connection-service'
21
+ import { NetworkSwitchService } from './services/network-switch-service'
22
+ import { SignatureService } from './services/signature-service'
23
+ import { TransactionService } from './services/transaction-service'
24
+ import { createNetworkRpcMap } from './utils/network-rpc-utils'
25
+
26
+ export class UniversalWalletConnector {
27
+ private static instanceCount = 0
28
+ private static hasWarnedAboutMultipleInstances = false
29
+
30
+ private sessionManager: SessionManager
31
+ private eventManager: EventManager
32
+ private connectionService: ConnectionService
33
+ private networkSwitchService: NetworkSwitchService
34
+ private signatureService: SignatureService
35
+ private transactionService: TransactionService
36
+ private connectors: Map<ConnectionMode, Connector>
37
+ private injectedConnector: InjectedConnector
38
+ private wallets: WalletMetadata[]
39
+ private usingIntegratedBrowser: boolean
40
+ private detectionComplete = false
41
+ private networkRpcMap: NetworkRpcMap
42
+
43
+ constructor(
44
+ networks: Network[],
45
+ wallets: WalletMetadata[] = [],
46
+ usingIntegratedBrowser = false,
47
+ walletConnectConfig?: WalletConnectConfig
48
+ ) {
49
+ // Track instance creation
50
+ UniversalWalletConnector.instanceCount++
51
+
52
+ // Warn about multiple instances
53
+ if (
54
+ UniversalWalletConnector.instanceCount > 1 &&
55
+ !UniversalWalletConnector.hasWarnedAboutMultipleInstances
56
+ ) {
57
+ // eslint-disable-next-line no-console
58
+ console.error(
59
+ `⚠️ WARNING: Multiple instances of UniversalWalletConnector detected (${UniversalWalletConnector.instanceCount} instances). ` +
60
+ 'This can lead to state inconsistencies and unexpected behavior. ' +
61
+ 'Please ensure you create only one instance and reuse it throughout your application. ' +
62
+ 'Consider using a singleton pattern or a context provider. ' +
63
+ 'You can ignore this warning if you are using React Strict Mode, which intentionally mounts components twice in development to help identify side effects.'
64
+ )
65
+ UniversalWalletConnector.hasWarnedAboutMultipleInstances = true
66
+ }
67
+
68
+ if (!networks || networks.length === 0) {
69
+ throw new Error(
70
+ 'Universal Wallet Connector must be initialized with at least one network'
71
+ )
72
+ }
73
+ if (!wallets || wallets.length === 0) {
74
+ throw new Error(
75
+ 'Universal Wallet Connector must be initialized with at least one wallet'
76
+ )
77
+ }
78
+
79
+ this.wallets = wallets
80
+ this.usingIntegratedBrowser = usingIntegratedBrowser
81
+
82
+ // Create RPC map from networks
83
+ this.networkRpcMap = createNetworkRpcMap(networks)
84
+
85
+ // Initialize connectors with RPC map
86
+ this.connectors = new Map()
87
+ this.injectedConnector = new InjectedConnector(this.networkRpcMap)
88
+ this.connectors.set('injected', this.injectedConnector)
89
+
90
+ // Only add WalletConnect connector if config is provided
91
+ if (walletConnectConfig) {
92
+ this.connectors.set(
93
+ 'walletConnect',
94
+ new WalletConnectConnector(walletConnectConfig, this.networkRpcMap)
95
+ )
96
+ }
97
+
98
+ this.sessionManager = new SessionManager()
99
+ this.eventManager = new EventManager()
100
+ this.connectionService = new ConnectionService(
101
+ networks,
102
+ this.wallets,
103
+ this.sessionManager,
104
+ this.connectors,
105
+ usingIntegratedBrowser,
106
+ this.eventManager
107
+ )
108
+ this.networkSwitchService = new NetworkSwitchService(
109
+ networks,
110
+ this.sessionManager,
111
+ this.connectors,
112
+ usingIntegratedBrowser,
113
+ this.eventManager
114
+ )
115
+ this.signatureService = new SignatureService(
116
+ this.sessionManager,
117
+ this.connectors
118
+ )
119
+ this.transactionService = new TransactionService(
120
+ this.sessionManager,
121
+ this.connectors
122
+ )
123
+
124
+ // Initialize detected wallets after services are created
125
+ this.initializeDetectedWallets()
126
+ }
127
+
128
+ /**
129
+ * Discovers available wallets and updates the wallet metadata
130
+ */
131
+ private async initializeDetectedWallets(): Promise<void> {
132
+ try {
133
+ // Collect all unique namespaces from all wallets
134
+ const namespaces = new Set<string>()
135
+
136
+ this.wallets.forEach(wallet => {
137
+ const provider = this.usingIntegratedBrowser
138
+ ? wallet.integratedBrowserInjectedProvider
139
+ : wallet.extensionInjectedProvider
140
+
141
+ if (provider?.namespaceMetaData) {
142
+ Object.keys(provider.namespaceMetaData).forEach(ns => {
143
+ namespaces.add(ns)
144
+ })
145
+ }
146
+ })
147
+
148
+ // Check available wallets for each namespace across all connectors
149
+ for (const namespace of namespaces) {
150
+ for (const [, connector] of this.connectors) {
151
+ // Check if connector supports getAvailableWallets
152
+ if (typeof connector.getAvailableWallets === 'function') {
153
+ try {
154
+ const detectedWallets = await connector.getAvailableWallets(
155
+ namespace as Namespace
156
+ )
157
+
158
+ // Update wallets with detected information
159
+ detectedWallets.forEach(detected => {
160
+ // Find matching wallet based on namespace-specific naming
161
+ let wallet: WalletMetadata | undefined
162
+
163
+ if (namespace === 'eip155') {
164
+ // For EIP155, match by eip155Name
165
+ wallet = this.wallets.find(w => {
166
+ const provider = this.usingIntegratedBrowser
167
+ ? w.integratedBrowserInjectedProvider
168
+ : w.extensionInjectedProvider
169
+ return (
170
+ provider?.namespaceMetaData?.eip155?.eip155Name?.toLowerCase() ===
171
+ detected.name.toLowerCase()
172
+ )
173
+ })
174
+ } else if (namespace === 'solana') {
175
+ // For Solana, match by walletStandardName
176
+ wallet = this.wallets.find(w => {
177
+ const provider = this.usingIntegratedBrowser
178
+ ? w.integratedBrowserInjectedProvider
179
+ : w.extensionInjectedProvider
180
+ return (
181
+ provider?.namespaceMetaData?.solana?.walletStandardName?.toLowerCase() ===
182
+ detected.name.toLowerCase()
183
+ )
184
+ })
185
+ } else {
186
+ // For other namespaces, fall back to wallet name
187
+ wallet = this.wallets.find(
188
+ w => w.name.toLowerCase() === detected.name.toLowerCase()
189
+ )
190
+ }
191
+
192
+ if (wallet) {
193
+ // Determine which provider to update based on usingIntegratedBrowser
194
+ const provider = this.usingIntegratedBrowser
195
+ ? wallet.integratedBrowserInjectedProvider
196
+ : wallet.extensionInjectedProvider
197
+
198
+ // Update the appropriate namespace metadata
199
+ if (provider?.namespaceMetaData) {
200
+ const nsMetadata =
201
+ provider.namespaceMetaData[namespace as Namespace]
202
+ if (nsMetadata && namespace === 'eip155') {
203
+ // For EIP155, we have specific fields
204
+ const eip155Metadata = nsMetadata as {
205
+ installed?: boolean
206
+ detectedWallet?: DetectedEIP6963WalletInfo
207
+ }
208
+ eip155Metadata.installed = true
209
+ const eip155Detected =
210
+ detected as DetectedEIP6963WalletInfo
211
+ eip155Metadata.detectedWallet = {
212
+ uuid: eip155Detected.uuid,
213
+ name: eip155Detected.name,
214
+ icon: eip155Detected.icon,
215
+ rdns: eip155Detected.rdns
216
+ }
217
+ } else if (namespace === 'solana') {
218
+ // For Solana, we have different fields
219
+ const solanaMetadata = nsMetadata as {
220
+ installed?: boolean
221
+ detectedWallet?: DetectedSolanaWalletInfo
222
+ }
223
+ solanaMetadata.installed = true
224
+ const solanaDetected =
225
+ detected as DetectedSolanaWalletInfo
226
+ solanaMetadata.detectedWallet = {
227
+ uuid: solanaDetected.uuid,
228
+ name: solanaDetected.name,
229
+ features: solanaDetected.features
230
+ }
231
+ }
232
+ // For other namespaces, we could add different handling if needed
233
+ }
234
+ }
235
+ })
236
+ } catch {
237
+ // Silently handle errors for individual connector/namespace combinations
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ // Notify the connection service to update its internal wallets reference
244
+ this.connectionService.updateWallets(this.wallets)
245
+
246
+ // Mark detection as complete
247
+ this.detectionComplete = true
248
+
249
+ // Notify listeners about the update
250
+ this.eventManager.notify()
251
+ } catch {
252
+ // Silently handle error during initialization
253
+ this.detectionComplete = true
254
+ this.eventManager.notify()
255
+ }
256
+ }
257
+
258
+ getSession() {
259
+ return this.sessionManager.getSession()
260
+ }
261
+
262
+ isReady() {
263
+ return this.detectionComplete
264
+ }
265
+
266
+ subscribe(listener: () => void): () => void {
267
+ return this.eventManager.subscribe(listener)
268
+ }
269
+
270
+ getState(): UniversalWalletConnectorState {
271
+ return {
272
+ session: this.sessionManager.getSession()
273
+ }
274
+ }
275
+
276
+ getWallets(): WalletMetadata[] {
277
+ return this.connectionService.getWallets()
278
+ }
279
+
280
+ getNetworks(): Network[] {
281
+ return this.connectionService.getNetworks()
282
+ }
283
+
284
+ async connect(
285
+ connectionMode: ConnectionMode,
286
+ walletId: string,
287
+ networkId?: NetworkId
288
+ ): Promise<void> {
289
+ await this.connectionService.connect(connectionMode, walletId, networkId)
290
+ this.eventManager.notify()
291
+ }
292
+
293
+ getConnectionURI(): string | undefined {
294
+ return this.connectionService.getConnectionURI()
295
+ }
296
+
297
+ async disconnect(): Promise<void> {
298
+ await this.connectionService.disconnect()
299
+ this.eventManager.notify()
300
+ }
301
+
302
+ async switchNetwork(networkId: NetworkId): Promise<void> {
303
+ await this.networkSwitchService.switchNetwork(networkId)
304
+ this.eventManager.notify()
305
+ }
306
+
307
+ getNetworkSwitchLoadingState() {
308
+ return this.networkSwitchService.getLoadingState()
309
+ }
310
+
311
+ /**
312
+ * Sign a message with the connected wallet
313
+ * @param message The message to sign
314
+ * @returns A promise that resolves to the signature
315
+ */
316
+ async signMessage(message: string): Promise<string> {
317
+ const session = this.sessionManager.getSession()
318
+
319
+ if (!session.activeWallet) {
320
+ throw new Error('No active wallet')
321
+ }
322
+
323
+ if (!session.connectionMode) {
324
+ throw new Error('No active connection')
325
+ }
326
+
327
+ // Get the provider from the active wallet based on connection mode
328
+ let provider
329
+ if (session.connectionMode === 'injected') {
330
+ provider = this.usingIntegratedBrowser
331
+ ? session.activeWallet.integratedBrowserInjectedProvider
332
+ : session.activeWallet.extensionInjectedProvider
333
+ } else if (session.connectionMode === 'walletConnect') {
334
+ provider = session.activeWallet.walletConnectProvider
335
+ }
336
+
337
+ if (!provider) {
338
+ throw new Error('No provider available for the active wallet')
339
+ }
340
+
341
+ return await this.signatureService.signMessage(message, provider)
342
+ }
343
+
344
+ /**
345
+ * Send a transaction with the connected wallet
346
+ * @param request The transaction request parameters
347
+ * @returns A promise that resolves to the transaction result
348
+ */
349
+ async sendTransaction(
350
+ request: TransactionRequest
351
+ ): Promise<TransactionResult> {
352
+ const session = this.sessionManager.getSession()
353
+
354
+ if (!session.activeWallet) {
355
+ throw new Error('No active wallet')
356
+ }
357
+
358
+ if (!session.connectionMode) {
359
+ throw new Error('No active connection')
360
+ }
361
+
362
+ // Get the provider from the active wallet based on connection mode
363
+ let provider
364
+ if (session.connectionMode === 'injected') {
365
+ provider = this.usingIntegratedBrowser
366
+ ? session.activeWallet.integratedBrowserInjectedProvider
367
+ : session.activeWallet.extensionInjectedProvider
368
+ } else if (session.connectionMode === 'walletConnect') {
369
+ provider = session.activeWallet.walletConnectProvider
370
+ }
371
+
372
+ if (!provider) {
373
+ throw new Error('No provider available for the active wallet')
374
+ }
375
+
376
+ return await this.transactionService.sendTransaction(request, provider)
377
+ }
378
+
379
+ /**
380
+ * Check if a connection mode is available for a specific wallet
381
+ * @param connectionMode The connection mode to check
382
+ * @param walletId The wallet ID to check availability for
383
+ * @returns True if the connection mode is available, false otherwise
384
+ */
385
+ isConnectionModeAvailable(
386
+ connectionMode: ConnectionMode,
387
+ walletId: string
388
+ ): boolean {
389
+ // Find the wallet
390
+ const wallet = this.wallets.find(w => w.id === walletId)
391
+ if (!wallet) {
392
+ return false
393
+ }
394
+
395
+ if (connectionMode === 'walletConnect') {
396
+ // WalletConnect is available if the connector exists and wallet has WalletConnect provider
397
+ return (
398
+ this.connectors.has('walletConnect') &&
399
+ wallet.walletConnectProvider !== undefined
400
+ )
401
+ }
402
+
403
+ if (connectionMode === 'injected') {
404
+ // Check the appropriate provider based on browser type
405
+ const provider = this.usingIntegratedBrowser
406
+ ? wallet.integratedBrowserInjectedProvider
407
+ : wallet.extensionInjectedProvider
408
+
409
+ if (!provider) {
410
+ return false
411
+ }
412
+
413
+ // Check if at least one namespace is installed
414
+ const hasInstalledNamespace = provider.namespaceMetaData
415
+ ? Object.values(provider.namespaceMetaData).some(
416
+ namespace => namespace?.installed === true
417
+ )
418
+ : false
419
+
420
+ // Injected is available if provider exists and wallet is installed
421
+ return hasInstalledNamespace
422
+ }
423
+
424
+ return false
425
+ }
426
+ }
@@ -0,0 +1,27 @@
1
+ import type { Network, NetworkId, NetworkRpcMap } from '@meshconnect/uwc-types'
2
+
3
+ /**
4
+ * Extract RPC URLs from networks array and create a mapping
5
+ */
6
+ export function createNetworkRpcMap(networks: Network[]): NetworkRpcMap {
7
+ const rpcMap: NetworkRpcMap = {}
8
+
9
+ for (const network of networks) {
10
+ if (network.rpcUrls?.default?.http?.[0]) {
11
+ // Use the first HTTP RPC URL as the default
12
+ rpcMap[network.id] = network.rpcUrls.default.http[0]
13
+ }
14
+ }
15
+
16
+ return rpcMap
17
+ }
18
+
19
+ /**
20
+ * Get RPC URL for a specific network from the map
21
+ */
22
+ export function getRpcUrlForNetwork(
23
+ rpcMap: NetworkRpcMap,
24
+ networkId: NetworkId
25
+ ): string | undefined {
26
+ return rpcMap[networkId]
27
+ }