@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.
- package/dist/browser/index.js +32 -24
- package/dist/node/index.cjs +55 -47
- package/dist/node/index.js +34 -26
- package/dist/types/guardian/index.d.ts +2 -1
- package/dist/types/magic/magicTEESigner.d.ts +7 -8
- package/dist/types/types.d.ts +6 -15
- package/dist/types/zkEvm/personalSign.d.ts +3 -4
- package/dist/types/zkEvm/relayerClient.d.ts +6 -6
- package/dist/types/zkEvm/sequenceCompat.d.ts +30 -60
- package/dist/types/zkEvm/signTypedDataV4.d.ts +3 -6
- package/dist/types/zkEvm/transactionHelpers.d.ts +4 -15
- package/dist/types/zkEvm/types.d.ts +6 -5
- package/dist/types/zkEvm/user/registerZkEvmUser.d.ts +3 -4
- package/dist/types/zkEvm/walletHelpers.d.ts +14 -15
- package/dist/types/zkEvm/zkEvmProvider.d.ts +3 -2
- package/package.json +6 -5
- package/src/guardian/index.ts +3 -3
- package/src/magic/magicTEESigner.ts +24 -11
- package/src/types.ts +6 -16
- package/src/utils/string.ts +2 -30
- package/src/zkEvm/personalSign.ts +5 -6
- package/src/zkEvm/relayerClient.ts +11 -11
- package/src/zkEvm/sendDeployTransactionAndPersonalSign.ts +1 -1
- package/src/zkEvm/sequenceCompat.ts +24 -26
- package/src/zkEvm/sessionActivity/sessionActivity.ts +3 -7
- package/src/zkEvm/signEjectionTransaction.ts +1 -1
- package/src/zkEvm/signTypedDataV4.ts +10 -16
- package/src/zkEvm/transactionHelpers.ts +11 -20
- package/src/zkEvm/types.ts +6 -5
- package/src/zkEvm/user/registerZkEvmUser.ts +8 -9
- package/src/zkEvm/walletHelpers.ts +63 -112
- package/src/zkEvm/zkEvmProvider.ts +32 -32
- package/dist/types/utils/crypto.d.ts +0 -12
- package/src/utils/crypto.ts +0 -85
- package/src/zkEvm/signTypedDataV4.test.ts +0 -125
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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:
|
|
33
|
+
target: t.to ?? ZeroAddress,
|
|
40
34
|
value: t.value ?? BigInt(0),
|
|
41
|
-
data:
|
|
35
|
+
data: t.data ?? '0x',
|
|
42
36
|
}));
|
|
43
37
|
|
|
44
38
|
export const digestOfTransactionsAndNonce = (
|
|
45
|
-
nonce:
|
|
39
|
+
nonce: BigNumberish,
|
|
46
40
|
normalisedTransactions: MetaTransactionNormalised[],
|
|
47
|
-
):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
):
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 <
|
|
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:
|
|
83
|
+
rpcProvider: JsonRpcProvider,
|
|
111
84
|
smartContractWalletAddress: string,
|
|
112
85
|
nonceSpace?: bigint,
|
|
113
86
|
): Promise<bigint> => {
|
|
114
87
|
try {
|
|
115
|
-
const contract =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
|
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.
|
|
98
|
+
throw new Error('Unexpected result from contract.nonce() call.');
|
|
126
99
|
} catch (error) {
|
|
127
|
-
|
|
128
|
-
|
|
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):
|
|
144
|
-
|
|
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
|
|
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:
|
|
119
|
+
nonce: BigNumberish,
|
|
153
120
|
chainId: bigint,
|
|
154
121
|
walletAddress: string,
|
|
155
|
-
signer:
|
|
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 =
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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:
|
|
208
|
+
signer: Signer,
|
|
251
209
|
): Promise<string> => {
|
|
252
|
-
//
|
|
210
|
+
// Ethers auto-generates the EIP712Domain type in the TypedDataEncoder, and so it needs to be removed
|
|
253
211
|
const types = { ...typedData.types };
|
|
254
|
-
//
|
|
255
|
-
|
|
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 =
|
|
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:
|
|
232
|
+
signer: Signer,
|
|
282
233
|
walletAddress: string,
|
|
283
234
|
): Promise<string> => {
|
|
284
|
-
// Generate digest
|
|
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 =
|
|
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
|
-
|
|
7
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
94
|
-
this.#rpcProvider =
|
|
95
|
-
|
|
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
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
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
|
-
|
|
364
|
-
|
|
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
|
-
|
|
371
|
-
|
|
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
|
-
|
|
379
|
-
|
|
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>;
|
package/src/utils/crypto.ts
DELETED
|
@@ -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
|
-
});
|