@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.
- package/README.md +924 -0
- package/dist/events.d.ts +71 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/managers/event-manager.d.ts +22 -3
- package/dist/managers/event-manager.d.ts.map +1 -1
- package/dist/managers/event-manager.js +63 -7
- package/dist/managers/event-manager.js.map +1 -1
- package/dist/services/connection-service.d.ts +5 -2
- package/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +19 -7
- package/dist/services/connection-service.js.map +1 -1
- package/dist/services/network-switch-service.d.ts +2 -1
- package/dist/services/network-switch-service.d.ts.map +1 -1
- package/dist/services/network-switch-service.js +15 -3
- package/dist/services/network-switch-service.js.map +1 -1
- package/dist/services/signature-service.d.ts +3 -1
- package/dist/services/signature-service.d.ts.map +1 -1
- package/dist/services/signature-service.js +10 -5
- package/dist/services/signature-service.js.map +1 -1
- package/dist/services/transaction-service.d.ts +3 -1
- package/dist/services/transaction-service.d.ts.map +1 -1
- package/dist/services/transaction-service.js +10 -5
- package/dist/services/transaction-service.js.map +1 -1
- package/dist/services/wallet-capabilities-service.d.ts +2 -1
- package/dist/services/wallet-capabilities-service.d.ts.map +1 -1
- package/dist/services/wallet-capabilities-service.js +9 -2
- package/dist/services/wallet-capabilities-service.js.map +1 -1
- package/dist/universal-wallet-connector.d.ts +52 -6
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +271 -177
- package/dist/universal-wallet-connector.js.map +1 -1
- package/package.json +5 -5
- package/src/events.ts +73 -0
- package/src/index.ts +11 -3
- package/src/managers/event-manager.test.ts +70 -0
- package/src/managers/event-manager.ts +80 -9
- package/src/services/connection-service.test.ts +13 -19
- package/src/services/connection-service.ts +34 -7
- package/src/services/network-switch-service.ts +22 -3
- package/src/services/signature-service.ts +13 -5
- package/src/services/transaction-service.ts +13 -5
- package/src/services/wallet-capabilities-service.ts +14 -2
- package/src/universal-wallet-connector.test.ts +87 -3
- 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
|
-
|
|
60
|
-
|
|
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
|
-
//
|
|
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
|
-
'
|
|
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 (!
|
|
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 =
|
|
90
|
-
this.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
|
-
|
|
183
|
+
configuredTonConnectConfig
|
|
101
184
|
)
|
|
102
185
|
this.connectors.set('injected', this.injectedConnector)
|
|
103
186
|
|
|
104
|
-
if (
|
|
187
|
+
if (configuredTonConnectConfig) {
|
|
105
188
|
this.connectors.set(
|
|
106
189
|
'tonConnect',
|
|
107
|
-
new TonConnectConnector(
|
|
190
|
+
new TonConnectConnector(configuredTonConnectConfig)
|
|
108
191
|
)
|
|
109
192
|
}
|
|
110
193
|
|
|
111
194
|
// Only add WalletConnect connector if config is provided
|
|
112
|
-
if (
|
|
195
|
+
if (configuredWalletConnectConfig) {
|
|
113
196
|
this.connectors.set(
|
|
114
197
|
'walletConnect',
|
|
115
|
-
new WalletConnectConnector(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
328
|
-
this.eventManager.
|
|
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.
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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(
|
|
381
|
-
|
|
382
|
-
|
|
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(
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
568
|
+
if (!provider) {
|
|
569
|
+
throw new Error('No provider available for the active wallet')
|
|
570
|
+
}
|
|
420
571
|
|
|
421
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
655
|
+
if (!provider) {
|
|
656
|
+
throw new Error('No provider available for the active wallet')
|
|
657
|
+
}
|
|
496
658
|
|
|
497
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
}
|