@portal-hq/web 3.10.0 → 3.11.0-alpha.0

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 CHANGED
@@ -8,6 +8,7 @@ Welcome to the `@portal-hq/web` SDK, the official TypeScript library for integra
8
8
  - **PortalContext Provider**: Expose the Portal instance to your React component tree.
9
9
  - **usePortal Hook**: Consume the Portal instance within your child components for easy access and interaction.
10
10
  - **Types and Enums**: Utilize helper types and enums for efficient development with the Portal SDK.
11
+ - **Configurable Log Level**: Control SDK logging via `logLevel` and `logger` options and the `setLogLevel` / `getLogLevel` methods.
11
12
 
12
13
  ## Documentation
13
14
 
@@ -45,6 +45,7 @@ const security_1 = __importDefault(require("./integrations/security"));
45
45
  const delegations_1 = __importDefault(require("./integrations/delegations"));
46
46
  const passkeys_1 = __importDefault(require("./passkeys"));
47
47
  const evmAccountType_1 = require("./namespaces/evmAccountType");
48
+ const logger_1 = require("./logger");
48
49
  class Portal {
49
50
  get ready() {
50
51
  return this.mpc.ready;
@@ -53,9 +54,10 @@ class Portal {
53
54
  // Required
54
55
  rpcConfig,
55
56
  // Optional
56
- apiKey, authToken, authUrl, autoApprove = false, gdrive, passkey, host = 'web.portalhq.io', mpcVersion = 'v6', mpcHost = 'mpc-client.portalhq.io', featureFlags = {}, chainId, }) {
57
+ apiKey, authToken, authUrl, autoApprove = false, gdrive, passkey, host = 'web.portalhq.io', mpcVersion = 'v6', mpcHost = 'mpc-client.portalhq.io', featureFlags = {}, chainId, logLevel = 'none', logger = console, }) {
57
58
  this.errorCallbacks = [];
58
59
  this.readyCallbacks = [];
60
+ this.logger = console;
59
61
  this.sendEth = ({ chainId, to, value, }) => __awaiter(this, void 0, void 0, function* () {
60
62
  return this.provider.request({
61
63
  chainId,
@@ -78,6 +80,10 @@ class Portal {
78
80
  this.mpcHost = mpcHost;
79
81
  this.mpcVersion = mpcVersion;
80
82
  this.featureFlags = featureFlags;
83
+ this.logger = logger;
84
+ // SDK logging is global: one shared logger for the entire runtime. The last Portal
85
+ // created or the last setLogLevel() call wins for all instances.
86
+ logger_1.sdkLogger.configure(logLevel, logger);
81
87
  if (gdrive) {
82
88
  this.gDriveConfig = gdrive;
83
89
  }
@@ -97,6 +103,27 @@ class Portal {
97
103
  });
98
104
  this.evmAccountType = new evmAccountType_1.EvmAccountType({ mpc: this.mpc });
99
105
  }
106
+ /***********************************
107
+ * Logging
108
+ ***********************************/
109
+ /**
110
+ * Set the SDK log level. Use this to enable or disable SDK logging at runtime.
111
+ * Note: Logging configuration is global for the entire SDK runtime. Changing the
112
+ * level on one Portal instance affects all Portal instances and all SDK log output.
113
+ * @example
114
+ * portal.setLogLevel('debug') // Enable verbose logging
115
+ * portal.setLogLevel('none') // Disable all logs
116
+ */
117
+ setLogLevel(level) {
118
+ logger_1.sdkLogger.configure(level, this.logger);
119
+ }
120
+ /**
121
+ * Get the current SDK log level.
122
+ * Note: This returns the global SDK log level (shared across all Portal instances).
123
+ */
124
+ getLogLevel() {
125
+ return logger_1.sdkLogger.getLogLevel();
126
+ }
100
127
  /*****************************
101
128
  * Initialization Methods
102
129
  *****************************/
@@ -463,6 +490,7 @@ class Portal {
463
490
  method: 'eth_sendTransaction',
464
491
  params: [buildTxResponse.transaction],
465
492
  sponsorGas: params.sponsorGas,
493
+ signatureApprovalMemo: params.signatureApprovalMemo,
466
494
  });
467
495
  break;
468
496
  }
@@ -472,6 +500,7 @@ class Portal {
472
500
  method: 'sol_signAndSendTransaction',
473
501
  params: [buildTxResponse.transaction],
474
502
  sponsorGas: params.sponsorGas,
503
+ signatureApprovalMemo: params.signatureApprovalMemo,
475
504
  });
476
505
  break;
477
506
  }
@@ -508,7 +537,7 @@ class Portal {
508
537
  */
509
538
  ethEstimateGas(chainId, transaction) {
510
539
  return __awaiter(this, void 0, void 0, function* () {
511
- console.warn('"portal.ethEstimateGas" is deprecated. Use "portal.request" instead.');
540
+ logger_1.sdkLogger.warn('"portal.ethEstimateGas" is deprecated. Use "portal.request" instead.');
512
541
  return this.provider.request({
513
542
  chainId,
514
543
  method: provider_1.RequestMethod.eth_estimateGas,
@@ -526,7 +555,7 @@ class Portal {
526
555
  */
527
556
  ethGasPrice(chainId) {
528
557
  return __awaiter(this, void 0, void 0, function* () {
529
- console.warn('"portal.ethGasPrice" is deprecated. Use "portal.request" instead.');
558
+ logger_1.sdkLogger.warn('"portal.ethGasPrice" is deprecated. Use "portal.request" instead.');
530
559
  return this.provider.request({
531
560
  chainId,
532
561
  method: provider_1.RequestMethod.eth_gasPrice,
@@ -544,7 +573,7 @@ class Portal {
544
573
  */
545
574
  ethGetBalance(chainId) {
546
575
  return __awaiter(this, void 0, void 0, function* () {
547
- console.warn('"portal.ethGetBalance" is deprecated. Use "portal.request" instead.');
576
+ logger_1.sdkLogger.warn('"portal.ethGetBalance" is deprecated. Use "portal.request" instead.');
548
577
  return this.provider.request({
549
578
  chainId,
550
579
  method: provider_1.RequestMethod.eth_getBalance,
@@ -563,7 +592,7 @@ class Portal {
563
592
  */
564
593
  ethSendTransaction(chainId, transaction) {
565
594
  return __awaiter(this, void 0, void 0, function* () {
566
- console.warn('"portal.ethSendTransaction" is deprecated. Use "portal.request" instead.');
595
+ logger_1.sdkLogger.warn('"portal.ethSendTransaction" is deprecated. Use "portal.request" instead.');
567
596
  return this.provider.request({
568
597
  chainId,
569
598
  method: provider_1.RequestMethod.eth_sendTransaction,
@@ -582,7 +611,7 @@ class Portal {
582
611
  */
583
612
  ethSignTransaction(chainId, transaction) {
584
613
  return __awaiter(this, void 0, void 0, function* () {
585
- console.warn('"portal.ethSignTransaction" is deprecated. Use "portal.request" instead.');
614
+ logger_1.sdkLogger.warn('"portal.ethSignTransaction" is deprecated. Use "portal.request" instead.');
586
615
  return this.provider.request({
587
616
  chainId,
588
617
  method: provider_1.RequestMethod.eth_signTransaction,
@@ -601,7 +630,7 @@ class Portal {
601
630
  */
602
631
  ethSignTypedData(chainId, data) {
603
632
  return __awaiter(this, void 0, void 0, function* () {
604
- console.warn('"portal.ethSignTypedData" is deprecated. Use "portal.request" instead.');
633
+ logger_1.sdkLogger.warn('"portal.ethSignTypedData" is deprecated. Use "portal.request" instead.');
605
634
  return this.provider.request({
606
635
  chainId,
607
636
  method: provider_1.RequestMethod.eth_signTypedData,
@@ -620,7 +649,7 @@ class Portal {
620
649
  */
621
650
  ethSignTypedDataV3(chainId, data) {
622
651
  return __awaiter(this, void 0, void 0, function* () {
623
- console.warn('"portal.ethSignTypedDataV3" is deprecated. Use "portal.request" instead.');
652
+ logger_1.sdkLogger.warn('"portal.ethSignTypedDataV3" is deprecated. Use "portal.request" instead.');
624
653
  return this.provider.request({
625
654
  chainId,
626
655
  method: provider_1.RequestMethod.eth_signTypedData_v3,
@@ -639,7 +668,7 @@ class Portal {
639
668
  */
640
669
  ethSignTypedDataV4(chainId, data) {
641
670
  return __awaiter(this, void 0, void 0, function* () {
642
- console.warn('"portal.ethSignTypedDataV4" is deprecated. Use "portal.request" instead.');
671
+ logger_1.sdkLogger.warn('"portal.ethSignTypedDataV4" is deprecated. Use "portal.request" instead.');
643
672
  return this.provider.request({
644
673
  chainId,
645
674
  method: provider_1.RequestMethod.eth_signTypedData_v4,
@@ -649,7 +678,7 @@ class Portal {
649
678
  }
650
679
  personalSign(chainId, message) {
651
680
  return __awaiter(this, void 0, void 0, function* () {
652
- console.warn('"portal.personalSign" is deprecated. Use "portal.request" instead.');
681
+ logger_1.sdkLogger.warn('"portal.personalSign" is deprecated. Use "portal.request" instead.');
653
682
  return (yield this.provider.request({
654
683
  chainId,
655
684
  method: provider_1.RequestMethod.personal_sign,
@@ -657,9 +686,9 @@ class Portal {
657
686
  }));
658
687
  });
659
688
  }
660
- rawSign(curve, param) {
689
+ rawSign(curve, param, options) {
661
690
  return __awaiter(this, void 0, void 0, function* () {
662
- const response = yield this.mpc.rawSign(curve, param);
691
+ const response = yield this.mpc.rawSign(curve, param, options);
663
692
  return response;
664
693
  });
665
694
  }
@@ -701,6 +701,22 @@ describe('Portal', () => {
701
701
  expect(portal.mpc.storedClientBackupShare).toHaveBeenCalledWith(true, _1.BackupMethods.password);
702
702
  }));
703
703
  });
704
+ describe('Logging', () => {
705
+ it('should default log level to "none"', () => {
706
+ expect(portal.getLogLevel()).toBe('none');
707
+ });
708
+ it('should use logLevel from constructor options', () => {
709
+ const p = new _1.default({ rpcConfig: constants_1.mockRpcConfig, logLevel: 'debug' });
710
+ expect(p.getLogLevel()).toBe('debug');
711
+ });
712
+ it('should update log level when setLogLevel is called', () => {
713
+ expect(portal.getLogLevel()).toBe('none');
714
+ portal.setLogLevel('warn');
715
+ expect(portal.getLogLevel()).toBe('warn');
716
+ portal.setLogLevel('error');
717
+ expect(portal.getLogLevel()).toBe('error');
718
+ });
719
+ });
704
720
  describe('getRpcUrl', () => {
705
721
  it('should return the correct rpc url for the given chainId', () => {
706
722
  expect(portal.getRpcUrl('eip155:1')).toEqual(constants_1.mockEthRpcUrl);
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SdkLogger = exports.sdkLogger = void 0;
4
+ const LOG_LEVEL_PRIORITY = {
5
+ none: 0,
6
+ error: 1,
7
+ warn: 2,
8
+ info: 3,
9
+ debug: 4,
10
+ };
11
+ class SdkLogger {
12
+ constructor() {
13
+ this.logLevel = 'none';
14
+ this.logger = console;
15
+ }
16
+ configure(logLevel, logger = console) {
17
+ this.logLevel = logLevel;
18
+ this.logger = logger;
19
+ }
20
+ getLogLevel() {
21
+ return this.logLevel;
22
+ }
23
+ shouldLog(level) {
24
+ return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.logLevel];
25
+ }
26
+ log(level, args) {
27
+ if (!this.shouldLog(level))
28
+ return;
29
+ try {
30
+ this.logger[level](...args);
31
+ }
32
+ catch (err) {
33
+ console.error('[Portal SDK] Logger error, falling back to console', {
34
+ level,
35
+ error: err,
36
+ });
37
+ console[level](...args);
38
+ }
39
+ }
40
+ error(...args) {
41
+ this.log('error', args);
42
+ }
43
+ warn(...args) {
44
+ this.log('warn', args);
45
+ }
46
+ info(...args) {
47
+ this.log('info', args);
48
+ }
49
+ debug(...args) {
50
+ this.log('debug', args);
51
+ }
52
+ }
53
+ exports.SdkLogger = SdkLogger;
54
+ const sdkLogger = new SdkLogger();
55
+ exports.sdkLogger = sdkLogger;
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.MpcErrorCodes = exports.MpcError = void 0;
13
13
  const errors_1 = require("./errors");
14
14
  const index_1 = require("../index");
15
- const WEB_SDK_VERSION = '3.10.0';
15
+ const WEB_SDK_VERSION = '3.11.0-alpha.0';
16
16
  class Mpc {
17
17
  get ready() {
18
18
  return this._ready;
@@ -150,16 +150,16 @@ class Mpc {
150
150
  });
151
151
  });
152
152
  }
153
- rawSign(curve, param) {
153
+ rawSign(curve, param, options) {
154
154
  return __awaiter(this, void 0, void 0, function* () {
155
155
  return this.handleRequestToIframeAndPost({
156
156
  methodMessage: 'portal:mpc:rawSign',
157
157
  errorMessage: 'portal:mpc:rawSignError',
158
158
  resultMessage: 'portal:mpc:rawSignResult',
159
- data: {
160
- curve,
161
- param,
162
- },
159
+ data: Object.assign({ curve,
160
+ param }, ((options === null || options === void 0 ? void 0 : options.signatureApprovalMemo) !== undefined && {
161
+ signatureApprovalMemo: options.signatureApprovalMemo,
162
+ })),
163
163
  });
164
164
  });
165
165
  }
@@ -30,6 +30,22 @@ class EvmAccountType {
30
30
  });
31
31
  });
32
32
  }
33
+ /**
34
+ * Get EOA and smart contract addresses for the wallet on the given chain.
35
+ * Returns addresses from the account-type API.
36
+ * @param {EvmAccountTypeGetStatusRequest} params - The request parameters (chain id, e.g. eip155:1)
37
+ * @returns {Promise<GetAddressesResponse>} The EOA and smart contract addresses
38
+ */
39
+ getAddresses({ chain, }) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ return this.mpc.handleRequestToIframeAndPost({
42
+ methodMessage: 'portal:evmAccountType:getAddresses',
43
+ errorMessage: 'portal:evmAccountType:getAddressesError',
44
+ resultMessage: 'portal:evmAccountType:getAddressesResult',
45
+ data: { chain },
46
+ });
47
+ });
48
+ }
33
49
  /**
34
50
  * Build the authorization list for a given chain.
35
51
  * @param {BuildAuthorizationListRequest} params - The request parameters
@@ -91,6 +91,73 @@ describe('EvmAccountType', () => {
91
91
  });
92
92
  });
93
93
  });
94
+ /*******************************
95
+ * getAddresses Tests
96
+ *******************************/
97
+ describe('getAddresses', () => {
98
+ const args = constants_1.mockGetAddressesRequest;
99
+ const res = constants_1.mockGetAddressesResponse;
100
+ it('should successfully get EVM addresses', (done) => {
101
+ var _a;
102
+ jest
103
+ .spyOn((_a = mpc.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow, 'postMessage')
104
+ .mockImplementation((message, origin) => {
105
+ const { type, data } = message;
106
+ expect(type).toEqual('portal:evmAccountType:getAddresses');
107
+ expect(data).toEqual(args);
108
+ expect(origin).toEqual(mockHostOrigin);
109
+ window.dispatchEvent(new MessageEvent('message', {
110
+ origin: mockHostOrigin,
111
+ data: {
112
+ type: 'portal:evmAccountType:getAddressesResult',
113
+ data: res,
114
+ },
115
+ }));
116
+ });
117
+ evmAccountType
118
+ .getAddresses(args)
119
+ .then((data) => {
120
+ expect(data).toEqual(res);
121
+ done();
122
+ })
123
+ .catch((_) => {
124
+ expect(0).toEqual(1);
125
+ done();
126
+ });
127
+ });
128
+ it('should error out if the iframe sends an error message', (done) => {
129
+ var _a;
130
+ jest
131
+ .spyOn((_a = mpc.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow, 'postMessage')
132
+ .mockImplementationOnce((message, origin) => {
133
+ const { type, data } = message;
134
+ expect(type).toEqual('portal:evmAccountType:getAddresses');
135
+ expect(data).toEqual(args);
136
+ expect(origin).toEqual(mockHostOrigin);
137
+ window.dispatchEvent(new MessageEvent('message', {
138
+ origin: mockHostOrigin,
139
+ data: {
140
+ type: 'portal:evmAccountType:getAddressesError',
141
+ data: {
142
+ code: 1,
143
+ message: 'test',
144
+ },
145
+ },
146
+ }));
147
+ });
148
+ evmAccountType
149
+ .getAddresses(args)
150
+ .then((_) => {
151
+ expect(0).toEqual(1);
152
+ done();
153
+ })
154
+ .catch((error) => {
155
+ expect(error).toBeInstanceOf(errors_1.PortalMpcError);
156
+ expect(error.message).toEqual('test');
157
+ done();
158
+ });
159
+ });
160
+ });
94
161
  /*******************************
95
162
  * buildAuthorizationList Tests
96
163
  *******************************/
@@ -21,6 +21,7 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  };
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.PasskeyService = void 0;
24
+ const logger_1 = require("../logger");
24
25
  class PasskeyService {
25
26
  constructor({ defaultDomain, getJwt, fetchImpl }) {
26
27
  this.defaultDomain = defaultDomain;
@@ -201,7 +202,7 @@ class PasskeyService {
201
202
  return data.status;
202
203
  }
203
204
  catch (error) {
204
- console.warn('[Portal] Failed to fetch passkey status', error);
205
+ logger_1.sdkLogger.warn('[Portal] Failed to fetch passkey status', error);
205
206
  return undefined;
206
207
  }
207
208
  });
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RequestMethod = void 0;
13
13
  const errors_1 = require("./errors");
14
+ const logger_1 = require("../logger");
14
15
  var RequestMethod;
15
16
  (function (RequestMethod) {
16
17
  RequestMethod["eth_accounts"] = "eth_accounts";
@@ -260,7 +261,7 @@ class Provider {
260
261
  * @param args The arguments of the request being made
261
262
  * @returns Promise<any>
262
263
  */
263
- request({ chainId: requestChainId, method, params, sponsorGas, }) {
264
+ request({ chainId: requestChainId, method, params, sponsorGas, signatureApprovalMemo, }) {
264
265
  return __awaiter(this, void 0, void 0, function* () {
265
266
  const isSignerMethod = signerMethods.includes(method);
266
267
  const chainId = this.getCAIP2ChainId(requestChainId);
@@ -289,6 +290,7 @@ class Provider {
289
290
  method,
290
291
  params,
291
292
  sponsorGas,
293
+ signatureApprovalMemo,
292
294
  });
293
295
  if (transactionHash) {
294
296
  this.emit('portal_signatureReceived', {
@@ -326,7 +328,7 @@ class Provider {
326
328
  *
327
329
  * @param args The arguments of the request being made
328
330
  */
329
- getApproval({ method, params, }) {
331
+ getApproval({ method, params, signatureApprovalMemo, }) {
330
332
  return __awaiter(this, void 0, void 0, function* () {
331
333
  // If autoApprove is enabled, just resolve to true
332
334
  if (this.portal.autoApprove) {
@@ -365,10 +367,10 @@ class Provider {
365
367
  // If the signing request has been rejected, resolve to false
366
368
  this.on('portal_signingRejected', handleRejection);
367
369
  // Tell any listening clients that signing has been requested
368
- this.emit('portal_signingRequested', {
369
- method,
370
- params,
371
- });
370
+ const signingRequestedPayload = signatureApprovalMemo !== undefined
371
+ ? { method, params, signatureApprovalMemo }
372
+ : { method, params };
373
+ this.emit('portal_signingRequested', signingRequestedPayload);
372
374
  });
373
375
  });
374
376
  }
@@ -404,13 +406,13 @@ class Provider {
404
406
  * @param args The arguments of the request being made
405
407
  * @returns Promise<any>
406
408
  */
407
- handleSigningRequest({ chainId, method, params, sponsorGas, }) {
409
+ handleSigningRequest({ chainId, method, params, sponsorGas, signatureApprovalMemo, }) {
408
410
  return __awaiter(this, void 0, void 0, function* () {
409
411
  const isApproved = passiveSignerMethods.includes(method)
410
412
  ? true
411
- : yield this.getApproval({ method, params });
413
+ : yield this.getApproval({ method, params, signatureApprovalMemo });
412
414
  if (!isApproved) {
413
- console.warn(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
415
+ logger_1.sdkLogger.warn(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
414
416
  return;
415
417
  }
416
418
  switch (method) {
@@ -431,13 +433,9 @@ class Provider {
431
433
  case RequestMethod.eth_signTypedData_v4:
432
434
  case RequestMethod.personal_sign: {
433
435
  this.enforceEip155ChainId(chainId);
434
- const result = yield this.portal.mpc.sign({
435
- chainId: chainId,
436
- method,
437
- params: this.buildParams(method, params),
438
- rpcUrl: this.portal.getRpcUrl(chainId),
439
- sponsorGas,
440
- });
436
+ const result = yield this.portal.mpc.sign(Object.assign({ chainId: chainId, method, params: this.buildParams(method, params), rpcUrl: this.portal.getRpcUrl(chainId), sponsorGas }, (signatureApprovalMemo !== undefined && {
437
+ signatureApprovalMemo,
438
+ })));
441
439
  return result;
442
440
  }
443
441
  case RequestMethod.sol_signAndConfirmTransaction:
@@ -451,6 +449,7 @@ class Provider {
451
449
  params: this.buildParams(method, params),
452
450
  rpcUrl: this.portal.getRpcUrl(chainId),
453
451
  sponsorGas,
452
+ signatureApprovalMemo,
454
453
  });
455
454
  return result;
456
455
  }
@@ -21,6 +21,7 @@ const constants_1 = require("../__mocks/constants");
21
21
  const errors_1 = require("./errors");
22
22
  const __1 = require("../");
23
23
  const mpc_1 = __importDefault(require("../__mocks/portal/mpc"));
24
+ const logger_1 = require("../logger");
24
25
  describe('Provider', () => {
25
26
  beforeEach(() => {
26
27
  portal_1.default.autoApprove = true;
@@ -495,11 +496,15 @@ describe('Provider', () => {
495
496
  const mockSignatureReceivedHandler = jest.fn();
496
497
  const mockConsoleWarn = jest.spyOn(global.console, 'warn');
497
498
  beforeEach(() => {
499
+ logger_1.sdkLogger.configure('warn', console);
498
500
  provider = new _1.default({ portal: portal_1.default });
499
501
  portal_1.default.autoApprove = false;
500
502
  provider.on('portal_signingRequested', mockSigningRequestedHandler);
501
503
  provider.on('portal_signatureReceived', mockSignatureReceivedHandler);
502
504
  });
505
+ afterEach(() => {
506
+ logger_1.sdkLogger.configure('none');
507
+ });
503
508
  describe('eth_chainId', () => {
504
509
  it('should successfully handle an eth_chainId request', () => __awaiter(void 0, void 0, void 0, function* () {
505
510
  const result = yield provider.request({