@dynamic-labs/ethereum 2.0.0-alpha.3 → 2.0.0-alpha.4

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.
@@ -1,100 +1,396 @@
1
- import { __rest, __awaiter } from '../../_virtual/_tslib.js';
2
- import WalletConnectProvider from '@walletconnect/ethereum-provider';
1
+ import { __awaiter } from '../../_virtual/_tslib.js';
2
+ import Provider from '@walletconnect/universal-provider';
3
+ import EventEmitter from 'eventemitter3';
3
4
  import { createWalletClient, custom } from 'viem';
4
- import { getDeepLink } from '@dynamic-labs/wallet-connector-core';
5
+ import { logger, performPlatformSpecificConnectionMethod, getDeepLink } from '@dynamic-labs/wallet-connector-core';
5
6
  import { getWalletBookWallet } from '@dynamic-labs/wallet-book';
6
- import { isMobile, DynamicError } from '@dynamic-labs/utils';
7
+ import { DynamicError, isMobile } from '@dynamic-labs/utils';
7
8
  import { EthWalletConnector } from '../EthWalletConnector.js';
8
- import { INFURA_ID } from '../constants.js';
9
- import { initClient, setupWalletConnectEventListeners, teardownWalletConnectEventListeners, fetchWalletConnectEVMPublicAddress, signWalletConnectPersonalMessage, killWalletConnectSession } from './client/client.js';
9
+ import { parseIntSafe } from '../utils/parseIntSafe.js';
10
10
 
11
+ const activeAccountKey = (walletName) => `dynamic-wc2-active-account-${walletName}`;
12
+ const sessionTopicKey = (walletName) => `dynamic-wc2-session-topic-${walletName}`;
13
+ const swicthedNetworkKey = (walletName) => `dynamic-wc2-switched-network-${walletName}`;
14
+ const currentChainKey = (walletName) => `dynamic-wc2-current-chain-${walletName}`;
15
+ const ee = new EventEmitter();
11
16
  class WalletConnect extends EthWalletConnector {
12
- constructor(_a) {
13
- var { walletConnectV1Bridge, walletName } = _a, props = __rest(_a, ["walletConnectV1Bridge", "walletName"]);
14
- super(props);
17
+ constructor(opts) {
18
+ var _a;
19
+ super(opts);
15
20
  this.supportedChains = ['EVM', 'ETH'];
16
21
  this.connectedChain = 'EVM';
17
- this.bridge = 'https://bridge.walletconnect.org';
22
+ this.isInitialized = false;
18
23
  this.canConnectViaQrCode = true;
19
24
  this.isWalletConnect = true;
20
- this.switchNetworkOnlyFromWallet = false;
21
- this.name = walletName;
22
- if (walletConnectV1Bridge) {
23
- this.bridge = walletConnectV1Bridge;
25
+ this.preferredChains = [];
26
+ // When trying to switch network for MetaMask, the switch promise gets stuck
27
+ // if the switch got trigged once already, so we need to keep track of that
28
+ this._hasSwitchedNetwork = false;
29
+ this.name = opts.walletName;
30
+ this.projectId = opts.projectId;
31
+ this.deepLinkPreference = opts.deepLinkPreference || 'native';
32
+ this.preferredChains = opts.walletConnectPreferredChains || [];
33
+ this.hasSwitchedNetwork =
34
+ (_a = Boolean(localStorage.getItem(this.swicthedNetworkKey))) !== null && _a !== void 0 ? _a : false;
35
+ const lsCurrentChain = localStorage.getItem(this.currentChainKey);
36
+ this.currentChainId = lsCurrentChain
37
+ ? parseIntSafe(lsCurrentChain)
38
+ : undefined;
39
+ }
40
+ getMappedChains() {
41
+ return (this.evmNetworks
42
+ // Filters out palm that crashes Trust Wallet
43
+ .filter((network) => network.chainId !== 11297108109)
44
+ .map((network) => `eip155:${network.chainId}`));
45
+ }
46
+ getMappedChainsByPreferredOrder() {
47
+ // adding Ethereum to avoid an error connecting if none of the evm networks are supported by the wallet
48
+ const allChains = this.getMappedChains();
49
+ if (!allChains.includes('eip155:1')) {
50
+ allChains.push('eip155:1');
24
51
  }
25
- this.deepLinkPreference = props.deepLinkPreference || 'native';
52
+ const reorderedChains = this.preferredChains.filter((chain) => allChains.includes(chain));
53
+ const remainingChains = allChains.filter((chain) => !this.preferredChains.includes(chain));
54
+ return [...reorderedChains, ...remainingChains];
55
+ }
56
+ initConnection() {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const { provider } = WalletConnect;
59
+ if (!provider) {
60
+ throw new DynamicError('No provider found (init connection)');
61
+ }
62
+ // this means there is already a connection in progress, so don't call connect again
63
+ if (provider === null || provider === void 0 ? void 0 : provider.uri) {
64
+ return;
65
+ }
66
+ const optionalNamespaces = {
67
+ eip155: {
68
+ chains: this.getMappedChainsByPreferredOrder(),
69
+ events: ['chainChanged', 'accountsChanged'],
70
+ methods: [
71
+ 'eth_chainId',
72
+ 'eth_signTypedData',
73
+ 'eth_signTransaction',
74
+ 'eth_sign',
75
+ 'personal_sign',
76
+ 'eth_sendTransaction',
77
+ 'eth_signTypedData_v4',
78
+ 'wallet_switchEthereumChain',
79
+ 'wallet_addEthereumChain',
80
+ ],
81
+ rpcMap: this.evmNetworkRpcMap(),
82
+ },
83
+ };
84
+ provider
85
+ .connect({
86
+ optionalNamespaces,
87
+ })
88
+ .catch((e) => {
89
+ logger.error(e);
90
+ ee.emit('walletconnect_connection_failed', e);
91
+ });
92
+ });
26
93
  }
27
- getClient() {
28
- if (this.client) {
29
- return this.client;
94
+ createInitProviderPromise() {
95
+ return __awaiter(this, void 0, void 0, function* () {
96
+ const provider = yield Provider.init({
97
+ logger: logger.logLevel.toLowerCase() === 'debug' ? 'debug' : undefined,
98
+ projectId: this.projectId,
99
+ });
100
+ WalletConnect.provider = provider;
101
+ this.teardownEventListeners();
102
+ this.setupEventListeners();
103
+ });
104
+ }
105
+ initProvider() {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ const { provider } = WalletConnect;
108
+ if (!provider) {
109
+ if (this.initializePromise === undefined) {
110
+ this.initializePromise = this.createInitProviderPromise();
111
+ }
112
+ yield this.initializePromise;
113
+ }
114
+ });
115
+ }
116
+ refreshSession() {
117
+ var _a, _b, _c, _d, _e;
118
+ if ((_b = (_a = WalletConnect.provider) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.topic) {
119
+ if (localStorage.getItem(this.sessionTopicKey) ===
120
+ ((_d = (_c = WalletConnect.provider) === null || _c === void 0 ? void 0 : _c.session) === null || _d === void 0 ? void 0 : _d.topic)) {
121
+ this.session = WalletConnect.provider.session;
122
+ this.activeAccount = ((_e = localStorage.getItem(this.activeAccountKey)) !== null && _e !== void 0 ? _e : undefined);
123
+ }
30
124
  }
31
- this.client = initClient(this.key, this.bridge, this.clientOptions);
32
- return this.client;
33
125
  }
34
- supportsNetworkSwitching() {
35
- if (this.connectedChain === 'EVM') {
36
- return true;
126
+ init() {
127
+ return __awaiter(this, void 0, void 0, function* () {
128
+ yield this.initProvider();
129
+ yield this.initConnection();
130
+ this.isInitialized = true;
131
+ });
132
+ }
133
+ get sessionTopicKey() {
134
+ return sessionTopicKey(this.key);
135
+ }
136
+ get activeAccountKey() {
137
+ return activeAccountKey(this.key);
138
+ }
139
+ get swicthedNetworkKey() {
140
+ return swicthedNetworkKey(this.key);
141
+ }
142
+ get currentChainKey() {
143
+ return currentChainKey(this.key);
144
+ }
145
+ set currentChainId(value) {
146
+ this._currentChainId = value;
147
+ if (value) {
148
+ localStorage.setItem(this.currentChainKey, value.toString());
37
149
  }
38
150
  else {
39
- const client = this.getClient();
40
- return Boolean(client === null || client === void 0 ? void 0 : client.chainId);
151
+ localStorage.removeItem(this.currentChainKey);
41
152
  }
42
153
  }
154
+ get currentChainId() {
155
+ return this._currentChainId;
156
+ }
157
+ set hasSwitchedNetwork(value) {
158
+ this._hasSwitchedNetwork = value;
159
+ if (value) {
160
+ localStorage.setItem(this.swicthedNetworkKey, value.toString());
161
+ }
162
+ else {
163
+ localStorage.removeItem(this.swicthedNetworkKey);
164
+ }
165
+ }
166
+ get hasSwitchedNetwork() {
167
+ return this._hasSwitchedNetwork;
168
+ }
169
+ supportsNetworkSwitching() {
170
+ return true;
171
+ }
43
172
  setupEventListeners() {
44
- setupWalletConnectEventListeners(this, this.getClient());
173
+ if (!WalletConnect.provider) {
174
+ return;
175
+ }
176
+ WalletConnect.provider.client.on('session_event', ({ params }) => {
177
+ logger.debug('session_event was called', { params });
178
+ if (!params || !params.event) {
179
+ logger.debug('session_event was called without params or params.event');
180
+ return;
181
+ }
182
+ const { name, data } = params.event;
183
+ if (name === 'chainChanged') {
184
+ const chainId = parseIntSafe(data);
185
+ if (chainId === undefined) {
186
+ logger.debug(`received unexpected data for chainChanged: ${data} with type ${typeof data}}`);
187
+ return;
188
+ }
189
+ this.currentChainId = chainId;
190
+ this.emit('chainChange', { chain: String(chainId) });
191
+ // When a user switches network from their wallet, we need the provider to change network
192
+ // such that any future calls to `getNetwork` will return the correct network
193
+ this.switchNetwork({ networkChainId: chainId });
194
+ }
195
+ else if (name === 'accountsChanged') {
196
+ if (!Array.isArray(data)) {
197
+ logger.debug(`received unexpected data for accountsChanged: ${data} with type ${typeof data}}`);
198
+ return;
199
+ }
200
+ // eslint-disable-next-line prefer-destructuring
201
+ const account = data[0].split(':')[2];
202
+ this.setActiveAccount(account);
203
+ }
204
+ });
205
+ WalletConnect.provider.client.on('session_delete', () => __awaiter(this, void 0, void 0, function* () {
206
+ this.endSession();
207
+ this.emit('disconnect');
208
+ }));
45
209
  }
46
210
  teardownEventListeners() {
47
- teardownWalletConnectEventListeners(this.getClient());
211
+ if (!WalletConnect.provider) {
212
+ return;
213
+ }
214
+ WalletConnect.provider.client.removeAllListeners('session_event');
215
+ WalletConnect.provider.client.removeAllListeners('session_delete');
48
216
  }
49
217
  getWalletClient() {
50
- const client = this.getClient();
51
- return client
52
- ? createWalletClient({
53
- transport: custom(new WalletConnectProvider({
54
- connector: client,
55
- infuraId: INFURA_ID,
56
- rpc: this.evmNetworkRpcMap(),
57
- })),
58
- })
59
- : undefined;
218
+ if (!WalletConnect.provider) {
219
+ return;
220
+ }
221
+ return createWalletClient({
222
+ transport: custom(WalletConnect.provider),
223
+ });
60
224
  }
61
225
  fetchPublicAddress(opts) {
226
+ var _a, _b;
62
227
  return __awaiter(this, void 0, void 0, function* () {
63
- return fetchWalletConnectEVMPublicAddress(getWalletBookWallet(this.walletBook, this.key), this.getClient(), this.deepLinkPreference, Object.assign(Object.assign({}, opts), { onConnect: (payload) => {
64
- var _a, _b;
65
- (_a = opts === null || opts === void 0 ? void 0 : opts.onConnect) === null || _a === void 0 ? void 0 : _a.call(opts, payload);
66
- this.connectedChain = payload.params[0].chainId ? 'EVM' : 'SOL';
67
- if ((_b = payload.params[0].accounts) === null || _b === void 0 ? void 0 : _b.length) {
68
- this.emit('accountChange', {
69
- accounts: payload.params[0].accounts,
70
- });
228
+ if (this.activeAccount) {
229
+ return this.activeAccount;
230
+ }
231
+ if (!WalletConnect.provider || !((_a = WalletConnect.provider) === null || _a === void 0 ? void 0 : _a.uri)) {
232
+ logger.debug('No WC2 provider found, re-initializing...');
233
+ yield this.endSession();
234
+ yield this.init();
235
+ // sleep 1 s to wait for connect call to finish
236
+ // the connect call isn't await-ed because it only resolves once
237
+ // the connection is established, but we need to wait for it to
238
+ // finish setting up the connection URI and making it available
239
+ // on the provider
240
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
241
+ if (!WalletConnect.provider || !((_b = WalletConnect.provider) === null || _b === void 0 ? void 0 : _b.uri)) {
242
+ logger.debug('No WC2 provider found, escaping and throwing error');
243
+ throw new DynamicError('No provider found');
244
+ }
245
+ }
246
+ const metadata = getWalletBookWallet(this.walletBook, this.key);
247
+ performPlatformSpecificConnectionMethod(WalletConnect.provider.uri, metadata, {
248
+ onDesktopUri: opts === null || opts === void 0 ? void 0 : opts.onDesktopUri,
249
+ onDisplayUri: opts === null || opts === void 0 ? void 0 : opts.onDisplayUri,
250
+ }, this.deepLinkPreference);
251
+ return new Promise((resolve, reject) => {
252
+ if (!WalletConnect.provider) {
253
+ reject(new DynamicError('No provider found'));
254
+ return;
255
+ }
256
+ ee.on('walletconnect_connection_failed', () => {
257
+ const error = new DynamicError('Connection rejected. Please try again.');
258
+ error.code = 'connection_rejected';
259
+ if (WalletConnect.provider) {
260
+ WalletConnect.provider.uri = undefined;
71
261
  }
72
- } }));
262
+ reject(error);
263
+ });
264
+ WalletConnect.provider.on('connect', ({ session }) => {
265
+ if (!session) {
266
+ reject(new DynamicError('No session found'));
267
+ }
268
+ this.setSession(session);
269
+ this.setActiveAccount(session.namespaces.eip155.accounts[0].split(':')[2]);
270
+ resolve(this.activeAccount);
271
+ });
272
+ });
273
+ });
274
+ }
275
+ /**
276
+ * WalletConnect V2 will fail to send the sign message request if the chainId
277
+ * is not the same as the one in the session. This method will wait for the
278
+ * chainId to change and then retry the sign message request.
279
+ *
280
+ * Otherwise it will just return the result of the sign message request.
281
+ *
282
+ * @param signMessageFn - Function to sign message with provider
283
+ * @param messageToSign - Message to sign
284
+ * @returns
285
+ */
286
+ waitForSignMessage(signMessageFn, messageToSign) {
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ const raceConditionPromise = new Promise((resolve) => {
289
+ // Create listener for chain change event
290
+ this.on('chainChange', () => resolve({ success: false }));
291
+ signMessageFn(messageToSign).then((result) => resolve({ signedMessage: result, success: true }));
292
+ });
293
+ const signedMessageResult = yield raceConditionPromise;
294
+ if (signedMessageResult.success === false) {
295
+ return signMessageFn(messageToSign);
296
+ }
297
+ return signedMessageResult.signedMessage;
73
298
  });
74
299
  }
75
300
  getDeepLink() {
76
301
  var _a;
77
- const wallet = getWalletBookWallet(this.walletBook, this.key);
78
- if (!isMobile() && !((_a = wallet.desktop) === null || _a === void 0 ? void 0 : _a.native)) {
79
- return undefined;
302
+ if (!this.session) {
303
+ return;
80
304
  }
81
- return getDeepLink({
82
- metadata: wallet,
305
+ const metadata = getWalletBookWallet(this.walletBook, this.key);
306
+ const deepLink = getDeepLink({
307
+ metadata,
83
308
  mode: 'regular',
84
309
  preference: this.deepLinkPreference,
85
- uri: this.getClient().uri,
310
+ uri: (_a = WalletConnect.provider) === null || _a === void 0 ? void 0 : _a.uri,
86
311
  });
312
+ if (!deepLink) {
313
+ return;
314
+ }
315
+ // we need to include the session topic here because it helps the wallet
316
+ // auto redirect back to the dapp after signing
317
+ return `${deepLink}?sessionTopic=${this.session.topic}`;
87
318
  }
88
319
  signMessage(messageToSign) {
89
320
  return __awaiter(this, void 0, void 0, function* () {
90
- return signWalletConnectPersonalMessage(messageToSign, getWalletBookWallet(this.walletBook, this.key), this.getClient(), this.deepLinkPreference,
91
- // don't call getPublicClient until we really need to
92
- () => __awaiter(this, void 0, void 0, function* () { return this.getPublicClient(); }));
321
+ if (!this.session) {
322
+ throw new DynamicError('no session');
323
+ }
324
+ const web3Provider = this.getWalletClient();
325
+ if (!web3Provider) {
326
+ throw new DynamicError('No WalletConnect provider found to handle signing');
327
+ }
328
+ const deepLink = this.getDeepLink();
329
+ if (isMobile() && deepLink) {
330
+ window.location.href = deepLink;
331
+ }
332
+ const signMessageFn = (messageToSign) => __awaiter(this, void 0, void 0, function* () {
333
+ const { activeAccount } = this;
334
+ if (!activeAccount) {
335
+ return;
336
+ }
337
+ return web3Provider.signMessage({
338
+ account: activeAccount,
339
+ message: messageToSign,
340
+ });
341
+ });
342
+ const response = yield this.waitForSignMessage(signMessageFn, messageToSign);
343
+ return response;
93
344
  });
94
345
  }
346
+ clearActiveAccount() {
347
+ localStorage.removeItem(this.activeAccountKey);
348
+ this.activeAccount = undefined;
349
+ }
350
+ clearSession() {
351
+ localStorage.removeItem(this.sessionTopicKey);
352
+ this.session = undefined;
353
+ }
354
+ setActiveAccount(account) {
355
+ localStorage.setItem(this.activeAccountKey, account);
356
+ this.activeAccount = account;
357
+ this.emit('accountChange', { accounts: [account] });
358
+ }
359
+ setSession(session) {
360
+ localStorage.setItem(this.sessionTopicKey, session.topic);
361
+ this.session = session;
362
+ }
95
363
  endSession() {
364
+ var _a;
96
365
  return __awaiter(this, void 0, void 0, function* () {
97
- killWalletConnectSession(this.getClient());
366
+ this.clearActiveAccount();
367
+ this.clearSession();
368
+ this.hasSwitchedNetwork = false;
369
+ this.currentChainId = undefined;
370
+ if (!((_a = WalletConnect.provider) === null || _a === void 0 ? void 0 : _a.session)) {
371
+ return;
372
+ }
373
+ try {
374
+ yield WalletConnect.provider.disconnect();
375
+ // We must unset provider on logout so that a new session can be established
376
+ // If we don't then the provider will still have the old session and will hang
377
+ WalletConnect.provider = undefined;
378
+ }
379
+ catch (e) {
380
+ logger.debug(e);
381
+ }
382
+ });
383
+ }
384
+ getNetwork() {
385
+ const _super = Object.create(null, {
386
+ getNetwork: { get: () => super.getNetwork }
387
+ });
388
+ return __awaiter(this, void 0, void 0, function* () {
389
+ if (this.currentChainId) {
390
+ return this.currentChainId;
391
+ }
392
+ yield this.initProvider();
393
+ return _super.getNetwork.call(this);
98
394
  });
99
395
  }
100
396
  providerSwitchNetwork({ network, provider, }) {
@@ -102,45 +398,67 @@ class WalletConnect extends EthWalletConnector {
102
398
  providerSwitchNetwork: { get: () => super.providerSwitchNetwork }
103
399
  });
104
400
  return __awaiter(this, void 0, void 0, function* () {
105
- const client = this.getClient();
106
401
  const currentNetworkId = yield this.getNetwork();
107
402
  if (currentNetworkId && currentNetworkId === network.chainId) {
108
403
  return;
109
404
  }
110
- if (this.switchNetworkOnlyFromWallet !== undefined &&
111
- this.switchNetworkOnlyFromWallet) {
405
+ if (this.switchNetworkOnlyFromWallet) {
112
406
  throw new DynamicError('Network switching is only supported through the wallet');
113
407
  }
114
408
  if (!this.supportsNetworkSwitching()) {
115
409
  throw new DynamicError('Network switching not supported');
116
410
  }
117
- if (!client) {
118
- throw new DynamicError('Client not found');
411
+ if (!provider) {
412
+ throw new DynamicError('Provider not found');
119
413
  }
120
- if (isMobile()) {
121
- const deepLink = getDeepLink({
122
- metadata: getWalletBookWallet(this.walletBook, this.key),
123
- mode: 'regular',
124
- preference: this.deepLinkPreference,
125
- uri: client.uri,
126
- });
127
- window.location.href = deepLink;
128
- }
129
- return _super.providerSwitchNetwork.call(this, { network, provider });
414
+ yield _super.providerSwitchNetwork.call(this, { network, provider });
415
+ this.currentChainId = network.chainId;
416
+ this.hasSwitchedNetwork = true;
417
+ this.emit('chainChange', { chain: String(network.chainId) });
130
418
  });
131
419
  }
132
420
  getConnectedAccounts() {
133
421
  return __awaiter(this, void 0, void 0, function* () {
134
- const client = this.getClient();
135
- if (!client.connected)
422
+ if (this.isInitialized === false) {
423
+ yield this.initProvider();
424
+ this.refreshSession();
425
+ this.isInitialized = true;
426
+ }
427
+ if (!this.activeAccount) {
136
428
  return [];
137
- return client.accounts;
429
+ }
430
+ return [this.activeAccount];
138
431
  });
139
432
  }
140
- getSession() {
433
+ isMetaMask() {
434
+ var _a, _b, _c, _d, _e;
435
+ return ((_e = (_d = (_c = (_b = (_a = this.session) === null || _a === void 0 ? void 0 : _a.peer) === null || _b === void 0 ? void 0 : _b.metadata) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.toLowerCase().startsWith('metamask')) !== null && _e !== void 0 ? _e : false);
436
+ }
437
+ getSupportedNetworks() {
141
438
  var _a;
142
439
  return __awaiter(this, void 0, void 0, function* () {
143
- return (_a = this.client) === null || _a === void 0 ? void 0 : _a.session;
440
+ // MM allows you to switch to any network the first time, even if it's not enabled in MM
441
+ // so we should consider all networks as supported if network switching hasn't been triggered yet
442
+ if (this.isMetaMask() && !this.hasSwitchedNetwork) {
443
+ return this.evmNetworks.map((network) => network.chainId.toString());
444
+ }
445
+ yield this.initProvider();
446
+ this.refreshSession();
447
+ if (!this.session) {
448
+ return [];
449
+ }
450
+ const chains = [];
451
+ // Some wallet (i.e ZenGo) use namespaces.account to list supported chains
452
+ // while others use keys within the namespaces object
453
+ Object.keys(this.session.namespaces).forEach((key) => {
454
+ if (key.startsWith('eip155:')) {
455
+ chains.push(key.split(':')[1]);
456
+ }
457
+ });
458
+ (_a = this.session.namespaces.eip155) === null || _a === void 0 ? void 0 : _a.accounts.forEach((account) => chains.push(account.split(':')[1]));
459
+ return chains.length
460
+ ? chains
461
+ : this.evmNetworks.map((network) => network.chainId.toString());
144
462
  });
145
463
  }
146
464
  }