@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.
- 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 +11 -3
- 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
|
@@ -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
|
-
|
|
59
|
-
|
|
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
|
-
//
|
|
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
|
-
'
|
|
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 (!
|
|
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 =
|
|
89
|
-
this.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
|
-
|
|
182
|
+
configuredTonConnectConfig
|
|
100
183
|
)
|
|
101
184
|
this.connectors.set('injected', this.injectedConnector)
|
|
102
185
|
|
|
103
|
-
if (
|
|
186
|
+
if (configuredTonConnectConfig) {
|
|
104
187
|
this.connectors.set(
|
|
105
188
|
'tonConnect',
|
|
106
|
-
new TonConnectConnector(
|
|
189
|
+
new TonConnectConnector(configuredTonConnectConfig)
|
|
107
190
|
)
|
|
108
191
|
}
|
|
109
192
|
|
|
110
193
|
// Only add WalletConnect connector if config is provided
|
|
111
|
-
if (
|
|
194
|
+
if (configuredWalletConnectConfig) {
|
|
112
195
|
this.connectors.set(
|
|
113
196
|
'walletConnect',
|
|
114
|
-
new WalletConnectConnector(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
-
|
|
327
|
-
this.eventManager.
|
|
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.
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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(
|
|
380
|
-
|
|
381
|
-
|
|
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(
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
567
|
+
if (!provider) {
|
|
568
|
+
throw new Error('No provider available for the active wallet')
|
|
569
|
+
}
|
|
419
570
|
|
|
420
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
615
|
+
if (!provider) {
|
|
616
|
+
throw new Error('No provider available for the active wallet')
|
|
617
|
+
}
|
|
456
618
|
|
|
457
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
}
|