@dynamic-labs/ethereum 0.18.0-RC.2 → 0.18.0-RC.21

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,23 +1,29 @@
1
1
  import { __awaiter } from '../../_virtual/_tslib.js';
2
2
  import Provider from '@walletconnect/universal-provider';
3
3
  import { ethers } from 'ethers';
4
+ import EventEmitter from 'eventemitter3';
4
5
  import { logger, getDeepLink, performPlatformSpecificConnectionMethod, utf8ToHex } from '@dynamic-labs/wallet-connector-core';
5
6
  import { getWalletBookWallet } from '@dynamic-labs/wallet-book';
6
7
  import { DynamicError, isMobile } from '@dynamic-labs/utils';
7
8
  import { EthWalletConnector } from '../EthWalletConnector.js';
9
+ import { parseIntSafe } from '../utils/parseIntSafe.js';
8
10
 
11
+ const activeAccountKey = (walletName) => `dynamic-wc2-active-account-${walletName}`;
9
12
  const sessionTopicKey = (walletName) => `dynamic-wc2-session-topic-${walletName}`;
13
+ const ee = new EventEmitter();
10
14
  class WalletConnectV2 extends EthWalletConnector {
11
15
  constructor(opts) {
12
16
  super(opts);
13
17
  this.supportedChains = ['EVM', 'ETH'];
14
18
  this.connectedChain = 'EVM';
19
+ this.isInitialized = false;
15
20
  this.canConnectViaQrCode = true;
16
21
  this.isWalletConnect = true;
17
22
  this.name = opts.walletName;
18
23
  this.projectId = opts.projectId;
19
24
  }
20
25
  initConnection() {
26
+ var _a;
21
27
  return __awaiter(this, void 0, void 0, function* () {
22
28
  const { provider } = WalletConnectV2;
23
29
  if (!provider) {
@@ -27,44 +33,73 @@ class WalletConnectV2 extends EthWalletConnector {
27
33
  if (provider === null || provider === void 0 ? void 0 : provider.uri) {
28
34
  return;
29
35
  }
30
- yield provider.connect({
36
+ const defaultChain = ((_a = this.evmNetworks) === null || _a === void 0 ? void 0 : _a.length)
37
+ ? this.evmNetworks[0].chainId
38
+ : 1;
39
+ provider
40
+ .connect({
31
41
  namespaces: {
42
+ eip155: {
43
+ chains: [`eip155:${defaultChain}`],
44
+ events: ['chainChanged', 'accountsChanged'],
45
+ methods: ['personal_sign', 'eth_sendTransaction'],
46
+ rpcMap: this.evmNetworkRpcMap(),
47
+ },
48
+ },
49
+ optionalNamespaces: {
32
50
  eip155: {
33
51
  chains: this.evmNetworks
34
- // filtering out Palm for now since it causes Trust to crash
35
- .filter((network) => network.chainId !== 11297108109)
52
+ // Filters out mainnet which is required and palm that crashes Trust Wallet
53
+ .filter((network) => network.chainId !== 11297108109 &&
54
+ network.chainId !== defaultChain)
36
55
  .map((network) => `eip155:${network.chainId}`),
37
- events: ['chainChanged', 'accountsChanged'],
56
+ events: [],
38
57
  methods: [
39
- 'eth_sendTransaction',
58
+ 'eth_signTypedData',
40
59
  'eth_signTransaction',
41
60
  'eth_sign',
42
61
  'personal_sign',
43
- 'eth_signTypedData',
62
+ 'eth_sendTransaction',
63
+ 'eth_signTypedData_v4',
44
64
  ],
45
65
  rpcMap: this.evmNetworkRpcMap(),
46
66
  },
47
67
  },
68
+ })
69
+ .catch((e) => {
70
+ logger.error(e);
71
+ ee.emit('walletconnect_connection_failed');
48
72
  });
49
73
  });
50
74
  }
75
+ createInitProviderPromise() {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ const provider = yield Provider.init({
78
+ logger: logger.logLevel.toLowerCase() === 'debug' ? 'debug' : undefined,
79
+ projectId: this.projectId,
80
+ });
81
+ WalletConnectV2.provider = provider;
82
+ });
83
+ }
51
84
  initProvider() {
52
85
  return __awaiter(this, void 0, void 0, function* () {
53
- let { provider } = WalletConnectV2;
86
+ const { provider } = WalletConnectV2;
54
87
  if (!provider) {
55
- provider = yield Provider.init({
56
- projectId: this.projectId,
57
- });
58
- WalletConnectV2.provider = provider;
88
+ if (this.initializePromise === undefined) {
89
+ this.initializePromise = this.createInitProviderPromise();
90
+ }
91
+ yield this.initializePromise;
59
92
  }
60
93
  });
61
94
  }
62
95
  refreshSession() {
63
- var _a, _b, _c, _d;
96
+ var _a, _b, _c, _d, _e;
64
97
  if ((_b = (_a = WalletConnectV2.provider) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.topic) {
65
98
  if (localStorage.getItem(this.sessionTopicKey) ===
66
99
  ((_d = (_c = WalletConnectV2.provider) === null || _c === void 0 ? void 0 : _c.session) === null || _d === void 0 ? void 0 : _d.topic)) {
67
100
  this.session = WalletConnectV2.provider.session;
101
+ this.activeAccount =
102
+ (_e = localStorage.getItem(this.activeAccountKey)) !== null && _e !== void 0 ? _e : undefined;
68
103
  }
69
104
  }
70
105
  }
@@ -72,11 +107,26 @@ class WalletConnectV2 extends EthWalletConnector {
72
107
  return __awaiter(this, void 0, void 0, function* () {
73
108
  yield this.initProvider();
74
109
  yield this.initConnection();
110
+ this.isInitialized = true;
75
111
  });
76
112
  }
77
113
  get sessionTopicKey() {
78
114
  return sessionTopicKey(this.key);
79
115
  }
116
+ get activeAccountKey() {
117
+ return activeAccountKey(this.key);
118
+ }
119
+ getDeepLink() {
120
+ var _a;
121
+ const wallet = getWalletBookWallet(this.walletBook, this.name);
122
+ if (!isMobile() && !((_a = wallet.desktop) === null || _a === void 0 ? void 0 : _a.native)) {
123
+ return undefined;
124
+ }
125
+ return getDeepLink({
126
+ metadata: wallet,
127
+ mode: 'regular',
128
+ });
129
+ }
80
130
  supportsNetworkSwitching() {
81
131
  return true;
82
132
  }
@@ -92,23 +142,30 @@ class WalletConnectV2 extends EthWalletConnector {
92
142
  }
93
143
  const { name, data } = params.event;
94
144
  if (name === 'chainChanged') {
95
- if (typeof data !== 'string') {
145
+ const chainId = parseIntSafe(data);
146
+ if (chainId === undefined) {
96
147
  logger.debug(`received unexpected data for chainChanged: ${data} with type ${typeof data}}`);
97
148
  return;
98
149
  }
99
- (_a = listeners.onChainChange) === null || _a === void 0 ? void 0 : _a.call(listeners, data);
150
+ (_a = listeners.onChainChange) === null || _a === void 0 ? void 0 : _a.call(listeners, String(chainId));
151
+ // When a user switches network from their wallet, we need the provider to change network
152
+ // such that any future calls to `getNetwork` will return the correct network
153
+ this.switchNetwork({ networkChainId: chainId });
100
154
  }
101
155
  else if (name === 'accountsChanged') {
102
- if (typeof data !== 'string' || !Array.isArray(data)) {
156
+ if (!Array.isArray(data)) {
103
157
  logger.debug(`received unexpected data for accountsChanged: ${data} with type ${typeof data}}`);
104
158
  return;
105
159
  }
106
- (_b = listeners.onAccountChange) === null || _b === void 0 ? void 0 : _b.call(listeners, data);
160
+ // eslint-disable-next-line prefer-destructuring
161
+ const account = data[0].split(':')[2];
162
+ this.setActiveAccount(account);
163
+ (_b = listeners.onAccountChange) === null || _b === void 0 ? void 0 : _b.call(listeners, [account]);
107
164
  }
108
165
  });
109
166
  WalletConnectV2.provider.client.on('session_delete', () => __awaiter(this, void 0, void 0, function* () {
110
167
  var _a;
111
- localStorage.removeItem(this.sessionTopicKey);
168
+ this.endSession();
112
169
  yield ((_a = listeners.onDisconnect) === null || _a === void 0 ? void 0 : _a.call(listeners));
113
170
  }));
114
171
  }
@@ -119,38 +176,39 @@ class WalletConnectV2 extends EthWalletConnector {
119
176
  WalletConnectV2.provider.client.removeAllListeners('session_event');
120
177
  WalletConnectV2.provider.client.removeAllListeners('session_delete');
121
178
  }
122
- getDeepLink() {
123
- var _a, _b;
124
- if (!((_a = WalletConnectV2.provider) === null || _a === void 0 ? void 0 : _a.uri))
125
- return;
126
- const wallet = getWalletBookWallet(this.walletBook, this.name);
127
- if (!isMobile() && !((_b = wallet.desktop) === null || _b === void 0 ? void 0 : _b.native)) {
128
- return undefined;
129
- }
130
- return getDeepLink({
131
- metadata: wallet,
132
- mode: 'regular',
133
- uri: WalletConnectV2.provider.uri,
134
- });
135
- }
136
179
  getWeb3Provider() {
137
180
  if (!WalletConnectV2.provider) {
138
- throw new Error('No provider found');
181
+ return;
139
182
  }
140
183
  return new ethers.providers.Web3Provider(WalletConnectV2.provider, 'any');
141
184
  }
142
185
  fetchPublicAddress(opts) {
186
+ var _a, _b;
143
187
  return __awaiter(this, void 0, void 0, function* () {
144
- if (this.session) {
145
- return this.session.namespaces.eip155.accounts[0].split(':')[2];
188
+ if (this.activeAccount) {
189
+ return this.activeAccount;
146
190
  }
147
- if (!WalletConnectV2.provider) {
148
- throw new DynamicError('No provider found');
149
- }
150
- if (!WalletConnectV2.provider.uri) {
151
- throw new DynamicError('No uri found');
191
+ if (!WalletConnectV2.provider || !((_a = WalletConnectV2.provider) === null || _a === void 0 ? void 0 : _a.uri)) {
192
+ logger.debug('No WC2 provider found, re-initializing...');
193
+ yield this.endSession();
194
+ yield this.init();
195
+ // sleep 1 s to wait for connect call to finish
196
+ // the connect call isn't await-ed because it only resolves once
197
+ // the connection is established, but we need to wait for it to
198
+ // finish setting up the connection URI and making it available
199
+ // on the provider
200
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
201
+ if (!WalletConnectV2.provider || !((_b = WalletConnectV2.provider) === null || _b === void 0 ? void 0 : _b.uri)) {
202
+ logger.debug('No WC2 provider found, escaping and throwing error');
203
+ throw new DynamicError('No provider found');
204
+ }
152
205
  }
153
206
  const metadata = getWalletBookWallet(this.walletBook, this.name);
207
+ if (isMobile()) {
208
+ // eslint-disable-next-line max-len
209
+ // see: https://dynamiclabsgroup.slack.com/archives/C04J69TS3JR/p1689918262752199?thread_ts=1689633771.128409&cid=C04J69TS3JR
210
+ localStorage.setItem('WALLETCONNECT_DEEPLINK_CHOICE', JSON.stringify({ href: this.getDeepLink(), name: metadata.name }));
211
+ }
154
212
  performPlatformSpecificConnectionMethod(WalletConnectV2.provider.uri, metadata, {
155
213
  onDesktopUri: opts === null || opts === void 0 ? void 0 : opts.onDesktopUri,
156
214
  onDisplayUri: opts === null || opts === void 0 ? void 0 : opts.onDisplayUri,
@@ -160,18 +218,56 @@ class WalletConnectV2 extends EthWalletConnector {
160
218
  reject(new DynamicError('No provider found'));
161
219
  return;
162
220
  }
221
+ ee.on('walletconnect_connection_failed', () => {
222
+ const error = new DynamicError('Connection rejected. Please try again.');
223
+ error.code = 'connection_rejected';
224
+ if (WalletConnectV2.provider) {
225
+ WalletConnectV2.provider.uri = undefined;
226
+ }
227
+ reject(error);
228
+ });
163
229
  WalletConnectV2.provider.on('connect', ({ session }) => {
164
230
  if (!session) {
165
231
  reject(new DynamicError('No session found'));
166
232
  }
167
- this.session = session;
168
- localStorage.setItem(this.sessionTopicKey, session.topic);
169
- resolve(this.session.namespaces.eip155.accounts[0].split(':')[2]);
233
+ this.setSession(session);
234
+ this.setActiveAccount(session.namespaces.eip155.accounts[0].split(':')[2]);
235
+ resolve(this.activeAccount);
236
+ });
237
+ });
238
+ });
239
+ }
240
+ /**
241
+ * WalletConnect V2 will fail to send the sign message request if the chainId
242
+ * is not the same as the one in the session. This method will wait for the
243
+ * chainId to change and then retry the sign message request.
244
+ *
245
+ * Otherwise it will just return the result of the sign message request.
246
+ *
247
+ * @param signMessageFn - Function to sign message with provider
248
+ * @param messageToSign - Message to sign
249
+ * @returns
250
+ */
251
+ waitForSignMessage(signMessageFn, messageToSign) {
252
+ return __awaiter(this, void 0, void 0, function* () {
253
+ const raceConditionPromise = new Promise((resolve) => {
254
+ // Create listener for chain change event
255
+ this.setupEventListeners({
256
+ onChainChange: () => __awaiter(this, void 0, void 0, function* () {
257
+ resolve({ success: false });
258
+ }),
170
259
  });
260
+ signMessageFn(messageToSign).then((result) => resolve({ signedMessage: result, success: true }));
171
261
  });
262
+ const signedMessageResult = yield raceConditionPromise;
263
+ if (signedMessageResult.success === false) {
264
+ return signMessageFn(messageToSign);
265
+ }
266
+ return signedMessageResult.signedMessage;
172
267
  });
173
268
  }
174
269
  signMessage(messageToSign) {
270
+ var _a, _b;
175
271
  return __awaiter(this, void 0, void 0, function* () {
176
272
  if (!this.session) {
177
273
  throw new DynamicError('no session');
@@ -181,39 +277,69 @@ class WalletConnectV2 extends EthWalletConnector {
181
277
  }
182
278
  const metadata = getWalletBookWallet(this.walletBook, this.name);
183
279
  if (isMobile()) {
184
- // for sign message, no uri is needed because the uri encodes connection details,
185
- // and at this point we are already connected
186
- const deepLink = getDeepLink({ metadata, mode: 'regular' });
280
+ const deepLink = ((_a = metadata['mobile']) === null || _a === void 0 ? void 0 : _a.native) || ((_b = metadata['mobile']) === null || _b === void 0 ? void 0 : _b.universal) || '';
187
281
  window.location.href = deepLink;
188
282
  }
189
- // eslint-disable-next-line prefer-destructuring
190
- const address = this.session.namespaces.eip155.accounts[0].split(':')[2];
191
283
  const web3Provider = this.getWeb3Provider();
192
- return web3Provider.send('personal_sign', [
193
- utf8ToHex(messageToSign, true),
194
- address,
195
- ]);
284
+ if (!web3Provider) {
285
+ throw new DynamicError('No web3 provider found');
286
+ }
287
+ const signMessageFn = (messageToSign) => __awaiter(this, void 0, void 0, function* () {
288
+ return web3Provider.send('personal_sign', [
289
+ utf8ToHex(messageToSign, true),
290
+ this.activeAccount,
291
+ ]);
292
+ });
293
+ const response = yield this.waitForSignMessage(signMessageFn, messageToSign);
294
+ return response;
196
295
  });
197
296
  }
297
+ clearActiveAccount() {
298
+ localStorage.removeItem(this.activeAccountKey);
299
+ this.activeAccount = undefined;
300
+ }
301
+ clearSession() {
302
+ localStorage.removeItem(this.sessionTopicKey);
303
+ this.session = undefined;
304
+ }
305
+ setActiveAccount(account) {
306
+ localStorage.setItem(this.activeAccountKey, account);
307
+ this.activeAccount = account;
308
+ }
309
+ setSession(session) {
310
+ localStorage.setItem(this.sessionTopicKey, session.topic);
311
+ this.session = session;
312
+ }
198
313
  endSession() {
314
+ var _a;
199
315
  return __awaiter(this, void 0, void 0, function* () {
200
- if (!this.session) {
201
- return;
202
- }
203
- if (!WalletConnectV2.provider) {
316
+ localStorage.removeItem('WALLETCONNECT_DEEPLINK_CHOICE');
317
+ this.clearActiveAccount();
318
+ this.clearSession();
319
+ if (!((_a = WalletConnectV2.provider) === null || _a === void 0 ? void 0 : _a.session)) {
204
320
  return;
205
321
  }
206
322
  try {
207
323
  yield WalletConnectV2.provider.disconnect();
208
- WalletConnectV2.provider.uri = undefined;
209
- localStorage.removeItem(this.sessionTopicKey);
210
- this.session = undefined;
324
+ // We must unset provider on logout so that a new session can be established
325
+ // If we don't then the provider will still have the old session and will hang
326
+ WalletConnectV2.provider = undefined;
327
+ this.initializePromise = undefined; // Needed so we can re-initiliza the provider
211
328
  }
212
329
  catch (e) {
213
330
  logger.debug(e);
214
331
  }
215
332
  });
216
333
  }
334
+ getNetwork() {
335
+ const _super = Object.create(null, {
336
+ getNetwork: { get: () => super.getNetwork }
337
+ });
338
+ return __awaiter(this, void 0, void 0, function* () {
339
+ yield this.initProvider();
340
+ return _super.getNetwork.call(this);
341
+ });
342
+ }
217
343
  providerSwitchNetwork({ network, provider, }) {
218
344
  const _super = Object.create(null, {
219
345
  providerSwitchNetwork: { get: () => super.providerSwitchNetwork }
@@ -236,13 +362,33 @@ class WalletConnectV2 extends EthWalletConnector {
236
362
  });
237
363
  }
238
364
  getConnectedAccounts() {
365
+ return __awaiter(this, void 0, void 0, function* () {
366
+ yield this.initProvider();
367
+ this.refreshSession();
368
+ this.isInitialized = true;
369
+ if (!this.activeAccount) {
370
+ return [];
371
+ }
372
+ return [this.activeAccount];
373
+ });
374
+ }
375
+ getSupportedNetworks() {
239
376
  return __awaiter(this, void 0, void 0, function* () {
240
377
  yield this.initProvider();
241
378
  this.refreshSession();
242
379
  if (!this.session) {
243
380
  return [];
244
381
  }
245
- return [this.session.namespaces.eip155.accounts[0].split(':')[2]];
382
+ const chains = [];
383
+ // Some wallet (i.e ZenGo) use namespaces.account to list supported chains
384
+ // while others use keys within the namespaces object
385
+ Object.keys(this.session.namespaces).forEach((key) => {
386
+ if (key.startsWith('eip155:')) {
387
+ chains.push(key.split(':')[1]);
388
+ }
389
+ });
390
+ this.session.namespaces.eip155.accounts.forEach((account) => chains.push(account.split(':')[1]));
391
+ return chains.length ? chains : undefined;
246
392
  });
247
393
  }
248
394
  }