@imtbl/wallet 2.12.3 → 2.12.4-alpha.1

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.
Files changed (35) hide show
  1. package/dist/browser/index.js +24 -32
  2. package/dist/node/index.cjs +47 -55
  3. package/dist/node/index.js +26 -34
  4. package/dist/types/guardian/index.d.ts +1 -2
  5. package/dist/types/magic/magicTEESigner.d.ts +8 -7
  6. package/dist/types/types.d.ts +15 -6
  7. package/dist/types/utils/crypto.d.ts +12 -0
  8. package/dist/types/zkEvm/personalSign.d.ts +4 -3
  9. package/dist/types/zkEvm/relayerClient.d.ts +6 -6
  10. package/dist/types/zkEvm/sequenceCompat.d.ts +60 -30
  11. package/dist/types/zkEvm/signTypedDataV4.d.ts +6 -3
  12. package/dist/types/zkEvm/transactionHelpers.d.ts +15 -4
  13. package/dist/types/zkEvm/types.d.ts +5 -6
  14. package/dist/types/zkEvm/user/registerZkEvmUser.d.ts +4 -3
  15. package/dist/types/zkEvm/walletHelpers.d.ts +15 -14
  16. package/dist/types/zkEvm/zkEvmProvider.d.ts +2 -3
  17. package/package.json +5 -6
  18. package/src/guardian/index.ts +3 -3
  19. package/src/magic/magicTEESigner.ts +11 -24
  20. package/src/types.ts +16 -6
  21. package/src/utils/crypto.ts +85 -0
  22. package/src/utils/string.ts +30 -2
  23. package/src/zkEvm/personalSign.ts +6 -5
  24. package/src/zkEvm/relayerClient.ts +11 -11
  25. package/src/zkEvm/sendDeployTransactionAndPersonalSign.ts +1 -1
  26. package/src/zkEvm/sequenceCompat.ts +26 -24
  27. package/src/zkEvm/sessionActivity/sessionActivity.ts +7 -3
  28. package/src/zkEvm/signEjectionTransaction.ts +1 -1
  29. package/src/zkEvm/signTypedDataV4.test.ts +125 -0
  30. package/src/zkEvm/signTypedDataV4.ts +16 -10
  31. package/src/zkEvm/transactionHelpers.ts +20 -11
  32. package/src/zkEvm/types.ts +5 -6
  33. package/src/zkEvm/user/registerZkEvmUser.ts +9 -8
  34. package/src/zkEvm/walletHelpers.ts +112 -63
  35. package/src/zkEvm/zkEvmProvider.ts +32 -32
@@ -1,35 +1,65 @@
1
1
  export declare const walletContracts: {
2
2
  mainModule: {
3
- abi: ({
4
- type: string;
5
- name: string;
6
- constant: boolean;
7
- inputs: {
8
- type: string;
9
- }[];
10
- outputs: {
11
- type: string;
12
- }[];
13
- payable: boolean;
14
- stateMutability: string;
15
- } | {
16
- type: string;
17
- name: string;
18
- constant: boolean;
19
- inputs: ({
20
- components: {
21
- type: string;
22
- name: string;
23
- }[];
24
- type: string;
25
- } | {
26
- type: string;
27
- components?: undefined;
28
- })[];
29
- outputs: never[];
30
- payable: boolean;
31
- stateMutability: string;
32
- })[];
3
+ abi: readonly [{
4
+ readonly type: "function";
5
+ readonly name: "nonce";
6
+ readonly constant: true;
7
+ readonly inputs: readonly [];
8
+ readonly outputs: readonly [{
9
+ readonly type: "uint256";
10
+ }];
11
+ readonly payable: false;
12
+ readonly stateMutability: "view";
13
+ }, {
14
+ readonly type: "function";
15
+ readonly name: "readNonce";
16
+ readonly constant: true;
17
+ readonly inputs: readonly [{
18
+ readonly type: "uint256";
19
+ readonly name: "_space";
20
+ }];
21
+ readonly outputs: readonly [{
22
+ readonly type: "uint256";
23
+ }];
24
+ readonly payable: false;
25
+ readonly stateMutability: "view";
26
+ }, {
27
+ readonly type: "function";
28
+ readonly name: "execute";
29
+ readonly constant: false;
30
+ readonly inputs: readonly [{
31
+ readonly components: readonly [{
32
+ readonly type: "bool";
33
+ readonly name: "delegateCall";
34
+ }, {
35
+ readonly type: "bool";
36
+ readonly name: "revertOnError";
37
+ }, {
38
+ readonly type: "uint256";
39
+ readonly name: "gasLimit";
40
+ }, {
41
+ readonly type: "address";
42
+ readonly name: "target";
43
+ }, {
44
+ readonly type: "uint256";
45
+ readonly name: "value";
46
+ }, {
47
+ readonly type: "bytes";
48
+ readonly name: "data";
49
+ }];
50
+ readonly name: "_txs";
51
+ readonly type: "tuple[]";
52
+ }, {
53
+ readonly type: "uint256";
54
+ readonly name: "_nonce";
55
+ }, {
56
+ readonly type: "bytes";
57
+ readonly name: "_signature";
58
+ }];
59
+ readonly outputs: readonly [];
60
+ readonly payable: false;
61
+ readonly stateMutability: "nonpayable";
62
+ }];
33
63
  };
34
64
  };
35
65
  export type SequenceSigner = {
@@ -1,14 +1,17 @@
1
1
  import { Flow } from '@imtbl/metrics';
2
- import { Signer, JsonRpcProvider } from 'ethers';
2
+ import type { PublicClient } from 'viem';
3
3
  import GuardianClient from '../guardian';
4
+ import { TypedDataPayload } from './types';
4
5
  import { RelayerClient } from './relayerClient';
6
+ import type { WalletSigner } from '../types';
5
7
  export type SignTypedDataV4Params = {
6
- ethSigner: Signer;
7
- rpcProvider: JsonRpcProvider;
8
+ ethSigner: WalletSigner;
9
+ rpcProvider: PublicClient;
8
10
  relayerClient: RelayerClient;
9
11
  method: string;
10
12
  params: Array<any>;
11
13
  guardianClient: GuardianClient;
12
14
  flow: Flow;
13
15
  };
16
+ export declare const transformTypedData: (typedData: string | object, chainId: bigint) => TypedDataPayload;
14
17
  export declare const signTypedDataV4: ({ params, method, ethSigner, rpcProvider, relayerClient, guardianClient, flow, }: SignTypedDataV4Params) => Promise<string>;
@@ -1,10 +1,21 @@
1
1
  import { Flow } from '@imtbl/metrics';
2
- import { Signer, TransactionRequest, JsonRpcProvider, BigNumberish } from 'ethers';
2
+ import type { PublicClient, Hex } from 'viem';
3
3
  import { RelayerClient } from './relayerClient';
4
4
  import GuardianClient from '../guardian';
5
+ import type { WalletSigner } from '../types';
6
+ /**
7
+ * Transaction request type compatible with eth_sendTransaction
8
+ */
9
+ export interface TransactionRequest {
10
+ to?: string;
11
+ data?: Hex | string | null;
12
+ value?: bigint;
13
+ nonce?: bigint;
14
+ chainId?: bigint | number;
15
+ }
5
16
  export type TransactionParams = {
6
- ethSigner: Signer;
7
- rpcProvider: JsonRpcProvider;
17
+ ethSigner: WalletSigner;
18
+ rpcProvider: PublicClient;
8
19
  guardianClient: GuardianClient;
9
20
  relayerClient: RelayerClient;
10
21
  zkEvmAddress: string;
@@ -24,7 +35,7 @@ export declare const prepareAndSignTransaction: ({ transactionRequest, ethSigner
24
35
  }) => Promise<{
25
36
  signedTransactions: string;
26
37
  relayerId: string;
27
- nonce: BigNumberish;
38
+ nonce: bigint;
28
39
  }>;
29
40
  export declare const prepareAndSignEjectionTransaction: ({ transactionRequest, ethSigner, zkEvmAddress, flow, }: EjectionTransactionParams & {
30
41
  transactionRequest: TransactionRequest;
@@ -1,4 +1,3 @@
1
- import { BigNumberish } from 'ethers';
2
1
  import { JsonRpcError } from './JsonRpcError';
3
2
  export declare enum RelayerTransactionStatus {
4
3
  PENDING = "PENDING",
@@ -24,19 +23,19 @@ export interface FeeOption {
24
23
  }
25
24
  export interface MetaTransaction {
26
25
  to: string;
27
- value?: BigNumberish | null;
26
+ value?: bigint | null;
28
27
  data?: string | null;
29
- nonce?: BigNumberish;
30
- gasLimit?: BigNumberish;
28
+ nonce?: bigint;
29
+ gasLimit?: bigint;
31
30
  delegateCall?: boolean;
32
31
  revertOnError?: boolean;
33
32
  }
34
33
  export interface MetaTransactionNormalised {
35
34
  delegateCall: boolean;
36
35
  revertOnError: boolean;
37
- gasLimit: BigNumberish;
36
+ gasLimit: bigint;
38
37
  target: string;
39
- value: BigNumberish;
38
+ value: bigint;
40
39
  data: string;
41
40
  }
42
41
  export interface TypedDataPayload {
@@ -1,13 +1,14 @@
1
1
  import { MultiRollupApiClients } from '@imtbl/generated-clients';
2
2
  import { Flow } from '@imtbl/metrics';
3
- import { Signer, JsonRpcProvider } from 'ethers';
3
+ import type { PublicClient } from 'viem';
4
4
  import { Auth } from '@imtbl/auth';
5
+ import type { WalletSigner } from '../../types';
5
6
  export type RegisterZkEvmUserInput = {
6
7
  auth: Auth;
7
- ethSigner: Signer;
8
+ ethSigner: WalletSigner;
8
9
  multiRollupApiClients: MultiRollupApiClients;
9
10
  accessToken: string;
10
- rpcProvider: JsonRpcProvider;
11
+ rpcProvider: PublicClient;
11
12
  flow: Flow;
12
13
  };
13
14
  export declare function registerZkEvmUser({ auth, ethSigner, multiRollupApiClients, accessToken, rpcProvider, flow, }: RegisterZkEvmUserInput): Promise<string>;
@@ -1,21 +1,22 @@
1
- import { BigNumberish, Signer, JsonRpcProvider } from 'ethers';
1
+ import { type PublicClient, type Hex } from 'viem';
2
2
  import { MetaTransaction, MetaTransactionNormalised, TypedDataPayload } from './types';
3
+ import type { WalletSigner } from '../types';
3
4
  export declare const getNormalisedTransactions: (txs: MetaTransaction[]) => MetaTransactionNormalised[];
4
- export declare const digestOfTransactionsAndNonce: (nonce: BigNumberish, normalisedTransactions: MetaTransactionNormalised[]) => string;
5
- export declare const encodedTransactions: (normalisedTransactions: MetaTransactionNormalised[]) => string;
5
+ export declare const digestOfTransactionsAndNonce: (nonce: bigint, normalisedTransactions: MetaTransactionNormalised[]) => Hex;
6
+ export declare const encodedTransactions: (normalisedTransactions: MetaTransactionNormalised[]) => Hex;
6
7
  /**
7
- * This helper function is used to coerce the type <BigNumber | undefined> to BigNumber for the
8
+ * This helper function is used to coerce the type <bigint | undefined> to bigint for the
8
9
  * getNonce function above.
9
- * @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
10
- * @returns {BigNumber} The passed in nonceSpace or instead initialises the nonce to 0.
10
+ * @param nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
11
+ * @returns The passed in nonceSpace or instead initialises the nonce to 0.
11
12
  */
12
13
  export declare const coerceNonceSpace: (nonceSpace?: bigint) => bigint;
13
14
  /**
14
15
  * This helper function is used to encode the nonce into a 256 bit value where the space is encoded into
15
16
  * the first 160 bits, and the nonce the remaining 96 bits.
16
- * @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
17
- * @param nonce {BigNumber} nonce - Sequential number starting at 0, and incrementing in single steps e.g. 0,1,2,...
18
- * @returns {BigNumber} The encoded value where the space is left shifted 96 bits, and the nonce is in the first 96 bits.
17
+ * @param nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
18
+ * @param nonce - Sequential number starting at 0, and incrementing in single steps e.g. 0,1,2,...
19
+ * @returns The encoded value where the space is left shifted 96 bits, and the nonce is in the first 96 bits.
19
20
  */
20
21
  export declare const encodeNonce: (nonceSpace: bigint, nonce: bigint) => bigint;
21
22
  /**
@@ -24,10 +25,10 @@ export declare const encodeNonce: (nonceSpace: bigint, nonce: bigint) => bigint;
24
25
  * wallet address do not. This function overload can be used to invoke transactions in parallel per smart
25
26
  * contract wallet if required.
26
27
  */
27
- export declare const getNonce: (rpcProvider: JsonRpcProvider, smartContractWalletAddress: string, nonceSpace?: bigint) => Promise<bigint>;
28
- export declare const encodeMessageSubDigest: (chainId: bigint, walletAddress: string, digest: string) => string;
29
- export declare const signMetaTransactions: (metaTransactions: MetaTransaction[], nonce: BigNumberish, chainId: bigint, walletAddress: string, signer: Signer) => Promise<string>;
28
+ export declare const getNonce: (rpcProvider: PublicClient, smartContractWalletAddress: string, nonceSpace?: bigint) => Promise<bigint>;
29
+ export declare const encodeMessageSubDigest: (chainId: bigint, walletAddress: string, digest: string) => Hex;
30
+ export declare const signMetaTransactions: (metaTransactions: MetaTransaction[], nonce: bigint, chainId: bigint, walletAddress: string, signer: WalletSigner) => Promise<string>;
30
31
  export declare const packSignatures: (EOASignature: string, EOAAddress: string, relayerSignature: string) => string;
31
- export declare const signAndPackTypedData: (typedData: TypedDataPayload, relayerSignature: string, chainId: bigint, walletAddress: string, signer: Signer) => Promise<string>;
32
- export declare const signERC191Message: (chainId: bigint, payload: string, signer: Signer, walletAddress: string) => Promise<string>;
32
+ export declare const signAndPackTypedData: (typedData: TypedDataPayload, relayerSignature: string, chainId: bigint, walletAddress: string, signer: WalletSigner) => Promise<string>;
33
+ export declare const signERC191Message: (chainId: bigint, payload: string, signer: WalletSigner, walletAddress: string) => Promise<string>;
33
34
  export declare const getEip155ChainId: (chainId: number) => string;
@@ -1,9 +1,8 @@
1
1
  import { MultiRollupApiClients } from '@imtbl/generated-clients';
2
- import { Signer } from 'ethers';
3
2
  import { Provider, RequestArguments } from './types';
4
3
  import { Auth, TypedEventEmitter } from '@imtbl/auth';
5
4
  import { WalletConfiguration } from '../config';
6
- import { PassportEventMap, User } from '../types';
5
+ import { PassportEventMap, User, WalletSigner } from '../types';
7
6
  import GuardianClient from '../guardian';
8
7
  export type ZkEvmProviderInput = {
9
8
  auth: Auth;
@@ -11,7 +10,7 @@ export type ZkEvmProviderInput = {
11
10
  multiRollupApiClients: MultiRollupApiClients;
12
11
  passportEventEmitter: TypedEventEmitter<PassportEventMap>;
13
12
  guardianClient: GuardianClient;
14
- ethSigner: Signer;
13
+ ethSigner: WalletSigner;
15
14
  user: User | null;
16
15
  sessionActivityApiUrl: string | null;
17
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imtbl/wallet",
3
- "version": "2.12.3",
3
+ "version": "2.12.4-alpha.1",
4
4
  "description": "Wallet SDK for Immutable",
5
5
  "author": "Immutable",
6
6
  "bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
@@ -25,11 +25,10 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@imtbl/auth": "2.12.3",
29
- "@imtbl/generated-clients": "2.12.3",
30
- "@imtbl/metrics": "2.12.3",
31
- "@imtbl/toolkit": "2.12.3",
32
- "ethers": "^6.13.4"
28
+ "@imtbl/auth": "2.12.4-alpha.1",
29
+ "@imtbl/generated-clients": "2.12.4-alpha.1",
30
+ "@imtbl/metrics": "2.12.4-alpha.1",
31
+ "viem": "~2.18.0"
33
32
  },
34
33
  "devDependencies": {
35
34
  "@swc/core": "^1.3.36",
@@ -1,5 +1,5 @@
1
1
  import * as GeneratedClients from '@imtbl/generated-clients';
2
- import { BigNumberish, ZeroAddress } from 'ethers';
2
+ import { zeroAddress } from 'viem';
3
3
  import { Auth, IAuthConfiguration } from '@imtbl/auth';
4
4
  import ConfirmationScreen from '../confirmation/confirmation';
5
5
  import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from '../zkEvm/JsonRpcError';
@@ -37,7 +37,7 @@ const transactionRejectedCrossSdkBridgeError = 'Transaction requires confirmatio
37
37
  + ' supported in this environment. Please contact Immutable support if you need to enable this feature.';
38
38
 
39
39
  export const convertBigNumberishToString = (
40
- value: BigNumberish,
40
+ value: bigint,
41
41
  ): string => BigInt(value).toString();
42
42
 
43
43
  const transformGuardianTransactions = (
@@ -48,7 +48,7 @@ const transformGuardianTransactions = (
48
48
  delegateCall: t.delegateCall === true,
49
49
  revertOnError: t.revertOnError === true,
50
50
  gasLimit: t.gasLimit ? convertBigNumberishToString(t.gasLimit) : '0',
51
- target: t.to ?? ZeroAddress,
51
+ target: t.to ?? zeroAddress,
52
52
  value: t.value ? convertBigNumberishToString(t.value) : '0',
53
53
  data: t.data ? t.data.toString() : '0x',
54
54
  }));
@@ -1,11 +1,10 @@
1
1
  /* eslint-disable no-bitwise */
2
- import { AbstractSigner, Signer } from 'ethers';
3
2
  import { MagicTeeApiClients } from '@imtbl/generated-clients';
4
3
  import { Flow, trackDuration } from '@imtbl/metrics';
5
4
  import { WalletError, WalletErrorType } from '../errors';
6
5
  import { Auth } from '@imtbl/auth';
7
6
  import { withMetricsAsync } from '../utils/metrics';
8
- import { isUserZkEvm, User } from '../types';
7
+ import { isUserZkEvm, User, WalletSigner } from '../types';
9
8
  import { isAxiosError } from '../utils/http';
10
9
 
11
10
  const CHAIN_IDENTIFIER = 'ETH';
@@ -58,7 +57,11 @@ const toBase64 = (value: string): string => {
58
57
  return output;
59
58
  };
60
59
 
61
- export default class MagicTEESigner extends AbstractSigner {
60
+ /**
61
+ * MagicTEESigner implements the WalletSigner interface for Magic TEE-based signing.
62
+ * This signer delegates cryptographic operations to the Magic TEE service.
63
+ */
64
+ export default class MagicTEESigner implements WalletSigner {
62
65
  private readonly auth: Auth;
63
66
 
64
67
  private readonly magicTeeApiClient: MagicTeeApiClients;
@@ -68,7 +71,6 @@ export default class MagicTEESigner extends AbstractSigner {
68
71
  private createWalletPromise: Promise<UserWallet> | null = null;
69
72
 
70
73
  constructor(auth: Auth, magicTeeApiClient: MagicTeeApiClients) {
71
- super();
72
74
  this.auth = auth;
73
75
  this.magicTeeApiClient = magicTeeApiClient;
74
76
  }
@@ -184,19 +186,19 @@ export default class MagicTEESigner extends AbstractSigner {
184
186
  };
185
187
  }
186
188
 
187
- public async getAddress(): Promise<string> {
189
+ public async getAddress(): Promise<`0x${string}`> {
188
190
  const userWallet = await this.getUserWallet();
189
- return userWallet.walletAddress;
191
+ return userWallet.walletAddress as `0x${string}`;
190
192
  }
191
193
 
192
- public async signMessage(message: string | Uint8Array): Promise<string> {
194
+ public async signMessage(message: string | Uint8Array): Promise<`0x${string}`> {
193
195
  // Call getUserWallet to ensure that the createWallet endpoint has been called at least once,
194
196
  // as this is a prerequisite for signing messages.
195
197
  await this.getUserWallet();
196
198
 
197
199
  const messageToSign = message instanceof Uint8Array ? `0x${toHex(message)}` : message;
198
200
  const user = await this.getUserOrThrow();
199
- const headers = await MagicTEESigner.getHeaders(user);
201
+ const headers = MagicTEESigner.getHeaders(user);
200
202
 
201
203
  return withMetricsAsync(async (flow: Flow) => {
202
204
  try {
@@ -217,7 +219,7 @@ export default class MagicTEESigner extends AbstractSigner {
217
219
  Math.round(performance.now() - startTime),
218
220
  );
219
221
 
220
- return response.data.signature;
222
+ return response.data.signature as `0x${string}`;
221
223
  } catch (error) {
222
224
  let errorMessage: string = 'MagicTEE: Failed to sign message using EOA';
223
225
 
@@ -235,19 +237,4 @@ export default class MagicTEESigner extends AbstractSigner {
235
237
  }
236
238
  }, 'magicSignMessage');
237
239
  }
238
-
239
- // eslint-disable-next-line class-methods-use-this
240
- connect(): Signer {
241
- throw new Error('Method not implemented.');
242
- }
243
-
244
- // eslint-disable-next-line class-methods-use-this
245
- signTransaction(): Promise<string> {
246
- throw new Error('Method not implemented.');
247
- }
248
-
249
- // eslint-disable-next-line class-methods-use-this
250
- signTypedData(): Promise<string> {
251
- throw new Error('Method not implemented.');
252
- }
253
240
  }
package/src/types.ts CHANGED
@@ -2,9 +2,19 @@ import { Flow } from '@imtbl/metrics';
2
2
  import {
3
3
  Auth, TypedEventEmitter, type AuthEventMap,
4
4
  } from '@imtbl/auth';
5
- import { BigNumberish } from 'ethers';
6
5
  import { JsonRpcError } from './zkEvm/JsonRpcError';
7
6
 
7
+ /**
8
+ * A viem-compatible signer interface for wallet operations.
9
+ * This replaces ethers' AbstractSigner/Signer.
10
+ */
11
+ export interface WalletSigner {
12
+ /** Get the wallet address */
13
+ getAddress(): Promise<`0x${string}`>;
14
+ /** Sign a message (EIP-191 personal_sign) */
15
+ signMessage(message: string | Uint8Array): Promise<`0x${string}`>;
16
+ }
17
+
8
18
  // Re-export auth types for convenience
9
19
  export type {
10
20
  User, UserProfile, UserZkEvm, DirectLoginMethod, AuthEventMap,
@@ -83,10 +93,10 @@ export interface TypedDataPayload {
83
93
 
84
94
  export interface MetaTransaction {
85
95
  to: string;
86
- value?: BigNumberish | null;
96
+ value?: bigint | null;
87
97
  data?: string | null;
88
- nonce?: BigNumberish;
89
- gasLimit?: BigNumberish;
98
+ nonce?: bigint;
99
+ gasLimit?: bigint;
90
100
  delegateCall?: boolean;
91
101
  revertOnError?: boolean;
92
102
  }
@@ -94,9 +104,9 @@ export interface MetaTransaction {
94
104
  export interface MetaTransactionNormalised {
95
105
  delegateCall: boolean;
96
106
  revertOnError: boolean;
97
- gasLimit: BigNumberish;
107
+ gasLimit: bigint;
98
108
  target: string;
99
- value: BigNumberish;
109
+ value: bigint;
100
110
  data: string;
101
111
  }
102
112
 
@@ -0,0 +1,85 @@
1
+ import type { WalletSigner } from '../types';
2
+
3
+ /**
4
+ * Signature components for Ethereum signatures
5
+ */
6
+ type SignatureOptions = {
7
+ r: bigint;
8
+ s: bigint;
9
+ recoveryParam: number | null | undefined;
10
+ };
11
+
12
+ /**
13
+ * Adds '0x' prefix to a hex string if not present
14
+ */
15
+ function addHexPrefix(hex: string): string {
16
+ return hex.startsWith('0x') ? hex : `0x${hex}`;
17
+ }
18
+
19
+ /**
20
+ * Removes '0x' prefix from a hex string if present
21
+ */
22
+ function removeHexPrefix(hex: string): string {
23
+ return hex.startsWith('0x') ? hex.slice(2) : hex;
24
+ }
25
+
26
+ /**
27
+ * Pads a hex string to a specified length with leading zeros
28
+ */
29
+ function padLeft(str: string, length: number): string {
30
+ return str.padStart(length, '0');
31
+ }
32
+
33
+ /**
34
+ * Serializes Ethereum signature components into a hex string.
35
+ * This format is used for IMX registration with golang backend.
36
+ * @see https://github.com/ethers-io/ethers.js/issues/823
37
+ */
38
+ function serializeEthSignature(sig: SignatureOptions): string {
39
+ const rHex = padLeft(sig.r.toString(16), 64);
40
+ const sHex = padLeft(sig.s.toString(16), 64);
41
+ const vHex = padLeft(sig.recoveryParam?.toString(16) || '', 2);
42
+ return addHexPrefix(rHex + sHex + vHex);
43
+ }
44
+
45
+ /**
46
+ * Imports recovery parameter from hex string, normalizing v value
47
+ */
48
+ function importRecoveryParam(v: string): number | undefined {
49
+ if (!v.trim()) return undefined;
50
+
51
+ const vValue = parseInt(v, 16);
52
+ // If v >= 27, subtract 27 to get recovery param (0 or 1)
53
+ return vValue >= 27 ? vValue - 27 : vValue;
54
+ }
55
+
56
+ /**
57
+ * Deserializes a signature hex string into its components (r, s, v)
58
+ */
59
+ function deserializeSignature(sig: string, size = 64): SignatureOptions {
60
+ const cleanSig = removeHexPrefix(sig);
61
+ return {
62
+ r: BigInt(`0x${cleanSig.substring(0, size)}`),
63
+ s: BigInt(`0x${cleanSig.substring(size, size * 2)}`),
64
+ recoveryParam: importRecoveryParam(cleanSig.substring(size * 2, size * 2 + 2)),
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Signs a message with the provided signer and returns a serialized signature
70
+ * suitable for IMX registration and authorization.
71
+ *
72
+ * This is inlined from @imtbl/toolkit to avoid ethers dependency.
73
+ *
74
+ * @param payload - The message to sign
75
+ * @param signer - A WalletSigner implementation
76
+ * @returns The serialized signature as a hex string
77
+ */
78
+ export async function signRaw(
79
+ payload: string,
80
+ signer: WalletSigner,
81
+ ): Promise<string> {
82
+ const rawSignature = await signer.signMessage(payload);
83
+ const signature = deserializeSignature(rawSignature);
84
+ return serializeEthSignature(signature);
85
+ }
@@ -1,10 +1,38 @@
1
- import { getBytes, stripZerosLeft, toUtf8String } from 'ethers';
1
+ import { toBytes, type Hex } from 'viem';
2
+
3
+ /**
4
+ * Strip leading zero bytes from a Uint8Array
5
+ */
6
+ const stripZerosLeft = (bytes: Uint8Array): Uint8Array => {
7
+ let start = 0;
8
+ while (start < bytes.length && bytes[start] === 0) {
9
+ start++;
10
+ }
11
+ return bytes.slice(start);
12
+ };
13
+
14
+ /**
15
+ * Convert UTF-8 bytes to string
16
+ */
17
+ const toUtf8String = (bytes: Uint8Array): string => {
18
+ if (typeof TextDecoder !== 'undefined') {
19
+ return new TextDecoder('utf-8').decode(bytes);
20
+ }
21
+
22
+ // Fallback for environments without TextDecoder
23
+ let result = '';
24
+ for (let i = 0; i < bytes.length; i++) {
25
+ result += String.fromCharCode(bytes[i]);
26
+ }
27
+ return decodeURIComponent(escape(result));
28
+ };
2
29
 
3
30
  export const hexToString = (hex: string) => {
4
31
  if (!hex) return hex;
5
32
 
6
33
  try {
7
- const stripped = stripZerosLeft(getBytes(hex));
34
+ const bytes = toBytes(hex as Hex);
35
+ const stripped = stripZerosLeft(bytes);
8
36
  return toUtf8String(stripped);
9
37
  } catch (e) {
10
38
  return hex;
@@ -1,14 +1,15 @@
1
1
  import { Flow } from '@imtbl/metrics';
2
- import { Signer, JsonRpcProvider } from 'ethers';
2
+ import type { PublicClient } from 'viem';
3
3
  import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
4
4
  import { hexToString } from '../utils/string';
5
5
  import GuardianClient from '../guardian';
6
6
  import { RelayerClient } from './relayerClient';
7
7
  import { packSignatures, signERC191Message } from './walletHelpers';
8
+ import type { WalletSigner } from '../types';
8
9
 
9
10
  interface PersonalSignParams {
10
- ethSigner: Signer;
11
- rpcProvider: JsonRpcProvider;
11
+ ethSigner: WalletSigner;
12
+ rpcProvider: PublicClient;
12
13
  params: any[];
13
14
  zkEvmAddress: string;
14
15
  guardianClient: GuardianClient;
@@ -38,7 +39,7 @@ export const personalSign = async ({
38
39
 
39
40
  // Convert message into a string if it's a hex
40
41
  const payload = hexToString(message);
41
- const { chainId } = await rpcProvider.getNetwork();
42
+ const chainId = await rpcProvider.getChainId();
42
43
  flow.addEvent('endDetectNetwork');
43
44
  const chainIdBigNumber = BigInt(chainId);
44
45
 
@@ -46,7 +47,7 @@ export const personalSign = async ({
46
47
  const eoaSignaturePromise = signERC191Message(chainIdBigNumber, payload, ethSigner, fromAddress);
47
48
  eoaSignaturePromise.then(() => flow.addEvent('endEOASignature'));
48
49
 
49
- await guardianClient.evaluateERC191Message({ chainID: chainId, payload });
50
+ await guardianClient.evaluateERC191Message({ chainID: chainIdBigNumber, payload });
50
51
  flow.addEvent('endEvaluateERC191Message');
51
52
 
52
53
  const [eoaSignature, relayerSignature] = await Promise.all([