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