@meshconnect/uwc-core 0.10.1 → 1.0.1
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 +928 -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 -4
- 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 +36 -9
- 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 +56 -8
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +299 -194
- package/dist/universal-wallet-connector.js.map +1 -1
- package/dist/utils/abort.d.ts +14 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +31 -0
- package/dist/utils/abort.js.map +1 -0
- package/package.json +3 -3
- package/src/events.ts +73 -0
- package/src/index.ts +11 -8
- package/src/managers/event-manager.test.ts +70 -0
- package/src/managers/event-manager.ts +80 -9
- package/src/services/connection-service.test.ts +148 -19
- package/src/services/connection-service.ts +63 -12
- 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 +196 -3
- package/src/universal-wallet-connector.ts +430 -222
- package/src/utils/abort.test.ts +68 -0
- package/src/utils/abort.ts +38 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WalletConnectorError } from '@meshconnect/uwc-types';
|
|
1
2
|
import { InjectedConnector } from '@meshconnect/uwc-injected-connector';
|
|
2
3
|
import { WalletConnectConnector } from '@meshconnect/uwc-wallet-connect-connector';
|
|
3
4
|
import { TonConnectConnector } from '@meshconnect/uwc-ton-connector';
|
|
@@ -10,6 +11,7 @@ import { TransactionService } from './services/transaction-service';
|
|
|
10
11
|
import { WalletCapabilitiesService } from './services/wallet-capabilities-service';
|
|
11
12
|
import { createNetworkRpcMap } from './utils/network-rpc-utils';
|
|
12
13
|
export class UniversalWalletConnector {
|
|
14
|
+
static instance = null;
|
|
13
15
|
static instanceCount = 0;
|
|
14
16
|
static hasWarnedAboutMultipleInstances = false;
|
|
15
17
|
sessionManager;
|
|
@@ -25,48 +27,100 @@ export class UniversalWalletConnector {
|
|
|
25
27
|
usingIntegratedBrowser;
|
|
26
28
|
detectionComplete = false;
|
|
27
29
|
networkRpcMap;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Recommended entry point: returns the shared instance, creating it on the
|
|
32
|
+
* first call. Subsequent calls ignore the `config` argument — use
|
|
33
|
+
* `resetInstance()` first if you need to reinitialise (e.g. in tests).
|
|
34
|
+
*/
|
|
35
|
+
static getInstance(config) {
|
|
36
|
+
if (UniversalWalletConnector.instance) {
|
|
37
|
+
return UniversalWalletConnector.instance;
|
|
38
|
+
}
|
|
39
|
+
if (!config) {
|
|
40
|
+
throw new Error('UniversalWalletConnector.getInstance() was called before an instance existed. Pass a config on the first call.');
|
|
41
|
+
}
|
|
42
|
+
return new UniversalWalletConnector(config);
|
|
43
|
+
}
|
|
44
|
+
/** Clear the shared instance reference. Primarily for tests and full-logout flows. */
|
|
45
|
+
static resetInstance() {
|
|
46
|
+
UniversalWalletConnector.instance = null;
|
|
47
|
+
UniversalWalletConnector.instanceCount = 0;
|
|
48
|
+
UniversalWalletConnector.hasWarnedAboutMultipleInstances = false;
|
|
49
|
+
}
|
|
50
|
+
constructor(networksOrConfig, wallets = [], usingIntegratedBrowser = false, walletConnectConfig, tonConnectConfig, tronConnectorConfig) {
|
|
51
|
+
if (!networksOrConfig) {
|
|
52
|
+
throw new Error('Universal Wallet Connector must be initialized with at least one network');
|
|
53
|
+
}
|
|
54
|
+
let config;
|
|
55
|
+
if (Array.isArray(networksOrConfig)) {
|
|
56
|
+
config = {
|
|
57
|
+
networks: networksOrConfig,
|
|
58
|
+
wallets,
|
|
59
|
+
usingIntegratedBrowser
|
|
60
|
+
};
|
|
61
|
+
if (walletConnectConfig) {
|
|
62
|
+
config.walletConnectConfig = walletConnectConfig;
|
|
63
|
+
}
|
|
64
|
+
if (tonConnectConfig) {
|
|
65
|
+
config.tonConnectConfig = tonConnectConfig;
|
|
66
|
+
}
|
|
67
|
+
if (tronConnectorConfig) {
|
|
68
|
+
config.tronConnectorConfig = tronConnectorConfig;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
config = networksOrConfig;
|
|
73
|
+
}
|
|
74
|
+
const { networks, wallets: configuredWallets = [], usingIntegratedBrowser: configuredUsingIntegratedBrowser = false, walletConnectConfig: configuredWalletConnectConfig, tonConnectConfig: configuredTonConnectConfig, tronConnectorConfig: configuredTronConnectorConfig } = config;
|
|
75
|
+
// Track instance creation and warn on duplicates
|
|
30
76
|
UniversalWalletConnector.instanceCount++;
|
|
31
|
-
// Warn about multiple instances
|
|
32
77
|
if (UniversalWalletConnector.instanceCount > 1 &&
|
|
33
78
|
!UniversalWalletConnector.hasWarnedAboutMultipleInstances) {
|
|
34
79
|
// eslint-disable-next-line no-console
|
|
35
80
|
console.error(`⚠️ WARNING: Multiple instances of UniversalWalletConnector detected (${UniversalWalletConnector.instanceCount} instances). ` +
|
|
36
81
|
'This can lead to state inconsistencies and unexpected behavior. ' +
|
|
37
82
|
'Please ensure you create only one instance and reuse it throughout your application. ' +
|
|
38
|
-
'
|
|
83
|
+
'Use UniversalWalletConnector.getInstance(config) instead of `new` to guarantee a single instance. ' +
|
|
39
84
|
'You can ignore this warning if you are using React Strict Mode, which intentionally mounts components twice in development to help identify side effects.');
|
|
40
85
|
UniversalWalletConnector.hasWarnedAboutMultipleInstances = true;
|
|
41
86
|
}
|
|
42
|
-
|
|
87
|
+
// Reject empty arrays as well as null/undefined to honour the error
|
|
88
|
+
// messages below. The companion type `UWCConfig.wallets` is required, so
|
|
89
|
+
// TypeScript callers get a compile error for the missing-wallets case;
|
|
90
|
+
// this runtime guard catches the empty-array case the type can't express.
|
|
91
|
+
if (!networks || networks.length === 0) {
|
|
43
92
|
throw new Error('Universal Wallet Connector must be initialized with at least one network');
|
|
44
93
|
}
|
|
45
|
-
if (!
|
|
94
|
+
if (!configuredWallets || configuredWallets.length === 0) {
|
|
46
95
|
throw new Error('Universal Wallet Connector must be initialized with at least one wallet');
|
|
47
96
|
}
|
|
48
|
-
this.wallets =
|
|
49
|
-
this.usingIntegratedBrowser =
|
|
97
|
+
this.wallets = configuredWallets;
|
|
98
|
+
this.usingIntegratedBrowser = configuredUsingIntegratedBrowser;
|
|
50
99
|
// Create RPC map from networks
|
|
51
100
|
this.networkRpcMap = createNetworkRpcMap(networks);
|
|
52
101
|
// Initialize connectors with RPC map
|
|
53
102
|
this.connectors = new Map();
|
|
54
|
-
this.injectedConnector = new InjectedConnector(this.networkRpcMap, this.wallets,
|
|
103
|
+
this.injectedConnector = new InjectedConnector(this.networkRpcMap, this.wallets, configuredTonConnectConfig, configuredTronConnectorConfig, networks);
|
|
55
104
|
this.connectors.set('injected', this.injectedConnector);
|
|
56
|
-
if (
|
|
57
|
-
this.connectors.set('tonConnect', new TonConnectConnector(
|
|
105
|
+
if (configuredTonConnectConfig) {
|
|
106
|
+
this.connectors.set('tonConnect', new TonConnectConnector(configuredTonConnectConfig));
|
|
58
107
|
}
|
|
59
108
|
// Only add WalletConnect connector if config is provided
|
|
60
|
-
if (
|
|
61
|
-
this.connectors.set('walletConnect', new WalletConnectConnector(
|
|
109
|
+
if (configuredWalletConnectConfig) {
|
|
110
|
+
this.connectors.set('walletConnect', new WalletConnectConnector(configuredWalletConnectConfig, this.networkRpcMap));
|
|
62
111
|
}
|
|
63
112
|
this.sessionManager = new SessionManager();
|
|
64
113
|
this.eventManager = new EventManager();
|
|
65
|
-
this.connectionService = new ConnectionService(networks, this.wallets, this.sessionManager, this.connectors,
|
|
66
|
-
this.networkSwitchService = new NetworkSwitchService(networks, this.sessionManager, this.connectors,
|
|
114
|
+
this.connectionService = new ConnectionService(networks, this.wallets, this.sessionManager, this.connectors, configuredUsingIntegratedBrowser, this.eventManager);
|
|
115
|
+
this.networkSwitchService = new NetworkSwitchService(networks, this.sessionManager, this.connectors, configuredUsingIntegratedBrowser, this.eventManager);
|
|
67
116
|
this.signatureService = new SignatureService(this.sessionManager, this.connectors);
|
|
68
117
|
this.transactionService = new TransactionService(this.sessionManager, this.connectors);
|
|
69
118
|
this.walletCapabilitiesService = new WalletCapabilitiesService(networks, this.connectors, this.sessionManager, this.eventManager);
|
|
119
|
+
// Register as the shared instance so getInstance() works
|
|
120
|
+
// (only set if no instance exists, to avoid overwriting an earlier one)
|
|
121
|
+
if (!UniversalWalletConnector.instance) {
|
|
122
|
+
UniversalWalletConnector.instance = this;
|
|
123
|
+
}
|
|
70
124
|
// Initialize detected wallets after services are created
|
|
71
125
|
this.initializeDetectedWallets();
|
|
72
126
|
}
|
|
@@ -87,153 +141,136 @@ export class UniversalWalletConnector {
|
|
|
87
141
|
});
|
|
88
142
|
}
|
|
89
143
|
});
|
|
90
|
-
//
|
|
144
|
+
// Run every namespace × connector detection concurrently.
|
|
145
|
+
// The namespace loop used to be sequential and `await`-ed each call,
|
|
146
|
+
// which meant a 100ms EIP-6963 native timeout + any bridge polling
|
|
147
|
+
// added up. With Promise.all the total time is the slowest single
|
|
148
|
+
// call, not the sum.
|
|
149
|
+
const detectionTasks = [];
|
|
91
150
|
for (const namespace of namespaces) {
|
|
92
151
|
for (const [, connector] of this.connectors) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
let wallet;
|
|
101
|
-
if (namespace === 'eip155') {
|
|
102
|
-
// For EIP155, match by eip155Name
|
|
103
|
-
wallet = this.wallets.find(w => {
|
|
104
|
-
const provider = this.usingIntegratedBrowser
|
|
105
|
-
? w.integratedBrowserInjectedProvider
|
|
106
|
-
: w.extensionInjectedProvider;
|
|
107
|
-
return (provider?.namespaceMetaData?.eip155?.eip155Name?.toLowerCase() ===
|
|
108
|
-
detected.name.toLowerCase());
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
else if (namespace === 'solana') {
|
|
112
|
-
// For Solana, match by walletStandardName
|
|
113
|
-
wallet = this.wallets.find(w => {
|
|
114
|
-
const provider = this.usingIntegratedBrowser
|
|
115
|
-
? w.integratedBrowserInjectedProvider
|
|
116
|
-
: w.extensionInjectedProvider;
|
|
117
|
-
return (provider?.namespaceMetaData?.solana?.walletStandardName?.toLowerCase() ===
|
|
118
|
-
detected.name.toLowerCase());
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
else if (namespace === 'tron') {
|
|
122
|
-
// Two id schemes can appear here:
|
|
123
|
-
// - Mesh-owned TronConnector: uuid === WalletMetadata.id
|
|
124
|
-
// (a stable TronWalletId like 'tronlink').
|
|
125
|
-
// - Legacy window-global discovery: uuid === `tron-${injectedId}`
|
|
126
|
-
// (e.g. 'tron-tronlink'), derived from the injection path.
|
|
127
|
-
const tronDetected = detected;
|
|
128
|
-
wallet = this.wallets.find(w => {
|
|
129
|
-
if (tronDetected.uuid === w.id)
|
|
130
|
-
return true;
|
|
131
|
-
const provider = this.usingIntegratedBrowser
|
|
132
|
-
? w.integratedBrowserInjectedProvider
|
|
133
|
-
: w.extensionInjectedProvider;
|
|
134
|
-
const injectedId = provider?.namespaceMetaData?.tron?.injectedId;
|
|
135
|
-
if (!injectedId)
|
|
136
|
-
return false;
|
|
137
|
-
// Mesh-owned connector reports the canonical injectedId —
|
|
138
|
-
// match it directly so catalog-driven wallets (whose `id`
|
|
139
|
-
// is a backend GUID, not a TronWalletId) are recognized.
|
|
140
|
-
if (tronDetected.injectedId &&
|
|
141
|
-
injectedId.toLowerCase() ===
|
|
142
|
-
tronDetected.injectedId.toLowerCase()) {
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
// Legacy window-global discovery scheme: `tron-${injectedId}`.
|
|
146
|
-
return (tronDetected.uuid ===
|
|
147
|
-
`tron-${injectedId}`.toLowerCase().replace(/\./g, '-'));
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
else if (namespace === 'tvm') {
|
|
151
|
-
// For TON, match by jsBridgeKey
|
|
152
|
-
const tonDetected = detected;
|
|
153
|
-
wallet = this.wallets.find(w => {
|
|
154
|
-
const provider = this.usingIntegratedBrowser
|
|
155
|
-
? w.integratedBrowserInjectedProvider
|
|
156
|
-
: w.extensionInjectedProvider;
|
|
157
|
-
return (provider?.namespaceMetaData?.tvm?.jsBridgeKey ===
|
|
158
|
-
tonDetected.jsBridgeKey);
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// For other namespaces, fall back to wallet name
|
|
163
|
-
wallet = this.wallets.find(w => w.name.toLowerCase() === detected.name.toLowerCase());
|
|
164
|
-
}
|
|
165
|
-
if (wallet) {
|
|
166
|
-
// Determine which provider to update based on usingIntegratedBrowser
|
|
167
|
-
const provider = this.usingIntegratedBrowser
|
|
168
|
-
? wallet.integratedBrowserInjectedProvider
|
|
169
|
-
: wallet.extensionInjectedProvider;
|
|
170
|
-
// Update the appropriate namespace metadata
|
|
171
|
-
if (provider?.namespaceMetaData) {
|
|
172
|
-
const nsMetadata = provider.namespaceMetaData[namespace];
|
|
173
|
-
if (nsMetadata && namespace === 'eip155') {
|
|
174
|
-
// For EIP155, we have specific fields
|
|
175
|
-
const eip155Metadata = nsMetadata;
|
|
176
|
-
eip155Metadata.installed = true;
|
|
177
|
-
const eip155Detected = detected;
|
|
178
|
-
eip155Metadata.detectedWallet = {
|
|
179
|
-
uuid: eip155Detected.uuid,
|
|
180
|
-
name: eip155Detected.name,
|
|
181
|
-
icon: eip155Detected.icon,
|
|
182
|
-
rdns: eip155Detected.rdns
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
else if (namespace === 'solana') {
|
|
186
|
-
// For Solana, we have different fields
|
|
187
|
-
const solanaMetadata = nsMetadata;
|
|
188
|
-
solanaMetadata.installed = true;
|
|
189
|
-
const solanaDetected = detected;
|
|
190
|
-
solanaMetadata.detectedWallet = {
|
|
191
|
-
uuid: solanaDetected.uuid,
|
|
192
|
-
name: solanaDetected.name,
|
|
193
|
-
features: solanaDetected.features
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
else if (namespace === 'tron') {
|
|
197
|
-
const tronMetadata = nsMetadata;
|
|
198
|
-
tronMetadata.installed = true;
|
|
199
|
-
const tronDetected = detected;
|
|
200
|
-
tronMetadata.detectedWallet = {
|
|
201
|
-
uuid: tronDetected.uuid,
|
|
202
|
-
name: tronDetected.name
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
else if (namespace === 'tvm') {
|
|
206
|
-
const tonMetadata = nsMetadata;
|
|
207
|
-
tonMetadata.installed = true;
|
|
208
|
-
const tonDetected = detected;
|
|
209
|
-
tonMetadata.detectedWallet = {
|
|
210
|
-
uuid: tonDetected.uuid,
|
|
211
|
-
name: tonDetected.name,
|
|
212
|
-
icon: tonDetected.icon,
|
|
213
|
-
jsBridgeKey: tonDetected.jsBridgeKey
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
// Silently handle errors for individual connector/namespace combinations
|
|
152
|
+
if (typeof connector.getAvailableWallets !== 'function')
|
|
153
|
+
continue;
|
|
154
|
+
detectionTasks.push(Promise.resolve()
|
|
155
|
+
.then(() => connector.getAvailableWallets(namespace, this.wallets))
|
|
156
|
+
.then(detected => {
|
|
157
|
+
for (const d of detected) {
|
|
158
|
+
this.applyDetectedWallet(namespace, d);
|
|
222
159
|
}
|
|
223
|
-
}
|
|
160
|
+
})
|
|
161
|
+
.catch(() => {
|
|
162
|
+
// Silently handle errors for individual connector/namespace combinations
|
|
163
|
+
}));
|
|
224
164
|
}
|
|
225
165
|
}
|
|
166
|
+
await Promise.all(detectionTasks);
|
|
226
167
|
// Notify the connection service to update its internal wallets reference
|
|
227
168
|
this.connectionService.updateWallets(this.wallets);
|
|
228
169
|
// Mark detection as complete
|
|
229
170
|
this.detectionComplete = true;
|
|
230
|
-
|
|
231
|
-
this.eventManager.
|
|
171
|
+
this.eventManager.emit('walletsDetected', { wallets: this.wallets });
|
|
172
|
+
this.eventManager.emit('ready', undefined);
|
|
232
173
|
}
|
|
233
|
-
catch {
|
|
234
|
-
// Silently handle error during initialization
|
|
174
|
+
catch (error) {
|
|
235
175
|
this.detectionComplete = true;
|
|
236
|
-
this.
|
|
176
|
+
this.emitError(error, 'initialize');
|
|
177
|
+
this.eventManager.emit('ready', undefined);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Merge a single detected wallet (from one namespace × connector run) into
|
|
182
|
+
* the configured wallet metadata. Factored out so detection tasks can run
|
|
183
|
+
* in parallel without racing on the merge logic.
|
|
184
|
+
*/
|
|
185
|
+
applyDetectedWallet(namespace, detected) {
|
|
186
|
+
let wallet;
|
|
187
|
+
const pickProvider = (w) => this.usingIntegratedBrowser
|
|
188
|
+
? w.integratedBrowserInjectedProvider
|
|
189
|
+
: w.extensionInjectedProvider;
|
|
190
|
+
if (namespace === 'eip155') {
|
|
191
|
+
wallet = this.wallets.find(w => pickProvider(w)?.namespaceMetaData?.eip155?.eip155Name?.toLowerCase() ===
|
|
192
|
+
detected.name.toLowerCase());
|
|
193
|
+
}
|
|
194
|
+
else if (namespace === 'solana') {
|
|
195
|
+
wallet = this.wallets.find(w => pickProvider(w)?.namespaceMetaData?.solana?.walletStandardName?.toLowerCase() ===
|
|
196
|
+
detected.name.toLowerCase());
|
|
197
|
+
}
|
|
198
|
+
else if (namespace === 'tron') {
|
|
199
|
+
// Three id schemes can appear here:
|
|
200
|
+
// - Mesh-owned TronConnector reports the canonical `injectedId` directly
|
|
201
|
+
// (added in #174) — match catalog wallets whose `id` is a backend GUID.
|
|
202
|
+
// - Mesh-owned TronConnector: uuid === WalletMetadata.id
|
|
203
|
+
// (a stable TronWalletId like 'tronlink').
|
|
204
|
+
// - Legacy window-global discovery: uuid === `tron-${injectedId}`
|
|
205
|
+
// (e.g. 'tron-tronlink'), derived from the injection path.
|
|
206
|
+
const tronDetected = detected;
|
|
207
|
+
wallet = this.wallets.find(w => {
|
|
208
|
+
if (tronDetected.uuid === w.id)
|
|
209
|
+
return true;
|
|
210
|
+
const injectedId = pickProvider(w)?.namespaceMetaData?.tron?.injectedId;
|
|
211
|
+
if (!injectedId)
|
|
212
|
+
return false;
|
|
213
|
+
if (tronDetected.injectedId &&
|
|
214
|
+
injectedId.toLowerCase() === tronDetected.injectedId.toLowerCase()) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return (tronDetected.uuid ===
|
|
218
|
+
`tron-${injectedId}`.toLowerCase().replace(/\./g, '-'));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else if (namespace === 'tvm') {
|
|
222
|
+
const tonDetected = detected;
|
|
223
|
+
wallet = this.wallets.find(w => pickProvider(w)?.namespaceMetaData?.tvm?.jsBridgeKey ===
|
|
224
|
+
tonDetected.jsBridgeKey);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
wallet = this.wallets.find(w => w.name.toLowerCase() === detected.name.toLowerCase());
|
|
228
|
+
}
|
|
229
|
+
if (!wallet)
|
|
230
|
+
return;
|
|
231
|
+
const provider = pickProvider(wallet);
|
|
232
|
+
if (!provider?.namespaceMetaData)
|
|
233
|
+
return;
|
|
234
|
+
const nsMetadata = provider.namespaceMetaData[namespace];
|
|
235
|
+
if (!nsMetadata)
|
|
236
|
+
return;
|
|
237
|
+
if (namespace === 'eip155') {
|
|
238
|
+
const m = nsMetadata;
|
|
239
|
+
const d = detected;
|
|
240
|
+
m.installed = true;
|
|
241
|
+
m.detectedWallet = {
|
|
242
|
+
uuid: d.uuid,
|
|
243
|
+
name: d.name,
|
|
244
|
+
icon: d.icon,
|
|
245
|
+
rdns: d.rdns
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
else if (namespace === 'solana') {
|
|
249
|
+
const m = nsMetadata;
|
|
250
|
+
const d = detected;
|
|
251
|
+
m.installed = true;
|
|
252
|
+
m.detectedWallet = {
|
|
253
|
+
uuid: d.uuid,
|
|
254
|
+
name: d.name,
|
|
255
|
+
features: d.features
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
else if (namespace === 'tron') {
|
|
259
|
+
const m = nsMetadata;
|
|
260
|
+
const d = detected;
|
|
261
|
+
m.installed = true;
|
|
262
|
+
m.detectedWallet = { uuid: d.uuid, name: d.name };
|
|
263
|
+
}
|
|
264
|
+
else if (namespace === 'tvm') {
|
|
265
|
+
const m = nsMetadata;
|
|
266
|
+
const d = detected;
|
|
267
|
+
m.installed = true;
|
|
268
|
+
m.detectedWallet = {
|
|
269
|
+
uuid: d.uuid,
|
|
270
|
+
name: d.name,
|
|
271
|
+
icon: d.icon,
|
|
272
|
+
jsBridgeKey: d.jsBridgeKey
|
|
273
|
+
};
|
|
237
274
|
}
|
|
238
275
|
}
|
|
239
276
|
getSession() {
|
|
@@ -242,6 +279,22 @@ export class UniversalWalletConnector {
|
|
|
242
279
|
isReady() {
|
|
243
280
|
return this.detectionComplete;
|
|
244
281
|
}
|
|
282
|
+
// ---- Event subscription ----
|
|
283
|
+
/** Subscribe to a typed event. Returns an unsubscribe function. */
|
|
284
|
+
on(event, listener) {
|
|
285
|
+
return this.eventManager.on(event, listener);
|
|
286
|
+
}
|
|
287
|
+
/** Subscribe once; the listener is removed after the first dispatch. */
|
|
288
|
+
once(event, listener) {
|
|
289
|
+
return this.eventManager.once(event, listener);
|
|
290
|
+
}
|
|
291
|
+
off(event, listener) {
|
|
292
|
+
this.eventManager.off(event, listener);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* @deprecated Prefer `on(eventName, listener)` for targeted updates.
|
|
296
|
+
* Subscribes to the catch-all `change` event.
|
|
297
|
+
*/
|
|
245
298
|
subscribe(listener) {
|
|
246
299
|
return this.eventManager.subscribe(listener);
|
|
247
300
|
}
|
|
@@ -256,20 +309,35 @@ export class UniversalWalletConnector {
|
|
|
256
309
|
getNetworks() {
|
|
257
310
|
return this.connectionService.getNetworks();
|
|
258
311
|
}
|
|
259
|
-
async connect(connectionMode, walletId, networkId) {
|
|
260
|
-
|
|
261
|
-
|
|
312
|
+
async connect(connectionMode, walletId, networkId, options) {
|
|
313
|
+
try {
|
|
314
|
+
await this.connectionService.connect(connectionMode, walletId, networkId, options);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
this.emitError(error, 'connect');
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
262
320
|
}
|
|
263
321
|
getConnectionURI() {
|
|
264
322
|
return this.connectionService.getConnectionURI();
|
|
265
323
|
}
|
|
266
|
-
async disconnect() {
|
|
267
|
-
|
|
268
|
-
|
|
324
|
+
async disconnect(options) {
|
|
325
|
+
try {
|
|
326
|
+
await this.connectionService.disconnect(options);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.emitError(error, 'disconnect');
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
269
332
|
}
|
|
270
|
-
async switchNetwork(networkId) {
|
|
271
|
-
|
|
272
|
-
|
|
333
|
+
async switchNetwork(networkId, options) {
|
|
334
|
+
try {
|
|
335
|
+
await this.networkSwitchService.switchNetwork(networkId, options);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
this.emitError(error, 'switchNetwork');
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
273
341
|
}
|
|
274
342
|
getNetworkSwitchLoadingState() {
|
|
275
343
|
return this.networkSwitchService.getLoadingState();
|
|
@@ -277,9 +345,10 @@ export class UniversalWalletConnector {
|
|
|
277
345
|
/**
|
|
278
346
|
* Sign a message with the connected wallet
|
|
279
347
|
* @param message The message to sign
|
|
348
|
+
* @param options Optional cancellation signal
|
|
280
349
|
* @returns A promise that resolves to the signature
|
|
281
350
|
*/
|
|
282
|
-
async signMessage(message) {
|
|
351
|
+
async signMessage(message, options) {
|
|
283
352
|
const session = this.sessionManager.getSession();
|
|
284
353
|
if (!session.activeWallet) {
|
|
285
354
|
throw new Error('No active wallet');
|
|
@@ -287,23 +356,29 @@ export class UniversalWalletConnector {
|
|
|
287
356
|
if (!session.connectionMode) {
|
|
288
357
|
throw new Error('No active connection');
|
|
289
358
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
359
|
+
try {
|
|
360
|
+
// Get the provider from the active wallet based on connection mode
|
|
361
|
+
let provider;
|
|
362
|
+
if (session.connectionMode === 'injected') {
|
|
363
|
+
provider = this.usingIntegratedBrowser
|
|
364
|
+
? session.activeWallet.integratedBrowserInjectedProvider
|
|
365
|
+
: session.activeWallet.extensionInjectedProvider;
|
|
366
|
+
}
|
|
367
|
+
else if (session.connectionMode === 'walletConnect') {
|
|
368
|
+
provider = session.activeWallet.walletConnectProvider;
|
|
369
|
+
}
|
|
370
|
+
else if (session.connectionMode === 'tonConnect') {
|
|
371
|
+
return await this.signatureService.signMessage(message, undefined, options);
|
|
372
|
+
}
|
|
373
|
+
if (!provider) {
|
|
374
|
+
throw new Error('No provider available for the active wallet');
|
|
375
|
+
}
|
|
376
|
+
return await this.signatureService.signMessage(message, provider, options);
|
|
302
377
|
}
|
|
303
|
-
|
|
304
|
-
|
|
378
|
+
catch (error) {
|
|
379
|
+
this.emitError(error, 'signMessage');
|
|
380
|
+
throw error;
|
|
305
381
|
}
|
|
306
|
-
return await this.signatureService.signMessage(message, provider);
|
|
307
382
|
}
|
|
308
383
|
/**
|
|
309
384
|
* Sign EIP-712 typed structured data (eth_signTypedData_v4).
|
|
@@ -340,9 +415,10 @@ export class UniversalWalletConnector {
|
|
|
340
415
|
/**
|
|
341
416
|
* Send a transaction with the connected wallet
|
|
342
417
|
* @param request The transaction request parameters
|
|
418
|
+
* @param options Optional cancellation signal
|
|
343
419
|
* @returns A promise that resolves to the transaction result
|
|
344
420
|
*/
|
|
345
|
-
async sendTransaction(request) {
|
|
421
|
+
async sendTransaction(request, options) {
|
|
346
422
|
const session = this.sessionManager.getSession();
|
|
347
423
|
if (!session.activeWallet) {
|
|
348
424
|
throw new Error('No active wallet');
|
|
@@ -350,23 +426,29 @@ export class UniversalWalletConnector {
|
|
|
350
426
|
if (!session.connectionMode) {
|
|
351
427
|
throw new Error('No active connection');
|
|
352
428
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
429
|
+
try {
|
|
430
|
+
// Get the provider from the active wallet based on connection mode
|
|
431
|
+
let provider;
|
|
432
|
+
if (session.connectionMode === 'injected') {
|
|
433
|
+
provider = this.usingIntegratedBrowser
|
|
434
|
+
? session.activeWallet.integratedBrowserInjectedProvider
|
|
435
|
+
: session.activeWallet.extensionInjectedProvider;
|
|
436
|
+
}
|
|
437
|
+
else if (session.connectionMode === 'walletConnect') {
|
|
438
|
+
provider = session.activeWallet.walletConnectProvider;
|
|
439
|
+
}
|
|
440
|
+
else if (session.connectionMode === 'tonConnect') {
|
|
441
|
+
return await this.transactionService.sendTransaction(request, undefined, options);
|
|
442
|
+
}
|
|
443
|
+
if (!provider) {
|
|
444
|
+
throw new Error('No provider available for the active wallet');
|
|
445
|
+
}
|
|
446
|
+
return await this.transactionService.sendTransaction(request, provider, options);
|
|
365
447
|
}
|
|
366
|
-
|
|
367
|
-
|
|
448
|
+
catch (error) {
|
|
449
|
+
this.emitError(error, 'sendTransaction');
|
|
450
|
+
throw error;
|
|
368
451
|
}
|
|
369
|
-
return await this.transactionService.sendTransaction(request, provider);
|
|
370
452
|
}
|
|
371
453
|
/**
|
|
372
454
|
* Sign a Solana tx without broadcasting (raw bytes in/out, no @solana/web3.js
|
|
@@ -392,10 +474,17 @@ export class UniversalWalletConnector {
|
|
|
392
474
|
* Fetch wallet capabilities for the connected wallet
|
|
393
475
|
* @param address The wallet address to fetch capabilities for
|
|
394
476
|
* @param activeNetwork Optional network to set as the active network for capabilities
|
|
477
|
+
* @param options Optional cancellation signal
|
|
395
478
|
* @returns A promise that resolves when capabilities are fetched and updated
|
|
396
479
|
*/
|
|
397
|
-
async getWalletCapabilities(address, activeNetwork) {
|
|
398
|
-
|
|
480
|
+
async getWalletCapabilities(address, activeNetwork, options) {
|
|
481
|
+
try {
|
|
482
|
+
return await this.walletCapabilitiesService.getWalletCapabilities(address, activeNetwork, options);
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
this.emitError(error, 'getWalletCapabilities');
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
399
488
|
}
|
|
400
489
|
/**
|
|
401
490
|
* Check if a connection mode is available for a specific wallet
|
|
@@ -439,5 +528,21 @@ export class UniversalWalletConnector {
|
|
|
439
528
|
const session = this.sessionManager.getSession();
|
|
440
529
|
return session.activeWalletCapabilities;
|
|
441
530
|
}
|
|
531
|
+
// ---- private helpers ----
|
|
532
|
+
emitError(error, operation) {
|
|
533
|
+
// Intentional cancellations (AbortSignal.throwIfAborted, fetch abort,
|
|
534
|
+
// DOMException) should NOT surface as wallet errors — the caller already
|
|
535
|
+
// knows it asked to abort. Skip emission so error telemetry stays clean.
|
|
536
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const walletError = error instanceof WalletConnectorError
|
|
540
|
+
? { type: error.type, message: error.message }
|
|
541
|
+
: {
|
|
542
|
+
type: 'unknown',
|
|
543
|
+
message: error instanceof Error ? error.message : String(error)
|
|
544
|
+
};
|
|
545
|
+
this.eventManager.emit('error', { error: walletError, operation });
|
|
546
|
+
}
|
|
442
547
|
}
|
|
443
548
|
//# sourceMappingURL=universal-wallet-connector.js.map
|