@hashgraph/hedera-wallet-connect 1.3.6-canary.67ec2cf.0 → 1.3.7-canary.689bd9f.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  import { Signer, AccountBalance, AccountId, AccountInfo, Executable, Key, LedgerId, SignerSignature, Transaction, TransactionRecord } from '@hashgraph/sdk';
2
- import type { ISignClient } from '@walletconnect/types';
2
+ import type { CoreTypes, ISignClient } from '@walletconnect/types';
3
3
  export declare class DAppSigner implements Signer {
4
4
  private readonly accountId;
5
5
  private readonly signClient;
@@ -24,6 +24,7 @@ export declare class DAppSigner implements Signer {
24
24
  getAccountBalance(): Promise<AccountBalance>;
25
25
  getAccountInfo(): Promise<AccountInfo>;
26
26
  getAccountRecords(): Promise<TransactionRecord[]>;
27
+ getMetadata(): CoreTypes.Metadata;
27
28
  sign(data: Uint8Array[], signOptions?: Record<string, any>): Promise<SignerSignature[]>;
28
29
  checkTransaction<T extends Transaction>(transaction: T): Promise<T>;
29
30
  populateTransaction<T extends Transaction>(transaction: T): Promise<T>;
@@ -49,8 +49,9 @@ export class DAppSigner {
49
49
  return allNodes.slice(0, numberOfNodes);
50
50
  }
51
51
  request(request) {
52
- if (this.extensionId)
52
+ if (this.extensionId) {
53
53
  extensionOpen(this.extensionId);
54
+ }
54
55
  return this.signClient.request({
55
56
  topic: this.topic,
56
57
  request,
@@ -81,6 +82,9 @@ export class DAppSigner {
81
82
  getAccountRecords() {
82
83
  return this.call(new AccountRecordsQuery().setAccountId(this.accountId));
83
84
  }
85
+ getMetadata() {
86
+ return this.signClient.metadata;
87
+ }
84
88
  async sign(data, signOptions) {
85
89
  const { signatureMap } = await this.request({
86
90
  method: HederaJsonRpcMethod.SignMessage,
@@ -7,6 +7,7 @@ import { DAppSigner } from './DAppSigner';
7
7
  export * from './DAppSigner';
8
8
  type BaseLogger = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'fatal';
9
9
  export declare class DAppConnector {
10
+ private logger;
10
11
  dAppMetadata: SignClientTypes.Metadata;
11
12
  network: LedgerId;
12
13
  projectId: string;
@@ -27,8 +28,14 @@ export declare class DAppConnector {
27
28
  * @param methods - Array of supported methods for the DApp (optional).
28
29
  * @param events - Array of supported events for the DApp (optional).
29
30
  * @param chains - Array of supported chains for the DApp (optional).
31
+ * @param logLevel - Logging level for the DAppConnector (optional).
30
32
  */
31
- constructor(metadata: SignClientTypes.Metadata, network: LedgerId, projectId: string, methods?: string[], events?: string[], chains?: string[]);
33
+ constructor(metadata: SignClientTypes.Metadata, network: LedgerId, projectId: string, methods?: string[], events?: string[], chains?: string[], logLevel?: 'error' | 'warn' | 'info' | 'debug');
34
+ /**
35
+ * Sets the logging level for the DAppConnector
36
+ * @param level - The logging level to set
37
+ */
38
+ setLogLevel(level: 'error' | 'warn' | 'info' | 'debug'): void;
32
39
  /**
33
40
  * Initializes the DAppConnector instance.
34
41
  * @param logger - `BaseLogger` for logging purposes (optional).
@@ -71,6 +78,16 @@ export declare class DAppConnector {
71
78
  * @returns A Promise that resolves when the connection process is complete.
72
79
  */
73
80
  connectExtension(extensionId: string, pairingTopic?: string): Promise<SessionTypes.Struct>;
81
+ /**
82
+ * Validates the session by checking if the session exists.
83
+ * @param topic - The topic of the session to validate.
84
+ * @returns {boolean} - True if the session exists, false otherwise.
85
+ */
86
+ private validateSession;
87
+ /**
88
+ * Validates the session and refreshes the signers by removing the invalid ones.
89
+ */
90
+ private validateAndRefreshSigners;
74
91
  /**
75
92
  * Initiates the WallecConnect connection if the wallet in iframe mode is detected.
76
93
  */
@@ -81,7 +98,7 @@ export declare class DAppConnector {
81
98
  * @param topic - The topic of the session to disconnect.
82
99
  * @returns A Promise that resolves when the session is disconnected.
83
100
  */
84
- disconnect(topic: string): Promise<void>;
101
+ disconnect(topic: string): Promise<boolean>;
85
102
  /**
86
103
  * Disconnects all active sessions and pairings.
87
104
  *
@@ -127,7 +144,7 @@ export declare class DAppConnector {
127
144
  * @example
128
145
  * ```ts
129
146
  * const params = {
130
- * signerAccountId: '0.0.12345',
147
+ * signerAccountId: 'hedera:testnet:0.0.12345',
131
148
  * message: 'Hello World!'
132
149
  * }
133
150
  *
@@ -22,6 +22,7 @@ import QRCodeModal from '@walletconnect/qrcode-modal';
22
22
  import { WalletConnectModal } from '@walletconnect/modal';
23
23
  import SignClient from '@walletconnect/sign-client';
24
24
  import { getSdkError } from '@walletconnect/utils';
25
+ import { DefaultLogger } from '../shared/logger';
25
26
  import { HederaJsonRpcMethod, accountAndLedgerFromSession, networkNamespaces, extensionConnect, findExtensions, } from '../shared';
26
27
  import { DAppSigner } from './DAppSigner';
27
28
  export * from './DAppSigner';
@@ -34,8 +35,9 @@ export class DAppConnector {
34
35
  * @param methods - Array of supported methods for the DApp (optional).
35
36
  * @param events - Array of supported events for the DApp (optional).
36
37
  * @param chains - Array of supported chains for the DApp (optional).
38
+ * @param logLevel - Logging level for the DAppConnector (optional).
37
39
  */
38
- constructor(metadata, network, projectId, methods, events, chains) {
40
+ constructor(metadata, network, projectId, methods, events, chains, logLevel = 'debug') {
39
41
  this.network = LedgerId.TESTNET;
40
42
  this.supportedMethods = [];
41
43
  this.supportedEvents = [];
@@ -62,6 +64,7 @@ export class DAppConnector {
62
64
  }
63
65
  });
64
66
  };
67
+ this.logger = new DefaultLogger(logLevel);
65
68
  this.dAppMetadata = metadata;
66
69
  this.network = network;
67
70
  this.projectId = projectId;
@@ -77,6 +80,15 @@ export class DAppConnector {
77
80
  this.extensions.push(Object.assign(Object.assign({}, metadata), { available: true, availableInIframe: isIframe }));
78
81
  });
79
82
  }
83
+ /**
84
+ * Sets the logging level for the DAppConnector
85
+ * @param level - The logging level to set
86
+ */
87
+ setLogLevel(level) {
88
+ if (this.logger instanceof DefaultLogger) {
89
+ this.logger.setLogLevel(level);
90
+ }
91
+ }
80
92
  /**
81
93
  * Initializes the DAppConnector instance.
82
94
  * @param logger - `BaseLogger` for logging purposes (optional).
@@ -100,7 +112,8 @@ export class DAppConnector {
100
112
  this.checkIframeConnect();
101
113
  this.walletConnectClient.on('session_event', (event) => {
102
114
  // Handle session events, such as "chainChanged", "accountsChanged", etc.
103
- console.log(event);
115
+ this.logger.debug('Session event received:', event);
116
+ this.validateAndRefreshSigners();
104
117
  });
105
118
  this.walletConnectClient.on('session_update', ({ topic, params }) => {
106
119
  // Handle session update
@@ -109,33 +122,37 @@ export class DAppConnector {
109
122
  // Overwrite the `namespaces` of the existing session with the incoming one.
110
123
  const updatedSession = Object.assign(Object.assign({}, _session), { namespaces });
111
124
  // Integrate the updated session state into your dapp state.
112
- console.log(updatedSession);
125
+ this.logger.info('Session updated:', updatedSession);
126
+ this.signers = this.signers.filter((signer) => signer.topic !== topic);
127
+ this.signers.push(...this.createSigners(updatedSession));
113
128
  });
114
129
  this.walletConnectClient.on('session_delete', (pairing) => {
115
- console.log(pairing);
130
+ this.logger.info('Session deleted:', pairing);
116
131
  this.signers = this.signers.filter((signer) => signer.topic !== pairing.topic);
117
132
  // Session was deleted -> reset the dapp state, clean up from user session, etc.
118
133
  try {
119
134
  this.disconnect(pairing.topic);
120
135
  }
121
136
  catch (e) {
122
- console.error(e);
137
+ this.logger.error('Error disconnecting session:', e);
123
138
  }
124
- console.log('Dapp: Session deleted by wallet!');
139
+ this.logger.info('Session deleted by wallet');
125
140
  });
126
141
  this.walletConnectClient.core.pairing.events.on('pairing_delete', (pairing) => {
127
- console.log(pairing);
142
+ this.logger.info('Pairing deleted:', pairing);
128
143
  this.signers = this.signers.filter((signer) => signer.topic !== pairing.topic);
129
144
  try {
130
145
  this.disconnect(pairing.topic);
131
146
  }
132
147
  catch (e) {
133
- console.error(e);
148
+ this.logger.error('Error disconnecting pairing:', e);
134
149
  }
135
- console.log(`Dapp: Pairing deleted by wallet!`);
136
- // clean up after the pairing for `topic` was deleted.
150
+ this.logger.info('Pairing deleted by wallet');
137
151
  });
138
152
  }
153
+ catch (e) {
154
+ this.logger.error('Error initializing DAppConnector:', e);
155
+ }
139
156
  finally {
140
157
  this.isInitializing = false;
141
158
  }
@@ -148,6 +165,9 @@ export class DAppConnector {
148
165
  * @throws {Error} - If no signer is found for the provided account ID.
149
166
  */
150
167
  getSigner(accountId) {
168
+ if (this.isInitializing) {
169
+ throw new Error('DAppConnector is not initialized yet. Try again later.');
170
+ }
151
171
  const signer = this.signers.find((signer) => signer.getAccountId().equals(accountId));
152
172
  if (!signer)
153
173
  throw new Error('Signer is not found for this accountId');
@@ -231,6 +251,32 @@ export class DAppConnector {
231
251
  extensionConnect(extension.id, extension.availableInIframe, uri);
232
252
  }, pairingTopic, extension.availableInIframe ? undefined : extensionId);
233
253
  }
254
+ /**
255
+ * Validates the session by checking if the session exists.
256
+ * @param topic - The topic of the session to validate.
257
+ * @returns {boolean} - True if the session exists, false otherwise.
258
+ */
259
+ validateSession(topic) {
260
+ try {
261
+ if (!this.walletConnectClient) {
262
+ return false;
263
+ }
264
+ const session = this.walletConnectClient.session.get(topic);
265
+ if (!session) {
266
+ return false;
267
+ }
268
+ return true;
269
+ }
270
+ catch (_a) {
271
+ return false;
272
+ }
273
+ }
274
+ /**
275
+ * Validates the session and refreshes the signers by removing the invalid ones.
276
+ */
277
+ validateAndRefreshSigners() {
278
+ this.signers = this.signers.filter((signer) => this.validateSession(signer.topic));
279
+ }
234
280
  /**
235
281
  * Initiates the WallecConnect connection if the wallet in iframe mode is detected.
236
282
  */
@@ -248,10 +294,20 @@ export class DAppConnector {
248
294
  * @returns A Promise that resolves when the session is disconnected.
249
295
  */
250
296
  async disconnect(topic) {
251
- await this.walletConnectClient.disconnect({
252
- topic: topic,
253
- reason: getSdkError('USER_DISCONNECTED'),
254
- });
297
+ try {
298
+ if (!this.walletConnectClient) {
299
+ throw new Error('WalletConnect is not initialized');
300
+ }
301
+ await this.walletConnectClient.disconnect({
302
+ topic: topic,
303
+ reason: getSdkError('USER_DISCONNECTED'),
304
+ });
305
+ return true;
306
+ }
307
+ catch (e) {
308
+ this.logger.error('Either the session was already disconnected or the topic is invalid', e);
309
+ return false;
310
+ }
255
311
  }
256
312
  /**
257
313
  * Disconnects all active sessions and pairings.
@@ -271,7 +327,7 @@ export class DAppConnector {
271
327
  const disconnectionPromises = [];
272
328
  // disconnect sessions
273
329
  for (const session of this.walletConnectClient.session.getAll()) {
274
- console.log(`Disconnecting from session: ${session}`);
330
+ this.logger.info(`Disconnecting from session: ${session}`);
275
331
  const promise = this.disconnect(session.topic);
276
332
  disconnectionPromises.push(promise);
277
333
  }
@@ -292,7 +348,34 @@ export class DAppConnector {
292
348
  });
293
349
  }
294
350
  async onSessionConnected(session) {
295
- this.signers.push(...this.createSigners(session));
351
+ const newSigners = this.createSigners(session);
352
+ // Filter out any existing signers with duplicate AccountIds
353
+ for (const newSigner of newSigners) {
354
+ // We check if any signers have the same account, extension + metadata name.
355
+ const existingSigners = this.signers.filter((currentSigner) => {
356
+ var _a, _b;
357
+ const matchingAccountId = ((_a = currentSigner === null || currentSigner === void 0 ? void 0 : currentSigner.getAccountId()) === null || _a === void 0 ? void 0 : _a.toString()) === ((_b = newSigner === null || newSigner === void 0 ? void 0 : newSigner.getAccountId()) === null || _b === void 0 ? void 0 : _b.toString());
358
+ const matchingExtensionId = newSigner.extensionId === currentSigner.extensionId;
359
+ const newSignerMetadata = newSigner.getMetadata();
360
+ const existingSignerMetadata = currentSigner.getMetadata();
361
+ const metadataNameMatch = (newSignerMetadata === null || newSignerMetadata === void 0 ? void 0 : newSignerMetadata.name) === (existingSignerMetadata === null || existingSignerMetadata === void 0 ? void 0 : existingSignerMetadata.name);
362
+ if (currentSigner.topic === newSigner.topic) {
363
+ this.logger.error('The topic was already connected. This is a weird error. Please report it.', newSigner.getAccountId().toString());
364
+ }
365
+ return matchingAccountId && matchingExtensionId && metadataNameMatch;
366
+ });
367
+ // Any dupes get disconnected + removed from the signers array.
368
+ for (const existingSigner of existingSigners) {
369
+ this.logger.debug(`Disconnecting duplicate signer for account ${existingSigner.getAccountId().toString()}`);
370
+ await this.disconnect(existingSigner.topic);
371
+ this.signers = this.signers.filter((s) => s.topic !== existingSigner.topic);
372
+ }
373
+ }
374
+ // Add new signers after all duplicates have been cleaned up
375
+ this.signers.push(...newSigners);
376
+ this.logger.debug(`Current signers after connection: ${this.signers
377
+ .map((s) => `${s.getAccountId().toString()}:${s.topic}`)
378
+ .join(', ')}`);
296
379
  }
297
380
  async connectURI(pairingTopic) {
298
381
  if (!this.walletConnectClient) {
@@ -304,10 +387,25 @@ export class DAppConnector {
304
387
  });
305
388
  }
306
389
  async request({ method, params, }) {
307
- const signer = this.signers[this.signers.length - 1];
390
+ var _a, _b, _c;
391
+ let signer;
392
+ this.logger.debug(`Requesting method: ${method} with params: ${JSON.stringify(params)}`);
393
+ if (params === null || params === void 0 ? void 0 : params.signerAccountId) {
394
+ // Extract the actual account ID from the hedera:<network>:<address> format
395
+ const actualAccountId = (_b = (_a = params === null || params === void 0 ? void 0 : params.signerAccountId) === null || _a === void 0 ? void 0 : _a.split(':')) === null || _b === void 0 ? void 0 : _b.pop();
396
+ signer = this.signers.find((s) => { var _a; return ((_a = s === null || s === void 0 ? void 0 : s.getAccountId()) === null || _a === void 0 ? void 0 : _a.toString()) === actualAccountId; });
397
+ this.logger.debug(`Found signer: ${(_c = signer === null || signer === void 0 ? void 0 : signer.getAccountId()) === null || _c === void 0 ? void 0 : _c.toString()}`);
398
+ if (!signer) {
399
+ throw new Error(`Signer not found for account ID: ${params === null || params === void 0 ? void 0 : params.signerAccountId}. Did you use the correct format? e.g hedera:<network>:<address> `);
400
+ }
401
+ }
402
+ else {
403
+ signer = this.signers[this.signers.length - 1];
404
+ }
308
405
  if (!signer) {
309
406
  throw new Error('There is no active session. Connect to the wallet at first.');
310
407
  }
408
+ this.logger.debug(`Using signer: ${signer.getAccountId().toString()}: ${signer.topic} - about to request.`);
311
409
  return await signer.request({
312
410
  method: method,
313
411
  params: params,
@@ -357,7 +455,7 @@ export class DAppConnector {
357
455
  * @example
358
456
  * ```ts
359
457
  * const params = {
360
- * signerAccountId: '0.0.12345',
458
+ * signerAccountId: 'hedera:testnet:0.0.12345',
361
459
  * message: 'Hello World!'
362
460
  * }
363
461
  *
@@ -0,0 +1,15 @@
1
+ export interface ILogger {
2
+ error(message: string, ...args: any[]): void;
3
+ warn(message: string, ...args: any[]): void;
4
+ info(message: string, ...args: any[]): void;
5
+ debug(message: string, ...args: any[]): void;
6
+ }
7
+ export declare class DefaultLogger implements ILogger {
8
+ private logLevel;
9
+ constructor(logLevel?: 'error' | 'warn' | 'info' | 'debug');
10
+ setLogLevel(level: 'error' | 'warn' | 'info' | 'debug'): void;
11
+ error(message: string, ...args: any[]): void;
12
+ warn(message: string, ...args: any[]): void;
13
+ info(message: string, ...args: any[]): void;
14
+ debug(message: string, ...args: any[]): void;
15
+ }
@@ -0,0 +1,29 @@
1
+ export class DefaultLogger {
2
+ constructor(logLevel = 'info') {
3
+ this.logLevel = 'info';
4
+ this.logLevel = logLevel;
5
+ }
6
+ setLogLevel(level) {
7
+ this.logLevel = level;
8
+ }
9
+ error(message, ...args) {
10
+ if (['error', 'warn', 'info', 'debug'].includes(this.logLevel)) {
11
+ console.error(`[ERROR] ${message}`, ...args);
12
+ }
13
+ }
14
+ warn(message, ...args) {
15
+ if (['warn', 'info', 'debug'].includes(this.logLevel)) {
16
+ console.warn(`[WARN] ${message}`, ...args);
17
+ }
18
+ }
19
+ info(message, ...args) {
20
+ if (['info', 'debug'].includes(this.logLevel)) {
21
+ console.info(`[INFO] ${message}`, ...args);
22
+ }
23
+ }
24
+ debug(message, ...args) {
25
+ if (this.logLevel === 'debug') {
26
+ console.debug(`[DEBUG] ${message}`, ...args);
27
+ }
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashgraph/hedera-wallet-connect",
3
- "version": "1.3.6-canary.67ec2cf.0",
3
+ "version": "1.3.7-canary.689bd9f.0",
4
4
  "description": "A library to facilitate integrating Hedera with WalletConnect",
5
5
  "repository": {
6
6
  "type": "git",