@imtbl/sdk 0.17.5 → 0.18.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.
@@ -10546,6 +10546,27 @@ declare class JsonRpcError extends Error {
10546
10546
  constructor(code: ProviderErrorCode | RpcErrorCode, message: string);
10547
10547
  }
10548
10548
 
10549
+ interface TypedDataPayload {
10550
+ types: {
10551
+ EIP712Domain: Array<{
10552
+ name: string;
10553
+ type: string;
10554
+ }>;
10555
+ [key: string]: Array<{
10556
+ name: string;
10557
+ type: string;
10558
+ }>;
10559
+ };
10560
+ domain: {
10561
+ name?: string;
10562
+ version?: string;
10563
+ chainId?: number;
10564
+ verifyingContract?: string;
10565
+ salt?: string;
10566
+ };
10567
+ primaryType: string;
10568
+ message: Record<string, any>;
10569
+ }
10549
10570
  interface RequestArguments {
10550
10571
  method: string;
10551
10572
  params?: Array<any>;
@@ -10613,4 +10634,4 @@ declare class Passport {
10613
10634
  getLinkedAddresses(): Promise<string[]>;
10614
10635
  }
10615
10636
 
10616
- export { AccountsChangedEvent, DeviceConnectResponse, DeviceTokenResponse, JsonRpcError, JsonRpcRequestCallback, JsonRpcRequestPayload, JsonRpcResponsePayload, Networks, Passport, PassportError, PassportModuleConfiguration, PassportOverrides, Provider, ProviderErrorCode, ProviderEvent, RequestArguments, RpcErrorCode, UserProfile };
10637
+ export { AccountsChangedEvent, DeviceConnectResponse, DeviceTokenResponse, JsonRpcError, JsonRpcRequestCallback, JsonRpcRequestPayload, JsonRpcResponsePayload, Networks, Passport, PassportError, PassportModuleConfiguration, PassportOverrides, Provider, ProviderErrorCode, ProviderEvent, RequestArguments, RpcErrorCode, TypedDataPayload, UserProfile };
package/dist/passport.js CHANGED
@@ -11,7 +11,7 @@ import * as encUtils from 'enc-utils';
11
11
  import { Web3Provider, JsonRpcProvider } from '@ethersproject/providers';
12
12
  import { EventEmitter } from 'events';
13
13
  import { walletContracts } from '@0xsequence/abi';
14
- import { encodeSignature } from '@0xsequence/config';
14
+ import { encodeSignature, decodeSignature } from '@0xsequence/config';
15
15
 
16
16
  /* tslint:disable */
17
17
  /* eslint-disable */
@@ -1709,7 +1709,7 @@ class MultiRollupApiClients {
1709
1709
  }
1710
1710
 
1711
1711
  // eslint-disable-next-line @typescript-eslint/naming-convention
1712
- const defaultHeaders = { 'x-sdk-version': 'ts-immutable-sdk-0.17.5' };
1712
+ const defaultHeaders = { 'x-sdk-version': 'ts-immutable-sdk-0.18.0' };
1713
1713
  const createConfig = ({ basePath, headers, }) => {
1714
1714
  if (!basePath.trim()) {
1715
1715
  throw Error('basePath can not be empty');
@@ -1765,7 +1765,7 @@ const createImmutableXConfiguration = ({ basePath, chainID, coreContractAddress,
1765
1765
  chainID,
1766
1766
  coreContractAddress,
1767
1767
  registrationContractAddress,
1768
- sdkVersion: 'ts-immutable-sdk-0.17.5',
1768
+ sdkVersion: 'ts-immutable-sdk-0.18.0',
1769
1769
  });
1770
1770
  /**
1771
1771
  * Sets `sdkVersion` at the time of build
@@ -5489,6 +5489,8 @@ var globalAxios = axios;
5489
5489
  * https://openapi-generator.tech
5490
5490
  * Do not edit the class manually.
5491
5491
  */
5492
+ // Some imports not used depending on template conditions
5493
+ // @ts-ignore
5492
5494
  const BASE_PATH = "https://guardian.sandbox.imtbl.com".replace(/\/+$/, "");
5493
5495
  /**
5494
5496
  *
@@ -5516,10 +5518,10 @@ class BaseAPI {
5516
5518
  */
5517
5519
  class RequiredError extends Error {
5518
5520
  field;
5521
+ name = "RequiredError";
5519
5522
  constructor(field, msg) {
5520
5523
  super(msg);
5521
5524
  this.field = field;
5522
- this.name = "RequiredError";
5523
5525
  }
5524
5526
  }
5525
5527
 
@@ -5564,8 +5566,6 @@ const setBearerAuthToObject = async function (object, configuration) {
5564
5566
  }
5565
5567
  };
5566
5568
  function setFlattenedQueryParams(urlSearchParams, parameter, key = "") {
5567
- if (parameter == null)
5568
- return;
5569
5569
  if (typeof parameter === "object") {
5570
5570
  if (Array.isArray(parameter)) {
5571
5571
  parameter.forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
@@ -5623,6 +5623,208 @@ const createRequestFunction = function (axiosArgs, globalAxios, BASE_PATH, confi
5623
5623
  };
5624
5624
  };
5625
5625
 
5626
+ /* tslint:disable */
5627
+ /* eslint-disable */
5628
+ /**
5629
+ * Guardian
5630
+ * Guardian API
5631
+ *
5632
+ * The version of the OpenAPI document: 1.0.0
5633
+ * Contact: support@immutable.com
5634
+ *
5635
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
5636
+ * https://openapi-generator.tech
5637
+ * Do not edit the class manually.
5638
+ */
5639
+ /**
5640
+ * MessagesApi - axios parameter creator
5641
+ * @export
5642
+ */
5643
+ const MessagesApiAxiosParamCreator = function (configuration) {
5644
+ return {
5645
+ /**
5646
+ * Approve a pending evm message
5647
+ * @summary Approve a pending evm message
5648
+ * @param {string} messageID id for the message
5649
+ * @param {*} [options] Override http request option.
5650
+ * @throws {RequiredError}
5651
+ */
5652
+ approvePendingMessage: async (messageID, options = {}) => {
5653
+ // verify required parameter 'messageID' is not null or undefined
5654
+ assertParamExists('approvePendingMessage', 'messageID', messageID);
5655
+ const localVarPath = `/guardian/v1/messages/{messageID}/approve`
5656
+ .replace(`{${"messageID"}}`, encodeURIComponent(String(messageID)));
5657
+ // use dummy base URL string because the URL constructor only accepts absolute URLs.
5658
+ const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
5659
+ let baseOptions;
5660
+ if (configuration) {
5661
+ baseOptions = configuration.baseOptions;
5662
+ }
5663
+ const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
5664
+ const localVarHeaderParameter = {};
5665
+ const localVarQueryParameter = {};
5666
+ // authentication BearerAuth required
5667
+ // http bearer authentication required
5668
+ await setBearerAuthToObject(localVarHeaderParameter, configuration);
5669
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
5670
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
5671
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
5672
+ return {
5673
+ url: toPathString(localVarUrlObj),
5674
+ options: localVarRequestOptions,
5675
+ };
5676
+ },
5677
+ /**
5678
+ * Check if a given message is valid for EVM
5679
+ * @summary Evaluate an evm message to sign
5680
+ * @param {MessageEvaluationRequest} messageEvaluationRequest Specifies the kind of transaction
5681
+ * @param {*} [options] Override http request option.
5682
+ * @throws {RequiredError}
5683
+ */
5684
+ evaluateMessage: async (messageEvaluationRequest, options = {}) => {
5685
+ // verify required parameter 'messageEvaluationRequest' is not null or undefined
5686
+ assertParamExists('evaluateMessage', 'messageEvaluationRequest', messageEvaluationRequest);
5687
+ const localVarPath = `/guardian/v1/messages/evaluate`;
5688
+ // use dummy base URL string because the URL constructor only accepts absolute URLs.
5689
+ const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
5690
+ let baseOptions;
5691
+ if (configuration) {
5692
+ baseOptions = configuration.baseOptions;
5693
+ }
5694
+ const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
5695
+ const localVarHeaderParameter = {};
5696
+ const localVarQueryParameter = {};
5697
+ // authentication BearerAuth required
5698
+ // http bearer authentication required
5699
+ await setBearerAuthToObject(localVarHeaderParameter, configuration);
5700
+ localVarHeaderParameter['Content-Type'] = 'application/json';
5701
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
5702
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
5703
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
5704
+ localVarRequestOptions.data = serializeDataIfNeeded(messageEvaluationRequest, localVarRequestOptions, configuration);
5705
+ return {
5706
+ url: toPathString(localVarUrlObj),
5707
+ options: localVarRequestOptions,
5708
+ };
5709
+ },
5710
+ /**
5711
+ * Get an evm message by id
5712
+ * @summary Info for a specific evm message
5713
+ * @param {string} messageID The id of the evm message
5714
+ * @param {*} [options] Override http request option.
5715
+ * @throws {RequiredError}
5716
+ */
5717
+ getMessageByID: async (messageID, options = {}) => {
5718
+ // verify required parameter 'messageID' is not null or undefined
5719
+ assertParamExists('getMessageByID', 'messageID', messageID);
5720
+ const localVarPath = `/guardian/v1/messages/{messageID}`
5721
+ .replace(`{${"messageID"}}`, encodeURIComponent(String(messageID)));
5722
+ // use dummy base URL string because the URL constructor only accepts absolute URLs.
5723
+ const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
5724
+ let baseOptions;
5725
+ if (configuration) {
5726
+ baseOptions = configuration.baseOptions;
5727
+ }
5728
+ const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options };
5729
+ const localVarHeaderParameter = {};
5730
+ const localVarQueryParameter = {};
5731
+ // authentication BearerAuth required
5732
+ // http bearer authentication required
5733
+ await setBearerAuthToObject(localVarHeaderParameter, configuration);
5734
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
5735
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
5736
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
5737
+ return {
5738
+ url: toPathString(localVarUrlObj),
5739
+ options: localVarRequestOptions,
5740
+ };
5741
+ },
5742
+ };
5743
+ };
5744
+ /**
5745
+ * MessagesApi - functional programming interface
5746
+ * @export
5747
+ */
5748
+ const MessagesApiFp = function (configuration) {
5749
+ const localVarAxiosParamCreator = MessagesApiAxiosParamCreator(configuration);
5750
+ return {
5751
+ /**
5752
+ * Approve a pending evm message
5753
+ * @summary Approve a pending evm message
5754
+ * @param {string} messageID id for the message
5755
+ * @param {*} [options] Override http request option.
5756
+ * @throws {RequiredError}
5757
+ */
5758
+ async approvePendingMessage(messageID, options) {
5759
+ const localVarAxiosArgs = await localVarAxiosParamCreator.approvePendingMessage(messageID, options);
5760
+ return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
5761
+ },
5762
+ /**
5763
+ * Check if a given message is valid for EVM
5764
+ * @summary Evaluate an evm message to sign
5765
+ * @param {MessageEvaluationRequest} messageEvaluationRequest Specifies the kind of transaction
5766
+ * @param {*} [options] Override http request option.
5767
+ * @throws {RequiredError}
5768
+ */
5769
+ async evaluateMessage(messageEvaluationRequest, options) {
5770
+ const localVarAxiosArgs = await localVarAxiosParamCreator.evaluateMessage(messageEvaluationRequest, options);
5771
+ return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
5772
+ },
5773
+ /**
5774
+ * Get an evm message by id
5775
+ * @summary Info for a specific evm message
5776
+ * @param {string} messageID The id of the evm message
5777
+ * @param {*} [options] Override http request option.
5778
+ * @throws {RequiredError}
5779
+ */
5780
+ async getMessageByID(messageID, options) {
5781
+ const localVarAxiosArgs = await localVarAxiosParamCreator.getMessageByID(messageID, options);
5782
+ return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
5783
+ },
5784
+ };
5785
+ };
5786
+ /**
5787
+ * MessagesApi - object-oriented interface
5788
+ * @export
5789
+ * @class MessagesApi
5790
+ * @extends {BaseAPI}
5791
+ */
5792
+ class MessagesApi extends BaseAPI {
5793
+ /**
5794
+ * Approve a pending evm message
5795
+ * @summary Approve a pending evm message
5796
+ * @param {MessagesApiApprovePendingMessageRequest} requestParameters Request parameters.
5797
+ * @param {*} [options] Override http request option.
5798
+ * @throws {RequiredError}
5799
+ * @memberof MessagesApi
5800
+ */
5801
+ approvePendingMessage(requestParameters, options) {
5802
+ return MessagesApiFp(this.configuration).approvePendingMessage(requestParameters.messageID, options).then((request) => request(this.axios, this.basePath));
5803
+ }
5804
+ /**
5805
+ * Check if a given message is valid for EVM
5806
+ * @summary Evaluate an evm message to sign
5807
+ * @param {MessagesApiEvaluateMessageRequest} requestParameters Request parameters.
5808
+ * @param {*} [options] Override http request option.
5809
+ * @throws {RequiredError}
5810
+ * @memberof MessagesApi
5811
+ */
5812
+ evaluateMessage(requestParameters, options) {
5813
+ return MessagesApiFp(this.configuration).evaluateMessage(requestParameters.messageEvaluationRequest, options).then((request) => request(this.axios, this.basePath));
5814
+ }
5815
+ /**
5816
+ * Get an evm message by id
5817
+ * @summary Info for a specific evm message
5818
+ * @param {MessagesApiGetMessageByIDRequest} requestParameters Request parameters.
5819
+ * @param {*} [options] Override http request option.
5820
+ * @throws {RequiredError}
5821
+ * @memberof MessagesApi
5822
+ */
5823
+ getMessageByID(requestParameters, options) {
5824
+ return MessagesApiFp(this.configuration).getMessageByID(requestParameters.messageID, options).then((request) => request(this.axios, this.basePath));
5825
+ }
5826
+ }
5827
+
5626
5828
  /* tslint:disable */
5627
5829
  /* eslint-disable */
5628
5830
  /**
@@ -5646,7 +5848,7 @@ const TransactionsApiAxiosParamCreator = function (configuration) {
5646
5848
  * Approve a pending transaction
5647
5849
  * @summary Approve a pending transaction given chain
5648
5850
  * @param {string} payloadHash Hash for the payload
5649
- * @param {TransactionApprovalRequest} transactionApprovalRequest request body for approving a pending transactio
5851
+ * @param {TransactionApprovalRequest} transactionApprovalRequest request body for approving a pending transaction
5650
5852
  * @param {*} [options] Override http request option.
5651
5853
  * @throws {RequiredError}
5652
5854
  */
@@ -5771,7 +5973,7 @@ const TransactionsApiFp = function (configuration) {
5771
5973
  * Approve a pending transaction
5772
5974
  * @summary Approve a pending transaction given chain
5773
5975
  * @param {string} payloadHash Hash for the payload
5774
- * @param {TransactionApprovalRequest} transactionApprovalRequest request body for approving a pending transactio
5976
+ * @param {TransactionApprovalRequest} transactionApprovalRequest request body for approving a pending transaction
5775
5977
  * @param {*} [options] Override http request option.
5776
5978
  * @throws {RequiredError}
5777
5979
  */
@@ -6528,18 +6730,18 @@ const transformGuardianTransactions = (txs) => {
6528
6730
  };
6529
6731
  class GuardianClient {
6530
6732
  transactionAPI;
6733
+ messageAPI;
6531
6734
  confirmationScreen;
6532
6735
  // TODO: ID-977, make this rollup agnostic
6533
6736
  imxEtherAddress;
6534
6737
  crossSdkBridgeEnabled;
6535
6738
  constructor({ accessToken, confirmationScreen, imxEtherAddress, config, }) {
6739
+ const guardianConfiguration = new Configuration({ accessToken, basePath: config.imxPublicApiDomain });
6536
6740
  this.confirmationScreen = confirmationScreen;
6537
- this.transactionAPI = new TransactionsApi(new Configuration({
6538
- accessToken,
6539
- basePath: config.imxPublicApiDomain,
6540
- }));
6741
+ this.transactionAPI = new TransactionsApi(new Configuration(guardianConfiguration));
6541
6742
  this.imxEtherAddress = imxEtherAddress;
6542
6743
  this.crossSdkBridgeEnabled = config.crossSdkBridgeEnabled;
6744
+ this.messageAPI = new MessagesApi(guardianConfiguration);
6543
6745
  }
6544
6746
  /**
6545
6747
  * Open confirmation screen and close it automatically if the
@@ -6638,6 +6840,31 @@ class GuardianClient {
6638
6840
  this.confirmationScreen.closeWindow();
6639
6841
  }
6640
6842
  }
6843
+ async evaluateMessage({ chainID, payload, user }) {
6844
+ try {
6845
+ const messageEvalResponse = await this.messageAPI.evaluateMessage({ messageEvaluationRequest: { chainID, payload } }, { headers: { Authorization: `Bearer ${user.accessToken}` } });
6846
+ return messageEvalResponse.data;
6847
+ }
6848
+ catch (error) {
6849
+ const errorMessage = error instanceof Error ? error.message : String(error);
6850
+ throw new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, `Message failed to validate with error: ${errorMessage}`);
6851
+ }
6852
+ }
6853
+ async validateMessage({ chainID, payload, user }) {
6854
+ const { messageId, confirmationRequired } = await this.evaluateMessage({ chainID, payload, user });
6855
+ if (confirmationRequired && this.crossSdkBridgeEnabled) {
6856
+ throw new JsonRpcError(RpcErrorCode.TRANSACTION_REJECTED, transactionRejectedCrossSdkBridgeError);
6857
+ }
6858
+ if (confirmationRequired && !!messageId) {
6859
+ const confirmationResult = await this.confirmationScreen.requestMessageConfirmation(messageId);
6860
+ if (!confirmationResult.confirmed) {
6861
+ throw new JsonRpcError(RpcErrorCode.TRANSACTION_REJECTED, 'Signature rejected by user');
6862
+ }
6863
+ }
6864
+ else {
6865
+ this.confirmationScreen.closeWindow();
6866
+ }
6867
+ }
6641
6868
  }
6642
6869
 
6643
6870
  var PassportEvents;
@@ -7265,6 +7492,8 @@ var ReceiveMessage;
7265
7492
  ReceiveMessage["CONFIRMATION_WINDOW_READY"] = "confirmation_window_ready";
7266
7493
  ReceiveMessage["TRANSACTION_CONFIRMED"] = "transaction_confirmed";
7267
7494
  ReceiveMessage["TRANSACTION_ERROR"] = "transaction_error";
7495
+ ReceiveMessage["MESSAGE_CONFIRMED"] = "message_confirmed";
7496
+ ReceiveMessage["MESSAGE_REJECTED"] = "message_rejected";
7268
7497
  })(ReceiveMessage || (ReceiveMessage = {}));
7269
7498
  const PASSPORT_EVENT_TYPE = 'imx_passport_confirmation';
7270
7499
 
@@ -7318,11 +7547,11 @@ class ConfirmationScreen {
7318
7547
  reject(new Error('Unsupported message type'));
7319
7548
  }
7320
7549
  };
7321
- window.addEventListener('message', messageHandler);
7322
7550
  if (!this.confirmationWindow) {
7323
7551
  resolve({ confirmed: false });
7324
7552
  return;
7325
7553
  }
7554
+ window.addEventListener('message', messageHandler);
7326
7555
  let href = '';
7327
7556
  if (chainType === TransactionApprovalRequestChainTypeEnum.Starkex) {
7328
7557
  // eslint-disable-next-line max-len
@@ -7332,15 +7561,39 @@ class ConfirmationScreen {
7332
7561
  // eslint-disable-next-line max-len
7333
7562
  href = `${this.config.passportDomain}/transaction-confirmation/zkevm?transactionId=${transactionId}&imxEtherAddress=${imxEtherAddress}&chainType=evm&chainId=${chainId}`;
7334
7563
  }
7335
- this.confirmationWindow.location.href = href;
7336
- // https://stackoverflow.com/questions/9388380/capture-the-close-event-of-popup-window-in-javascript/48240128#48240128
7337
- const timer = setInterval(() => {
7338
- if (this.confirmationWindow?.closed) {
7339
- clearInterval(timer);
7340
- window.removeEventListener('message', messageHandler);
7341
- resolve({ confirmed: false });
7564
+ this.showConfirmationScreen(href, messageHandler, resolve);
7565
+ });
7566
+ }
7567
+ requestMessageConfirmation(messageId) {
7568
+ return new Promise((resolve, reject) => {
7569
+ const messageHandler = ({ data, origin }) => {
7570
+ if (origin !== this.config.passportDomain
7571
+ || data.eventType !== PASSPORT_EVENT_TYPE) {
7572
+ return;
7573
+ }
7574
+ switch (data.messageType) {
7575
+ case ReceiveMessage.CONFIRMATION_WINDOW_READY: {
7576
+ break;
7577
+ }
7578
+ case ReceiveMessage.MESSAGE_CONFIRMED: {
7579
+ resolve({ confirmed: true });
7580
+ break;
7581
+ }
7582
+ case ReceiveMessage.MESSAGE_REJECTED: {
7583
+ reject(new Error('Message rejected'));
7584
+ break;
7585
+ }
7586
+ default:
7587
+ reject(new Error('Unsupported message type'));
7342
7588
  }
7343
- }, CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION);
7589
+ };
7590
+ if (!this.confirmationWindow) {
7591
+ resolve({ confirmed: false });
7592
+ return;
7593
+ }
7594
+ window.addEventListener('message', messageHandler);
7595
+ const href = `${this.config.passportDomain}/transaction-confirmation/zkevm/message?messageID=${messageId}`;
7596
+ this.showConfirmationScreen(href, messageHandler, resolve);
7344
7597
  });
7345
7598
  }
7346
7599
  loading(popupOptions) {
@@ -7358,6 +7611,17 @@ class ConfirmationScreen {
7358
7611
  closeWindow() {
7359
7612
  this.confirmationWindow?.close();
7360
7613
  }
7614
+ showConfirmationScreen(href, messageHandler, resolve) {
7615
+ this.confirmationWindow.location.href = href;
7616
+ // https://stackoverflow.com/questions/9388380/capture-the-close-event-of-popup-window-in-javascript/48240128#48240128
7617
+ const timer = setInterval(() => {
7618
+ if (this.confirmationWindow?.closed) {
7619
+ clearInterval(timer);
7620
+ window.removeEventListener('message', messageHandler);
7621
+ resolve({ confirmed: false });
7622
+ }
7623
+ }, CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION);
7624
+ }
7361
7625
  }
7362
7626
 
7363
7627
  var RelayerTransactionStatus;
@@ -7386,13 +7650,9 @@ class TypedEventEmitter {
7386
7650
  }
7387
7651
  }
7388
7652
 
7389
- // These are ignored by the Relayer but for consistency we set them to the
7390
- // appropriate values for a 1/1 wallet.
7391
- //
7392
- // Weight of a single signature in the multisig
7393
- const SIGNATURE_WEIGHT = 1;
7394
- // Total required weight in the multisig
7395
- const SIGNATURE_THRESHOLD = 1;
7653
+ const SIGNATURE_WEIGHT = 1; // Weight of a single signature in the multi-sig
7654
+ const TRANSACTION_SIGNATURE_THRESHOLD = 1; // Total required weight in the multi-sig for a transaction
7655
+ const EIP712_SIGNATURE_THRESHOLD = 2; // Total required weight in the multi-sig for data signing
7396
7656
  const ETH_SIGN_FLAG = '02';
7397
7657
  const ETH_SIGN_PREFIX = '\x19\x01';
7398
7658
  const META_TRANSACTIONS_TYPE = `tuple(
@@ -7425,11 +7685,12 @@ const getNonce = async (jsonRpcProvider, smartContractWalletAddress) => {
7425
7685
  }
7426
7686
  return 0;
7427
7687
  };
7688
+ const encodeMessageSubDigest = (chainId, walletAddress, digest) => (ethers.utils.solidityPack(['string', 'uint256', 'address', 'bytes32'], [ETH_SIGN_PREFIX, chainId, walletAddress, digest]));
7428
7689
  const getSignedMetaTransactions = async (metaTransactions, nonce, chainId, walletAddress, signer) => {
7429
7690
  const normalisedMetaTransactions = getNormalisedTransactions(metaTransactions);
7430
7691
  // Get the hash
7431
7692
  const digest = digestOfTransactionsAndNonce(nonce, normalisedMetaTransactions);
7432
- const completePayload = ethers.utils.solidityPack(['string', 'uint256', 'address', 'bytes32'], [ETH_SIGN_PREFIX, chainId, walletAddress, digest]);
7693
+ const completePayload = encodeMessageSubDigest(chainId, walletAddress, digest);
7433
7694
  const hash = ethers.utils.keccak256(completePayload);
7434
7695
  // Sign the digest
7435
7696
  const hashArray = ethers.utils.arrayify(hash);
@@ -7437,7 +7698,7 @@ const getSignedMetaTransactions = async (metaTransactions, nonce, chainId, walle
7437
7698
  const signedDigest = `${ethsigNoType}${ETH_SIGN_FLAG}`;
7438
7699
  // Add metadata
7439
7700
  const encodedSignature = encodeSignature({
7440
- threshold: SIGNATURE_THRESHOLD,
7701
+ threshold: TRANSACTION_SIGNATURE_THRESHOLD,
7441
7702
  signers: [
7442
7703
  {
7443
7704
  weight: SIGNATURE_WEIGHT,
@@ -7453,6 +7714,35 @@ const getSignedMetaTransactions = async (metaTransactions, nonce, chainId, walle
7453
7714
  encodedSignature,
7454
7715
  ]);
7455
7716
  };
7717
+ const decodeRelayerTypedDataSignature = (relayerSignature) => {
7718
+ const signatureWithThreshold = `0000${relayerSignature}`;
7719
+ return decodeSignature(signatureWithThreshold);
7720
+ };
7721
+ const getSignedTypedData = async (typedData, relayerSignature, chainId, walletAddress, signer) => {
7722
+ // Ethers auto-generates the EIP712Domain type in the TypedDataEncoder, and so it needs to be removed
7723
+ const types = { ...typedData.types };
7724
+ // @ts-ignore
7725
+ delete types.EIP712Domain;
7726
+ // eslint-disable-next-line no-underscore-dangle
7727
+ const digest = ethers.utils._TypedDataEncoder.hash(typedData.domain, types, typedData.message);
7728
+ const completePayload = encodeMessageSubDigest(chainId, walletAddress, digest);
7729
+ const hash = ethers.utils.keccak256(completePayload);
7730
+ // Sign the digest
7731
+ const hashArray = ethers.utils.arrayify(hash);
7732
+ const ethsigNoType = await signer.signMessage(hashArray);
7733
+ const signedDigest = `${ethsigNoType}${ETH_SIGN_FLAG}`;
7734
+ const { signers } = decodeRelayerTypedDataSignature(relayerSignature);
7735
+ return encodeSignature({
7736
+ threshold: EIP712_SIGNATURE_THRESHOLD,
7737
+ signers: [
7738
+ ...signers,
7739
+ {
7740
+ weight: SIGNATURE_WEIGHT,
7741
+ signature: signedDigest,
7742
+ },
7743
+ ],
7744
+ });
7745
+ };
7456
7746
  const getEip155ChainId = (chainId) => `eip155:${chainId}`;
7457
7747
 
7458
7748
  class RelayerClient {
@@ -7518,6 +7808,19 @@ class RelayerClient {
7518
7808
  const { result } = await this.postToRelayer(payload);
7519
7809
  return result;
7520
7810
  }
7811
+ async imSignTypedData(address, eip712Payload) {
7812
+ const { chainId } = await this.jsonRpcProvider.ready;
7813
+ const payload = {
7814
+ method: 'im_signTypedData',
7815
+ params: [{
7816
+ address,
7817
+ eip712Payload,
7818
+ chainId: getEip155ChainId(chainId),
7819
+ }],
7820
+ };
7821
+ const { result } = await this.postToRelayer(payload);
7822
+ return result;
7823
+ }
7521
7824
  }
7522
7825
 
7523
7826
  const MESSAGE_TO_SIGN = 'Only sign this message from Immutable Passport';
@@ -7658,6 +7961,60 @@ const sendTransaction = ({ params, magicProvider, jsonRpcProvider, relayerClient
7658
7961
  return relayerTransaction.hash;
7659
7962
  });
7660
7963
 
7964
+ const REQUIRED_TYPED_DATA_PROPERTIES = ['types', 'domain', 'primaryType', 'message'];
7965
+ const isValidTypedDataPayload = (typedData) => (REQUIRED_TYPED_DATA_PROPERTIES.every((key) => key in typedData));
7966
+ const transformTypedData = (typedData, chainId) => {
7967
+ let transformedTypedData;
7968
+ if (typeof typedData === 'string') {
7969
+ try {
7970
+ transformedTypedData = JSON.parse(typedData);
7971
+ }
7972
+ catch (err) {
7973
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Failed to parse typed data JSON: ${err}`);
7974
+ }
7975
+ }
7976
+ else if (typeof typedData === 'object') {
7977
+ transformedTypedData = typedData;
7978
+ }
7979
+ else {
7980
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Invalid typed data argument: ${typedData}`);
7981
+ }
7982
+ if (!isValidTypedDataPayload(transformedTypedData)) {
7983
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Invalid typed data argument. The following properties are required: ${REQUIRED_TYPED_DATA_PROPERTIES.join(', ')}`);
7984
+ }
7985
+ const providedChainId = transformedTypedData.domain?.chainId;
7986
+ if (providedChainId) {
7987
+ // domain.chainId (if defined) can be a number, string, or hex value, but the relayer & guardian only accept a number.
7988
+ if (typeof providedChainId === 'string') {
7989
+ if (providedChainId.startsWith('0x')) {
7990
+ transformedTypedData.domain.chainId = parseInt(providedChainId, 16);
7991
+ }
7992
+ else {
7993
+ transformedTypedData.domain.chainId = parseInt(providedChainId, 10);
7994
+ }
7995
+ }
7996
+ if (transformedTypedData.domain.chainId !== chainId) {
7997
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Invalid chainId, expected ${chainId}`);
7998
+ }
7999
+ }
8000
+ return transformedTypedData;
8001
+ };
8002
+ const signTypedDataV4 = async ({ params, method, magicProvider, jsonRpcProvider, relayerClient, guardianClient, user, }) => guardianClient
8003
+ .withConfirmationScreen({ width: 480, height: 730 })(async () => {
8004
+ const fromAddress = params[0];
8005
+ const typedDataParam = params[1];
8006
+ if (!fromAddress || !typedDataParam) {
8007
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `${method} requires an address and a typed data JSON`);
8008
+ }
8009
+ const { chainId } = await jsonRpcProvider.ready;
8010
+ const typedData = transformTypedData(typedDataParam, chainId);
8011
+ await guardianClient.validateMessage({ chainID: String(chainId), payload: typedData, user });
8012
+ const relayerSignature = await relayerClient.imSignTypedData(fromAddress, typedData);
8013
+ const magicWeb3Provider = new Web3Provider(magicProvider);
8014
+ const signer = magicWeb3Provider.getSigner();
8015
+ return getSignedTypedData(typedData, relayerSignature, BigNumber.from(chainId), fromAddress, signer);
8016
+ });
8017
+
7661
8018
  class ZkEvmProvider {
7662
8019
  authManager;
7663
8020
  config;
@@ -7741,6 +8098,21 @@ class ZkEvmProvider {
7741
8098
  case 'eth_accounts': {
7742
8099
  return this.isLoggedIn() ? [this.user.zkEvm.ethAddress] : [];
7743
8100
  }
8101
+ case 'eth_signTypedData':
8102
+ case 'eth_signTypedData_v4': {
8103
+ if (!this.isLoggedIn()) {
8104
+ throw new JsonRpcError(ProviderErrorCode.UNAUTHORIZED, 'Unauthorised - call eth_requestAccounts first');
8105
+ }
8106
+ return signTypedDataV4({
8107
+ method: request.method,
8108
+ params: request.params || [],
8109
+ magicProvider: this.magicProvider,
8110
+ jsonRpcProvider: this.jsonRpcProvider,
8111
+ relayerClient: this.relayerClient,
8112
+ user: this.user,
8113
+ guardianClient: this.guardianClient,
8114
+ });
8115
+ }
7744
8116
  // Pass through methods
7745
8117
  case 'eth_gasPrice':
7746
8118
  case 'eth_getBalance':
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@imtbl/sdk",
3
3
  "description": "Immutable Unified SDK",
4
- "version": "0.17.5",
4
+ "version": "0.18.0",
5
5
  "author": "Immutable",
6
6
  "browser": "./dist/index.browser.js",
7
7
  "bugs": "https://github.com/immutable/ts-immutable-sdk/issues",