@imtbl/wallet 2.12.3-alpha.0 → 2.12.3

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 +32 -24
  2. package/dist/node/index.cjs +55 -47
  3. package/dist/node/index.js +34 -26
  4. package/dist/types/guardian/index.d.ts +2 -1
  5. package/dist/types/magic/magicTEESigner.d.ts +7 -8
  6. package/dist/types/types.d.ts +6 -15
  7. package/dist/types/zkEvm/personalSign.d.ts +3 -4
  8. package/dist/types/zkEvm/relayerClient.d.ts +6 -6
  9. package/dist/types/zkEvm/sequenceCompat.d.ts +30 -60
  10. package/dist/types/zkEvm/signTypedDataV4.d.ts +3 -6
  11. package/dist/types/zkEvm/transactionHelpers.d.ts +4 -15
  12. package/dist/types/zkEvm/types.d.ts +6 -5
  13. package/dist/types/zkEvm/user/registerZkEvmUser.d.ts +3 -4
  14. package/dist/types/zkEvm/walletHelpers.d.ts +14 -15
  15. package/dist/types/zkEvm/zkEvmProvider.d.ts +3 -2
  16. package/package.json +6 -5
  17. package/src/guardian/index.ts +3 -3
  18. package/src/magic/magicTEESigner.ts +24 -11
  19. package/src/types.ts +6 -16
  20. package/src/utils/string.ts +2 -30
  21. package/src/zkEvm/personalSign.ts +5 -6
  22. package/src/zkEvm/relayerClient.ts +11 -11
  23. package/src/zkEvm/sendDeployTransactionAndPersonalSign.ts +1 -1
  24. package/src/zkEvm/sequenceCompat.ts +24 -26
  25. package/src/zkEvm/sessionActivity/sessionActivity.ts +3 -7
  26. package/src/zkEvm/signEjectionTransaction.ts +1 -1
  27. package/src/zkEvm/signTypedDataV4.ts +10 -16
  28. package/src/zkEvm/transactionHelpers.ts +11 -20
  29. package/src/zkEvm/types.ts +6 -5
  30. package/src/zkEvm/user/registerZkEvmUser.ts +8 -9
  31. package/src/zkEvm/walletHelpers.ts +63 -112
  32. package/src/zkEvm/zkEvmProvider.ts +32 -32
  33. package/dist/types/utils/crypto.d.ts +0 -12
  34. package/src/utils/crypto.ts +0 -85
  35. package/src/zkEvm/signTypedDataV4.test.ts +0 -125
@@ -1,24 +1,15 @@
1
1
  import {
2
- keccak256,
3
- encodePacked,
4
- encodeAbiParameters,
5
- parseAbiParameters,
6
- getContract,
7
- encodeFunctionData,
8
- toBytes,
9
- hashMessage,
10
- hashTypedData,
11
- zeroAddress,
12
- type PublicClient,
13
- type Hex,
14
- } from 'viem';
2
+ BigNumberish, Contract, getBytes, hashMessage,
3
+ Interface, keccak256, Signer, solidityPacked, ZeroAddress,
4
+ TypedDataEncoder, JsonRpcProvider, AbiCoder,
5
+ isError,
6
+ } from 'ethers';
15
7
  import { MetaTransaction, MetaTransactionNormalised, TypedDataPayload } from './types';
16
8
  import {
17
9
  decodeSequenceSignatureV1,
18
10
  encodeSequenceSignatureV1,
19
11
  walletContracts,
20
12
  } from './sequenceCompat';
21
- import type { WalletSigner } from '../types';
22
13
 
23
14
  const SIGNATURE_WEIGHT = 1; // Weight of a single signature in the multi-sig
24
15
  const TRANSACTION_SIGNATURE_THRESHOLD = 1; // Total required weight in the multi-sig for a transaction
@@ -26,74 +17,56 @@ const PACKED_SIGNATURE_THRESHOLD = 2; // Total required weight in the multi-sig
26
17
 
27
18
  const ETH_SIGN_FLAG = '02';
28
19
  const ETH_SIGN_PREFIX = '\x19\x01';
29
-
30
- // ABI parameter type for meta transactions array
31
- const META_TRANSACTIONS_ABI_TYPE = parseAbiParameters(
32
- '(bool delegateCall, bool revertOnError, uint256 gasLimit, address target, uint256 value, bytes data)[]',
33
- );
20
+ const META_TRANSACTIONS_TYPE = `tuple(
21
+ bool delegateCall,
22
+ bool revertOnError,
23
+ uint256 gasLimit,
24
+ address target,
25
+ uint256 value,
26
+ bytes data
27
+ )[]`;
34
28
 
35
29
  export const getNormalisedTransactions = (txs: MetaTransaction[]): MetaTransactionNormalised[] => txs.map((t) => ({
36
30
  delegateCall: t.delegateCall === true,
37
31
  revertOnError: t.revertOnError === true,
38
32
  gasLimit: t.gasLimit ?? BigInt(0),
39
- target: (t.to ?? zeroAddress) as `0x${string}`,
33
+ target: t.to ?? ZeroAddress,
40
34
  value: t.value ?? BigInt(0),
41
- data: (t.data ?? '0x') as `0x${string}`,
35
+ data: t.data ?? '0x',
42
36
  }));
43
37
 
44
38
  export const digestOfTransactionsAndNonce = (
45
- nonce: bigint,
39
+ nonce: BigNumberish,
46
40
  normalisedTransactions: MetaTransactionNormalised[],
47
- ): Hex => {
48
- // Convert normalised transactions to the format expected by encodeAbiParameters
49
- const txsForEncoding = normalisedTransactions.map((t) => ({
50
- delegateCall: t.delegateCall,
51
- revertOnError: t.revertOnError,
52
- gasLimit: t.gasLimit,
53
- target: t.target as `0x${string}`,
54
- value: t.value,
55
- data: t.data as `0x${string}`,
56
- }));
57
-
58
- const packMetaTransactionsNonceData = encodeAbiParameters(
59
- [{ type: 'uint256' }, ...META_TRANSACTIONS_ABI_TYPE],
60
- [nonce, txsForEncoding],
41
+ ): string => {
42
+ const packMetaTransactionsNonceData = AbiCoder.defaultAbiCoder().encode(
43
+ ['uint256', META_TRANSACTIONS_TYPE],
44
+ [nonce, normalisedTransactions],
61
45
  );
62
46
  return keccak256(packMetaTransactionsNonceData);
63
47
  };
64
48
 
65
49
  export const encodedTransactions = (
66
50
  normalisedTransactions: MetaTransactionNormalised[],
67
- ): Hex => {
68
- const txsForEncoding = normalisedTransactions.map((t) => ({
69
- delegateCall: t.delegateCall,
70
- revertOnError: t.revertOnError,
71
- gasLimit: t.gasLimit,
72
- target: t.target as `0x${string}`,
73
- value: t.value,
74
- data: t.data as `0x${string}`,
75
- }));
76
-
77
- return encodeAbiParameters(
78
- META_TRANSACTIONS_ABI_TYPE,
79
- [txsForEncoding],
80
- );
81
- };
51
+ ): string => AbiCoder.defaultAbiCoder().encode(
52
+ [META_TRANSACTIONS_TYPE],
53
+ [normalisedTransactions],
54
+ );
82
55
 
83
56
  /**
84
- * This helper function is used to coerce the type <bigint | undefined> to bigint for the
57
+ * This helper function is used to coerce the type <BigNumber | undefined> to BigNumber for the
85
58
  * getNonce function above.
86
- * @param nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
87
- * @returns The passed in nonceSpace or instead initialises the nonce to 0.
59
+ * @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
60
+ * @returns {BigNumber} The passed in nonceSpace or instead initialises the nonce to 0.
88
61
  */
89
62
  export const coerceNonceSpace = (nonceSpace?: bigint): bigint => nonceSpace || 0n;
90
63
 
91
64
  /**
92
65
  * This helper function is used to encode the nonce into a 256 bit value where the space is encoded into
93
66
  * the first 160 bits, and the nonce the remaining 96 bits.
94
- * @param nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
95
- * @param nonce - Sequential number starting at 0, and incrementing in single steps e.g. 0,1,2,...
96
- * @returns The encoded value where the space is left shifted 96 bits, and the nonce is in the first 96 bits.
67
+ * @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
68
+ * @param nonce {BigNumber} nonce - Sequential number starting at 0, and incrementing in single steps e.g. 0,1,2,...
69
+ * @returns {BigNumber} The encoded value where the space is left shifted 96 bits, and the nonce is in the first 96 bits.
97
70
  */
98
71
  export const encodeNonce = (nonceSpace: bigint, nonce: bigint): bigint => {
99
72
  const shiftedSpace = BigInt(nonceSpace) * (2n ** 96n);
@@ -107,31 +80,25 @@ export const encodeNonce = (nonceSpace: bigint, nonce: bigint): bigint => {
107
80
  * contract wallet if required.
108
81
  */
109
82
  export const getNonce = async (
110
- rpcProvider: PublicClient,
83
+ rpcProvider: JsonRpcProvider,
111
84
  smartContractWalletAddress: string,
112
85
  nonceSpace?: bigint,
113
86
  ): Promise<bigint> => {
114
87
  try {
115
- const contract = getContract({
116
- address: smartContractWalletAddress as `0x${string}`,
117
- abi: walletContracts.mainModule.abi,
118
- client: rpcProvider,
119
- });
88
+ const contract = new Contract(
89
+ smartContractWalletAddress,
90
+ walletContracts.mainModule.abi,
91
+ rpcProvider,
92
+ );
120
93
  const space: bigint = coerceNonceSpace(nonceSpace); // Default nonce space is 0
121
- const result = await contract.read.readNonce([space]);
94
+ const result = await contract.readNonce(space);
122
95
  if (typeof result === 'bigint') {
123
96
  return encodeNonce(space, result);
124
97
  }
125
- throw new Error('Unexpected result from contract.readNonce() call.');
98
+ throw new Error('Unexpected result from contract.nonce() call.');
126
99
  } catch (error) {
127
- // Check if the error is due to contract not being deployed (similar to ethers BAD_DATA)
128
- // In viem, this typically manifests as a ContractFunctionExecutionError with empty return data
129
- if (error instanceof Error && (
130
- error.message.includes('returned no data')
131
- || error.message.includes('execution reverted')
132
- || error.message.includes('ContractFunctionExecutionError')
133
- )) {
134
- // The most likely reason for this error is that the smart contract wallet
100
+ if (isError(error, 'BAD_DATA')) {
101
+ // The most likely reason for a BAD_DATA error is that the smart contract wallet
135
102
  // has not been deployed yet, so we should default to a nonce of 0.
136
103
  return BigInt(0);
137
104
  }
@@ -140,19 +107,19 @@ export const getNonce = async (
140
107
  }
141
108
  };
142
109
 
143
- export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, digest: string): Hex => (
144
- encodePacked(
110
+ export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, digest: string): string => (
111
+ solidityPacked(
145
112
  ['string', 'uint256', 'address', 'bytes32'],
146
- [ETH_SIGN_PREFIX, chainId, walletAddress as `0x${string}`, digest as `0x${string}`],
113
+ [ETH_SIGN_PREFIX, chainId, walletAddress, digest],
147
114
  )
148
115
  );
149
116
 
150
117
  export const signMetaTransactions = async (
151
118
  metaTransactions: MetaTransaction[],
152
- nonce: bigint,
119
+ nonce: BigNumberish,
153
120
  chainId: bigint,
154
121
  walletAddress: string,
155
- signer: WalletSigner,
122
+ signer: Signer,
156
123
  ): Promise<string> => {
157
124
  const normalisedMetaTransactions = getNormalisedTransactions(metaTransactions);
158
125
 
@@ -163,7 +130,7 @@ export const signMetaTransactions = async (
163
130
  const hash = keccak256(completePayload);
164
131
 
165
132
  // Sign the digest
166
- const hashArray = toBytes(hash);
133
+ const hashArray = getBytes(hash);
167
134
  const ethsigNoType = await signer.signMessage(hashArray);
168
135
  const signedDigest = `${ethsigNoType}${ETH_SIGN_FLAG}`;
169
136
 
@@ -181,22 +148,13 @@ export const signMetaTransactions = async (
181
148
  ],
182
149
  });
183
150
 
184
- // Encode the transaction using viem's encodeFunctionData
185
- // Convert normalised transactions to tuple format for encoding
186
- const txsForEncoding = normalisedMetaTransactions.map((t) => ({
187
- delegateCall: t.delegateCall,
188
- revertOnError: t.revertOnError,
189
- gasLimit: t.gasLimit,
190
- target: t.target as `0x${string}`,
191
- value: t.value,
192
- data: t.data as `0x${string}`,
193
- }));
194
-
195
- return encodeFunctionData({
196
- abi: walletContracts.mainModule.abi,
197
- functionName: 'execute',
198
- args: [txsForEncoding, nonce, encodedSignature as `0x${string}`],
199
- });
151
+ // Encode the transaction;
152
+ const walletInterface = new Interface(walletContracts.mainModule.abi);
153
+ return walletInterface.encodeFunctionData(walletInterface.getFunction('execute') ?? '', [
154
+ normalisedMetaTransactions,
155
+ nonce,
156
+ encodedSignature,
157
+ ]);
200
158
  };
201
159
 
202
160
  const decodeRelayerSignature = (relayerSignature: string) => {
@@ -247,28 +205,21 @@ export const signAndPackTypedData = async (
247
205
  relayerSignature: string,
248
206
  chainId: bigint,
249
207
  walletAddress: string,
250
- signer: WalletSigner,
208
+ signer: Signer,
251
209
  ): Promise<string> => {
252
- // viem's hashTypedData handles EIP712Domain automatically
210
+ // Ethers auto-generates the EIP712Domain type in the TypedDataEncoder, and so it needs to be removed
253
211
  const types = { ...typedData.types };
254
- // Remove EIP712Domain from types as viem handles it
255
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
256
- const { EIP712Domain, ...typesWithoutDomain } = types;
257
-
258
- // Hash the EIP712 payload
259
- const typedDataHash = hashTypedData({
260
- domain: typedData.domain as Parameters<typeof hashTypedData>[0]['domain'],
261
- types: typesWithoutDomain,
262
- primaryType: typedData.primaryType,
263
- message: typedData.message,
264
- });
212
+ // @ts-ignore
213
+ delete types.EIP712Domain;
265
214
 
215
+ // Hash the EIP712 payload and generate the complete payload
216
+ const typedDataHash = TypedDataEncoder.hash(typedData.domain, types, typedData.message);
266
217
  const messageSubDigest = encodeMessageSubDigest(chainId, walletAddress, typedDataHash);
267
218
  const hash = keccak256(messageSubDigest);
268
219
 
269
220
  // Sign the sub digest
270
221
  // https://github.com/immutable/wallet-contracts/blob/7824b5f24b2e0eb2dc465ecb5cd71f3984556b73/src/contracts/modules/commons/ModuleAuth.sol#L155
271
- const hashArray = toBytes(hash);
222
+ const hashArray = getBytes(hash);
272
223
  const eoaSignature = await signer.signMessage(hashArray);
273
224
  const eoaAddress = await signer.getAddress();
274
225
 
@@ -278,16 +229,16 @@ export const signAndPackTypedData = async (
278
229
  export const signERC191Message = async (
279
230
  chainId: bigint,
280
231
  payload: string,
281
- signer: WalletSigner,
232
+ signer: Signer,
282
233
  walletAddress: string,
283
234
  ): Promise<string> => {
284
- // Generate digest using viem's hashMessage
235
+ // Generate digest
285
236
  const digest = hashMessage(payload);
286
237
 
287
238
  // Generate subDigest
288
239
  const subDigest = encodeMessageSubDigest(chainId, walletAddress, digest);
289
240
  const subDigestHash = keccak256(subDigest);
290
- const subDigestHashArray = toBytes(subDigestHash);
241
+ const subDigestHashArray = getBytes(subDigestHash);
291
242
 
292
243
  return signer.signMessage(subDigestHashArray);
293
244
  };
@@ -3,11 +3,8 @@ import {
3
3
  Flow, identify, trackError, trackFlow,
4
4
  } from '@imtbl/metrics';
5
5
  import {
6
- createPublicClient,
7
- http,
8
- toHex,
9
- type PublicClient,
10
- } from 'viem';
6
+ JsonRpcProvider, Signer, toBeHex,
7
+ } from 'ethers';
11
8
  import {
12
9
  Provider,
13
10
  ProviderEvent,
@@ -17,7 +14,7 @@ import {
17
14
  import { Auth, TypedEventEmitter } from '@imtbl/auth';
18
15
  import { WalletConfiguration } from '../config';
19
16
  import {
20
- PassportEventMap, AuthEvents, WalletEvents, User, UserZkEvm, WalletSigner,
17
+ PassportEventMap, AuthEvents, WalletEvents, User, UserZkEvm,
21
18
  } from '../types';
22
19
  import { RelayerClient } from './relayerClient';
23
20
  import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from './JsonRpcError';
@@ -37,7 +34,7 @@ export type ZkEvmProviderInput = {
37
34
  multiRollupApiClients: MultiRollupApiClients;
38
35
  passportEventEmitter: TypedEventEmitter<PassportEventMap>;
39
36
  guardianClient: GuardianClient;
40
- ethSigner: WalletSigner;
37
+ ethSigner: Signer;
41
38
  user: User | null;
42
39
  sessionActivityApiUrl: string | null;
43
40
  };
@@ -63,13 +60,13 @@ export class ZkEvmProvider implements Provider {
63
60
 
64
61
  readonly #guardianClient: GuardianClient;
65
62
 
66
- readonly #rpcProvider: PublicClient; // Used for read
63
+ readonly #rpcProvider: JsonRpcProvider; // Used for read
67
64
 
68
65
  readonly #multiRollupApiClients: MultiRollupApiClients;
69
66
 
70
67
  readonly #relayerClient: RelayerClient;
71
68
 
72
- readonly #ethSigner: WalletSigner;
69
+ readonly #ethSigner: Signer;
73
70
 
74
71
  public readonly isPassport: boolean = true;
75
72
 
@@ -90,9 +87,9 @@ export class ZkEvmProvider implements Provider {
90
87
  this.#sessionActivityApiUrl = sessionActivityApiUrl;
91
88
  this.#ethSigner = ethSigner;
92
89
 
93
- // Create PublicClient for reading from the chain using viem
94
- this.#rpcProvider = createPublicClient({
95
- transport: http(this.#config.zkEvmRpcUrl),
90
+ // Create JsonRpcProvider for reading from the chain
91
+ this.#rpcProvider = new JsonRpcProvider(this.#config.zkEvmRpcUrl, undefined, {
92
+ staticNetwork: true,
96
93
  });
97
94
 
98
95
  // Create RelayerClient for transaction submission
@@ -350,34 +347,40 @@ export class ZkEvmProvider implements Provider {
350
347
  }
351
348
  }
352
349
  case 'eth_chainId': {
353
- // Get chain ID using viem's PublicClient
354
- const chainId = await this.#rpcProvider.getChainId();
355
- return toHex(chainId);
350
+ // Call detect network to fetch the chainId so to take advantage of
351
+ // the caching layer provided by StaticJsonRpcProvider.
352
+ // In case Passport is changed from StaticJsonRpcProvider to a
353
+ // JsonRpcProvider, this function will still work as expected given
354
+ // that detectNetwork call _uncachedDetectNetwork which will force
355
+ // the provider to re-fetch the chainId from remote.
356
+ const { chainId } = await this.#rpcProvider.getNetwork();
357
+ return toBeHex(chainId);
356
358
  }
357
- // Pass through methods - use viem's request method for raw RPC calls
359
+ // Pass through methods
358
360
  case 'eth_getBalance':
359
361
  case 'eth_getCode':
360
362
  case 'eth_getTransactionCount': {
361
363
  const [address, blockNumber] = request.params || [];
362
- return this.#rpcProvider.request({
363
- method: request.method as any,
364
- params: [address, blockNumber || 'latest'],
365
- });
364
+ return this.#rpcProvider.send(request.method, [
365
+ address,
366
+ blockNumber || 'latest',
367
+ ]);
366
368
  }
367
369
  case 'eth_getStorageAt': {
368
370
  const [address, storageSlot, blockNumber] = request.params || [];
369
- return this.#rpcProvider.request({
370
- method: 'eth_getStorageAt',
371
- params: [address, storageSlot, blockNumber || 'latest'],
372
- });
371
+ return this.#rpcProvider.send(request.method, [
372
+ address,
373
+ storageSlot,
374
+ blockNumber || 'latest',
375
+ ]);
373
376
  }
374
377
  case 'eth_call':
375
378
  case 'eth_estimateGas': {
376
379
  const [transaction, blockNumber] = request.params || [];
377
- return this.#rpcProvider.request({
378
- method: request.method as any,
379
- params: [transaction, blockNumber || 'latest'],
380
- });
380
+ return this.#rpcProvider.send(request.method, [
381
+ transaction,
382
+ blockNumber || 'latest',
383
+ ]);
381
384
  }
382
385
  case 'eth_gasPrice':
383
386
  case 'eth_blockNumber':
@@ -385,10 +388,7 @@ export class ZkEvmProvider implements Provider {
385
388
  case 'eth_getBlockByNumber':
386
389
  case 'eth_getTransactionByHash':
387
390
  case 'eth_getTransactionReceipt': {
388
- return this.#rpcProvider.request({
389
- method: request.method as any,
390
- params: (request.params || []) as any,
391
- });
391
+ return this.#rpcProvider.send(request.method, request.params || []);
392
392
  }
393
393
  case 'im_signEjectionTransaction': {
394
394
  const zkEvmAddress = await this.#getZkEvmAddress();
@@ -1,12 +0,0 @@
1
- import type { WalletSigner } from '../types';
2
- /**
3
- * Signs a message with the provided signer and returns a serialized signature
4
- * suitable for IMX registration and authorization.
5
- *
6
- * This is inlined from @imtbl/toolkit to avoid ethers dependency.
7
- *
8
- * @param payload - The message to sign
9
- * @param signer - A WalletSigner implementation
10
- * @returns The serialized signature as a hex string
11
- */
12
- export declare function signRaw(payload: string, signer: WalletSigner): Promise<string>;
@@ -1,85 +0,0 @@
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,125 +0,0 @@
1
- import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
2
-
3
- const { transformTypedData } = require('./signTypedDataV4');
4
-
5
- describe('transformTypedData', () => {
6
- const validTypedData = {
7
- types: {
8
- EIP712Domain: [
9
- { name: 'name', type: 'string' },
10
- { name: 'version', type: 'string' },
11
- { name: 'chainId', type: 'uint256' },
12
- { name: 'verifyingContract', type: 'address' },
13
- ],
14
- Person: [
15
- { name: 'name', type: 'string' },
16
- { name: 'wallet', type: 'address' },
17
- ],
18
- },
19
- domain: {
20
- name: 'Test',
21
- version: '1',
22
- chainId: 13473,
23
- verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
24
- },
25
- primaryType: 'Person',
26
- message: {
27
- name: 'Bob',
28
- wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
29
- },
30
- };
31
-
32
- describe('chainId normalization', () => {
33
- it('should normalize chainId from number to number', () => {
34
- const typedData = {
35
- ...validTypedData,
36
- domain: { ...validTypedData.domain, chainId: 13473 },
37
- };
38
-
39
- const result = transformTypedData(typedData, BigInt(13473));
40
-
41
- expect(result.domain.chainId).toBe(13473);
42
- expect(typeof result.domain.chainId).toBe('number');
43
- });
44
-
45
- it('should normalize chainId from string to number', () => {
46
- const typedData = {
47
- ...validTypedData,
48
- domain: { ...validTypedData.domain, chainId: '13473' },
49
- };
50
-
51
- const result = transformTypedData(typedData, BigInt(13473));
52
-
53
- expect(result.domain.chainId).toBe(13473);
54
- expect(typeof result.domain.chainId).toBe('number');
55
- });
56
-
57
- it('should normalize chainId from hex string to number', () => {
58
- const typedData = {
59
- ...validTypedData,
60
- domain: { ...validTypedData.domain, chainId: '0x34a1' }, // 13473 in hex
61
- };
62
-
63
- const result = transformTypedData(typedData, BigInt(13473));
64
-
65
- expect(result.domain.chainId).toBe(13473);
66
- expect(typeof result.domain.chainId).toBe('number');
67
- });
68
-
69
- it('should throw error when chainId does not match expected', () => {
70
- const typedData = {
71
- ...validTypedData,
72
- domain: { ...validTypedData.domain, chainId: 1 },
73
- };
74
-
75
- expect(() => transformTypedData(typedData, BigInt(13473))).toThrow(
76
- new JsonRpcError(RpcErrorCode.INVALID_PARAMS, 'Invalid chainId, expected 13473'),
77
- );
78
- });
79
-
80
- it('should allow missing chainId in domain', () => {
81
- const typedData = {
82
- ...validTypedData,
83
- domain: {
84
- name: 'Test',
85
- version: '1',
86
- verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
87
- },
88
- };
89
-
90
- const result = transformTypedData(typedData, BigInt(13473));
91
-
92
- expect(result.domain.chainId).toBeUndefined();
93
- });
94
- });
95
-
96
- describe('JSON string parsing', () => {
97
- it('should parse valid JSON string typed data', () => {
98
- const result = transformTypedData(JSON.stringify(validTypedData), BigInt(13473));
99
-
100
- expect(result.primaryType).toBe('Person');
101
- expect(result.domain.chainId).toBe(13473);
102
- });
103
-
104
- it('should throw error for invalid JSON string', () => {
105
- expect(() => transformTypedData('invalid json', BigInt(13473))).toThrow(JsonRpcError);
106
- });
107
- });
108
-
109
- describe('validation', () => {
110
- it('should throw error when required properties are missing', () => {
111
- const invalidTypedData = {
112
- types: {},
113
- domain: {},
114
- // missing primaryType and message
115
- };
116
-
117
- expect(() => transformTypedData(invalidTypedData, BigInt(13473))).toThrow(
118
- new JsonRpcError(
119
- RpcErrorCode.INVALID_PARAMS,
120
- 'Invalid typed data argument. The following properties are required: types, domain, primaryType, message',
121
- ),
122
- );
123
- });
124
- });
125
- });