@imtbl/wallet 2.10.7-alpha.2

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 (71) hide show
  1. package/.eslintrc.cjs +18 -0
  2. package/LICENSE.md +176 -0
  3. package/dist/browser/index.mjs +21 -0
  4. package/dist/node/index.js +71 -0
  5. package/dist/node/index.mjs +22 -0
  6. package/dist/types/config.d.ts +13 -0
  7. package/dist/types/errors.d.ts +14 -0
  8. package/dist/types/guardian/index.d.ts +57 -0
  9. package/dist/types/index.d.ts +12 -0
  10. package/dist/types/magic/index.d.ts +1 -0
  11. package/dist/types/magic/magicTEESigner.d.ts +24 -0
  12. package/dist/types/network/chains.d.ts +32 -0
  13. package/dist/types/network/constants.d.ts +3 -0
  14. package/dist/types/network/retry.d.ts +8 -0
  15. package/dist/types/provider/eip6963.d.ts +3 -0
  16. package/dist/types/types.d.ts +163 -0
  17. package/dist/types/utils/metrics.d.ts +3 -0
  18. package/dist/types/utils/string.d.ts +1 -0
  19. package/dist/types/utils/typedEventEmitter.d.ts +6 -0
  20. package/dist/types/zkEvm/JsonRpcError.d.ts +25 -0
  21. package/dist/types/zkEvm/index.d.ts +2 -0
  22. package/dist/types/zkEvm/personalSign.d.ts +15 -0
  23. package/dist/types/zkEvm/provider/eip6963.d.ts +3 -0
  24. package/dist/types/zkEvm/relayerClient.d.ts +60 -0
  25. package/dist/types/zkEvm/sendDeployTransactionAndPersonalSign.d.ts +6 -0
  26. package/dist/types/zkEvm/sendTransaction.d.ts +6 -0
  27. package/dist/types/zkEvm/sessionActivity/errorBoundary.d.ts +1 -0
  28. package/dist/types/zkEvm/sessionActivity/request.d.ts +15 -0
  29. package/dist/types/zkEvm/sessionActivity/sessionActivity.d.ts +2 -0
  30. package/dist/types/zkEvm/signEjectionTransaction.d.ts +6 -0
  31. package/dist/types/zkEvm/signTypedDataV4.d.ts +14 -0
  32. package/dist/types/zkEvm/transactionHelpers.d.ts +31 -0
  33. package/dist/types/zkEvm/types.d.ts +120 -0
  34. package/dist/types/zkEvm/user/index.d.ts +1 -0
  35. package/dist/types/zkEvm/user/registerZkEvmUser.d.ts +13 -0
  36. package/dist/types/zkEvm/walletHelpers.d.ts +33 -0
  37. package/dist/types/zkEvm/zkEvmProvider.d.ts +25 -0
  38. package/package.json +55 -0
  39. package/src/config.ts +51 -0
  40. package/src/errors.ts +33 -0
  41. package/src/guardian/index.ts +358 -0
  42. package/src/index.ts +27 -0
  43. package/src/magic/index.ts +1 -0
  44. package/src/magic/magicTEESigner.ts +214 -0
  45. package/src/network/chains.ts +33 -0
  46. package/src/network/constants.ts +28 -0
  47. package/src/network/retry.ts +37 -0
  48. package/src/provider/eip6963.ts +25 -0
  49. package/src/types.ts +192 -0
  50. package/src/utils/metrics.ts +57 -0
  51. package/src/utils/string.ts +12 -0
  52. package/src/utils/typedEventEmitter.ts +26 -0
  53. package/src/zkEvm/JsonRpcError.ts +33 -0
  54. package/src/zkEvm/index.ts +2 -0
  55. package/src/zkEvm/personalSign.ts +62 -0
  56. package/src/zkEvm/provider/eip6963.ts +25 -0
  57. package/src/zkEvm/relayerClient.ts +216 -0
  58. package/src/zkEvm/sendDeployTransactionAndPersonalSign.ts +44 -0
  59. package/src/zkEvm/sendTransaction.ts +34 -0
  60. package/src/zkEvm/sessionActivity/errorBoundary.ts +33 -0
  61. package/src/zkEvm/sessionActivity/request.ts +62 -0
  62. package/src/zkEvm/sessionActivity/sessionActivity.ts +140 -0
  63. package/src/zkEvm/signEjectionTransaction.ts +33 -0
  64. package/src/zkEvm/signTypedDataV4.ts +103 -0
  65. package/src/zkEvm/transactionHelpers.ts +295 -0
  66. package/src/zkEvm/types.ts +136 -0
  67. package/src/zkEvm/user/index.ts +1 -0
  68. package/src/zkEvm/user/registerZkEvmUser.ts +75 -0
  69. package/src/zkEvm/walletHelpers.ts +243 -0
  70. package/src/zkEvm/zkEvmProvider.ts +453 -0
  71. package/tsconfig.json +15 -0
@@ -0,0 +1,103 @@
1
+ import { Flow } from '@imtbl/metrics';
2
+ import { Signer, JsonRpcProvider } from 'ethers';
3
+ import GuardianClient from '../guardian';
4
+ import { signAndPackTypedData } from './walletHelpers';
5
+ import { TypedDataPayload } from './types';
6
+ import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
7
+ import { RelayerClient } from './relayerClient';
8
+
9
+ export type SignTypedDataV4Params = {
10
+ ethSigner: Signer;
11
+ rpcProvider: JsonRpcProvider;
12
+ relayerClient: RelayerClient;
13
+ method: string;
14
+ params: Array<any>;
15
+ guardianClient: GuardianClient;
16
+ flow: Flow;
17
+ };
18
+
19
+ const REQUIRED_TYPED_DATA_PROPERTIES = ['types', 'domain', 'primaryType', 'message'];
20
+ const isValidTypedDataPayload = (typedData: object): typedData is TypedDataPayload => (
21
+ REQUIRED_TYPED_DATA_PROPERTIES.every((key) => key in typedData)
22
+ );
23
+
24
+ const transformTypedData = (typedData: string | object, chainId: bigint): TypedDataPayload => {
25
+ let transformedTypedData: object | TypedDataPayload;
26
+
27
+ if (typeof typedData === 'string') {
28
+ try {
29
+ transformedTypedData = JSON.parse(typedData);
30
+ } catch (err: any) {
31
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Failed to parse typed data JSON: ${err}`);
32
+ }
33
+ } else if (typeof typedData === 'object') {
34
+ transformedTypedData = typedData;
35
+ } else {
36
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Invalid typed data argument: ${typedData}`);
37
+ }
38
+
39
+ if (!isValidTypedDataPayload(transformedTypedData)) {
40
+ throw new JsonRpcError(
41
+ RpcErrorCode.INVALID_PARAMS,
42
+ // eslint-disable-next-line max-len
43
+ `Invalid typed data argument. The following properties are required: ${REQUIRED_TYPED_DATA_PROPERTIES.join(', ')}`,
44
+ );
45
+ }
46
+
47
+ const providedChainId = transformedTypedData.domain?.chainId;
48
+
49
+ if (providedChainId) {
50
+ // domain.chainId (if defined) can be a number, string, or hex value, but the relayer & guardian only accept a number.
51
+ if (typeof providedChainId === 'string') {
52
+ if (providedChainId.startsWith('0x')) {
53
+ transformedTypedData.domain.chainId = parseInt(providedChainId, 16).toString();
54
+ } else {
55
+ transformedTypedData.domain.chainId = parseInt(providedChainId, 10).toString();
56
+ }
57
+ }
58
+
59
+ if (BigInt(transformedTypedData.domain.chainId ?? 0) !== chainId) {
60
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `Invalid chainId, expected ${chainId}`);
61
+ }
62
+ }
63
+
64
+ return transformedTypedData;
65
+ };
66
+
67
+ export const signTypedDataV4 = async ({
68
+ params,
69
+ method,
70
+ ethSigner,
71
+ rpcProvider,
72
+ relayerClient,
73
+ guardianClient,
74
+ flow,
75
+ }: SignTypedDataV4Params): Promise<string> => {
76
+ const fromAddress: string = params[0];
77
+ const typedDataParam: string | object = params[1];
78
+
79
+ if (!fromAddress || !typedDataParam) {
80
+ throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, `${method} requires an address and a typed data JSON`);
81
+ }
82
+
83
+ const { chainId } = await rpcProvider.getNetwork();
84
+ const typedData = transformTypedData(typedDataParam, chainId);
85
+ flow.addEvent('endDetectNetwork');
86
+
87
+ await guardianClient.evaluateEIP712Message({ chainID: String(chainId), payload: typedData });
88
+ flow.addEvent('endValidateMessage');
89
+
90
+ const relayerSignature = await relayerClient.imSignTypedData(fromAddress, typedData);
91
+ flow.addEvent('endRelayerSignTypedData');
92
+
93
+ const signature = await signAndPackTypedData(
94
+ typedData,
95
+ relayerSignature,
96
+ BigInt(chainId),
97
+ fromAddress,
98
+ ethSigner,
99
+ );
100
+ flow.addEvent('getSignedTypedData');
101
+
102
+ return signature;
103
+ };
@@ -0,0 +1,295 @@
1
+ import { Flow } from '@imtbl/metrics';
2
+ import {
3
+ Signer, TransactionRequest, JsonRpcProvider,
4
+ BigNumberish,
5
+ } from 'ethers';
6
+ import {
7
+ getEip155ChainId,
8
+ signMetaTransactions,
9
+ encodedTransactions,
10
+ getNormalisedTransactions,
11
+ getNonce,
12
+ } from './walletHelpers';
13
+ import { RelayerClient } from './relayerClient';
14
+ import GuardianClient, { convertBigNumberishToString } from '../guardian';
15
+ import {
16
+ FeeOption,
17
+ MetaTransaction,
18
+ RelayerTransactionStatus,
19
+ } from './types';
20
+ import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
21
+ import { retryWithDelay } from '../network/retry';
22
+
23
+ const MAX_TRANSACTION_HASH_RETRIEVAL_RETRIES = 30;
24
+ const TRANSACTION_HASH_RETRIEVAL_WAIT = 1000;
25
+
26
+ export type TransactionParams = {
27
+ ethSigner: Signer;
28
+ rpcProvider: JsonRpcProvider;
29
+ guardianClient: GuardianClient;
30
+ relayerClient: RelayerClient;
31
+ zkEvmAddress: string;
32
+ flow: Flow;
33
+ nonceSpace?: bigint;
34
+ isBackgroundTransaction?: boolean;
35
+ };
36
+
37
+ export type EjectionTransactionParams = Pick<TransactionParams, 'ethSigner' | 'zkEvmAddress' | 'flow'>;
38
+ export type EjectionTransactionResponse = {
39
+ to: string;
40
+ data: string;
41
+ chainId: string;
42
+ };
43
+
44
+ const getFeeOption = async (
45
+ metaTransaction: MetaTransaction,
46
+ walletAddress: string,
47
+ relayerClient: RelayerClient,
48
+ ): Promise<FeeOption> => {
49
+ const normalisedMetaTransaction = getNormalisedTransactions([
50
+ metaTransaction,
51
+ ]);
52
+ const transactions = encodedTransactions(normalisedMetaTransaction);
53
+ const feeOptions = await relayerClient.imGetFeeOptions(
54
+ walletAddress,
55
+ transactions,
56
+ );
57
+
58
+ if (!feeOptions || !Array.isArray(feeOptions)) {
59
+ throw new Error('Invalid fee options received from relayer');
60
+ }
61
+
62
+ const imxFeeOption = feeOptions.find(
63
+ (feeOption) => feeOption.tokenSymbol === 'IMX',
64
+ );
65
+ if (!imxFeeOption) {
66
+ throw new Error('Failed to retrieve fees for IMX token');
67
+ }
68
+
69
+ return imxFeeOption;
70
+ };
71
+
72
+ /**
73
+ * Prepares the meta transactions array to be signed by estimating the fee and
74
+ * getting the nonce from the smart wallet.
75
+ */
76
+ const buildMetaTransactions = async (
77
+ transactionRequest: TransactionRequest,
78
+ rpcProvider: JsonRpcProvider,
79
+ relayerClient: RelayerClient,
80
+ zkevmAddress: string,
81
+ nonceSpace?: bigint,
82
+ ): Promise<[MetaTransaction, ...MetaTransaction[]]> => {
83
+ if (!transactionRequest.to) {
84
+ throw new JsonRpcError(
85
+ RpcErrorCode.INVALID_PARAMS,
86
+ 'eth_sendTransaction requires a "to" field',
87
+ );
88
+ }
89
+
90
+ const metaTransaction: MetaTransaction = {
91
+ to: transactionRequest.to.toString(),
92
+ data: transactionRequest.data,
93
+ nonce: BigInt(0), // NOTE: We don't need a valid nonce to estimate the fee
94
+ value: transactionRequest.value,
95
+ revertOnError: true,
96
+ };
97
+
98
+ // Estimate the fee and get the nonce from the smart wallet
99
+ const [nonce, feeOption] = await Promise.all([
100
+ getNonce(rpcProvider, zkevmAddress, nonceSpace),
101
+ getFeeOption(metaTransaction, zkevmAddress, relayerClient),
102
+ ]);
103
+
104
+ // Build the meta transactions array with a valid nonce and fee transaction
105
+ const metaTransactions: [MetaTransaction, ...MetaTransaction[]] = [
106
+ {
107
+ ...metaTransaction,
108
+ nonce,
109
+ },
110
+ ];
111
+
112
+ // Add a fee transaction if the fee is non-zero
113
+ const feeValue = BigInt(feeOption.tokenPrice);
114
+ if (feeValue !== BigInt(0)) {
115
+ metaTransactions.push({
116
+ nonce,
117
+ to: feeOption.recipientAddress,
118
+ value: feeValue,
119
+ revertOnError: true,
120
+ });
121
+ }
122
+
123
+ return metaTransactions;
124
+ };
125
+
126
+ export const pollRelayerTransaction = async (
127
+ relayerClient: RelayerClient,
128
+ relayerId: string,
129
+ flow: Flow,
130
+ ) => {
131
+ const retrieveRelayerTransaction = async () => {
132
+ const tx = await relayerClient.imGetTransactionByHash(relayerId);
133
+ // NOTE: The transaction hash is only available from the Relayer once the
134
+ // transaction is actually submitted onchain. Hence we need to poll the
135
+ // Relayer get transaction endpoint until the status transitions to one that
136
+ // has the hash available.
137
+ if (tx.status === RelayerTransactionStatus.PENDING) {
138
+ throw new Error();
139
+ }
140
+ return tx;
141
+ };
142
+
143
+ const relayerTransaction = await retryWithDelay(retrieveRelayerTransaction, {
144
+ retries: MAX_TRANSACTION_HASH_RETRIEVAL_RETRIES,
145
+ interval: TRANSACTION_HASH_RETRIEVAL_WAIT,
146
+ finalErr: new JsonRpcError(
147
+ RpcErrorCode.RPC_SERVER_ERROR,
148
+ 'transaction hash not generated in time',
149
+ ),
150
+ });
151
+ flow.addEvent('endRetrieveRelayerTransaction');
152
+
153
+ if (
154
+ ![
155
+ RelayerTransactionStatus.SUBMITTED,
156
+ RelayerTransactionStatus.SUCCESSFUL,
157
+ ].includes(relayerTransaction.status)
158
+ ) {
159
+ let errorMessage = `Transaction failed to submit with status ${relayerTransaction.status}.`;
160
+ if (relayerTransaction.statusMessage) {
161
+ errorMessage += ` Error message: ${relayerTransaction.statusMessage}`;
162
+ }
163
+ throw new JsonRpcError(RpcErrorCode.RPC_SERVER_ERROR, errorMessage);
164
+ }
165
+
166
+ return relayerTransaction;
167
+ };
168
+
169
+ export const prepareAndSignTransaction = async ({
170
+ transactionRequest,
171
+ ethSigner,
172
+ rpcProvider,
173
+ guardianClient,
174
+ relayerClient,
175
+ zkEvmAddress,
176
+ flow,
177
+ nonceSpace,
178
+ isBackgroundTransaction,
179
+ }: TransactionParams & { transactionRequest: TransactionRequest }) => {
180
+ const { chainId } = await rpcProvider.getNetwork();
181
+ const chainIdBigNumber = BigInt(chainId);
182
+ flow.addEvent('endDetectNetwork');
183
+
184
+ const metaTransactions = await buildMetaTransactions(
185
+ transactionRequest,
186
+ rpcProvider,
187
+ relayerClient,
188
+ zkEvmAddress,
189
+ nonceSpace,
190
+ );
191
+ flow.addEvent('endBuildMetaTransactions');
192
+
193
+ const { nonce } = metaTransactions[0];
194
+ if (typeof nonce === 'undefined') {
195
+ throw new Error('Failed to retrieve nonce from the smart wallet');
196
+ }
197
+
198
+ // Parallelize the validation and signing of the transaction
199
+ // without waiting for the validation to complete
200
+ const validateTransaction = async () => {
201
+ await guardianClient.validateEVMTransaction({
202
+ chainId: getEip155ChainId(Number(chainId)),
203
+ nonce: convertBigNumberishToString(nonce),
204
+ metaTransactions,
205
+ isBackgroundTransaction,
206
+ });
207
+ flow.addEvent('endValidateEVMTransaction');
208
+ };
209
+
210
+ // NOTE: We sign again because we now are adding the fee transaction, so the
211
+ // whole payload is different and needs a new signature.
212
+ const signTransaction = async () => {
213
+ const signed = await signMetaTransactions(
214
+ metaTransactions,
215
+ nonce,
216
+ chainIdBigNumber,
217
+ zkEvmAddress,
218
+ ethSigner,
219
+ );
220
+ flow.addEvent('endGetSignedMetaTransactions');
221
+ return signed;
222
+ };
223
+
224
+ const [, signedTransactions] = await Promise.all([
225
+ validateTransaction(),
226
+ signTransaction(),
227
+ ]);
228
+
229
+ const relayerId = await relayerClient.ethSendTransaction(zkEvmAddress, signedTransactions);
230
+ flow.addEvent('endRelayerSendTransaction');
231
+
232
+ return { signedTransactions, relayerId, nonce };
233
+ };
234
+
235
+ const buildMetaTransactionForEjection = async (
236
+ transactionRequest: TransactionRequest,
237
+ ): Promise<[MetaTransaction, ...MetaTransaction[]]> => {
238
+ if (!transactionRequest.to) {
239
+ throw new JsonRpcError(
240
+ RpcErrorCode.INVALID_PARAMS,
241
+ 'im_signEjectionTransaction requires a "to" field',
242
+ );
243
+ }
244
+
245
+ if (typeof transactionRequest.nonce === 'undefined') {
246
+ throw new JsonRpcError(
247
+ RpcErrorCode.INVALID_PARAMS,
248
+ 'im_signEjectionTransaction requires a "nonce" field',
249
+ );
250
+ }
251
+
252
+ if (!transactionRequest.chainId) {
253
+ throw new JsonRpcError(
254
+ RpcErrorCode.INVALID_PARAMS,
255
+ 'im_signEjectionTransaction requires a "chainId" field',
256
+ );
257
+ }
258
+
259
+ const metaTransaction: MetaTransaction = {
260
+ to: transactionRequest.to.toString(),
261
+ data: transactionRequest.data,
262
+ nonce: transactionRequest.nonce ?? undefined,
263
+ value: transactionRequest.value,
264
+ revertOnError: true,
265
+ };
266
+
267
+ return [metaTransaction];
268
+ };
269
+
270
+ export const prepareAndSignEjectionTransaction = async ({
271
+ transactionRequest,
272
+ ethSigner,
273
+ zkEvmAddress,
274
+ flow,
275
+ }: EjectionTransactionParams & { transactionRequest: TransactionRequest }): Promise<EjectionTransactionResponse> => {
276
+ const metaTransaction = await buildMetaTransactionForEjection(
277
+ transactionRequest,
278
+ );
279
+ flow.addEvent('endBuildMetaTransactions');
280
+
281
+ const signedTransaction = await signMetaTransactions(
282
+ metaTransaction,
283
+ transactionRequest.nonce as BigNumberish,
284
+ BigInt(transactionRequest.chainId ?? 0),
285
+ zkEvmAddress,
286
+ ethSigner,
287
+ );
288
+ flow.addEvent('endGetSignedMetaTransactions');
289
+
290
+ return {
291
+ to: zkEvmAddress,
292
+ data: signedTransaction,
293
+ chainId: getEip155ChainId(Number(transactionRequest.chainId ?? 0)),
294
+ };
295
+ };
@@ -0,0 +1,136 @@
1
+ import { BigNumberish } from 'ethers';
2
+ import { JsonRpcError } from './JsonRpcError';
3
+
4
+ export enum RelayerTransactionStatus {
5
+ PENDING = 'PENDING',
6
+ SUBMITTED = 'SUBMITTED',
7
+ SUCCESSFUL = 'SUCCESSFUL',
8
+ REVERTED = 'REVERTED',
9
+ FAILED = 'FAILED',
10
+ CANCELLED = 'CANCELLED',
11
+ }
12
+
13
+ export interface RelayerTransaction {
14
+ status: RelayerTransactionStatus;
15
+ chainId: string;
16
+ relayerId: string;
17
+ hash: string;
18
+ statusMessage?: string;
19
+ }
20
+
21
+ export interface FeeOption {
22
+ tokenPrice: string;
23
+ tokenSymbol: string;
24
+ tokenDecimals: number;
25
+ tokenAddress: string;
26
+ recipientAddress: string;
27
+ }
28
+
29
+ export interface MetaTransaction {
30
+ to: string;
31
+ value?: BigNumberish | null;
32
+ data?: string | null;
33
+ nonce?: BigNumberish;
34
+ gasLimit?: BigNumberish;
35
+ delegateCall?: boolean;
36
+ revertOnError?: boolean;
37
+ }
38
+
39
+ export interface MetaTransactionNormalised {
40
+ delegateCall: boolean;
41
+ revertOnError: boolean;
42
+ gasLimit: BigNumberish;
43
+ target: string;
44
+ value: BigNumberish;
45
+ data: string;
46
+ }
47
+
48
+ // https://eips.ethereum.org/EIPS/eip-712
49
+ export interface TypedDataPayload {
50
+ types: {
51
+ EIP712Domain: Array<{ name: string; type: string }>;
52
+ [key: string]: Array<{ name: string; type: string }>;
53
+ };
54
+ domain: {
55
+ name?: string;
56
+ version?: string;
57
+ chainId?: number | string;
58
+ verifyingContract?: string;
59
+ salt?: string;
60
+ } | {
61
+ name?: string;
62
+ version?: string;
63
+ chainId?: number;
64
+ verifyingContract?: string;
65
+ salt?: string;
66
+ };
67
+ primaryType: string;
68
+ message: Record<string, any>;
69
+ }
70
+
71
+ export interface RequestArguments {
72
+ method: string;
73
+ params?: Array<any>;
74
+ }
75
+
76
+ export type JsonRpcRequestPayload = RequestArguments & {
77
+ jsonrpc?: string;
78
+ id?: string | number;
79
+ };
80
+
81
+ export interface JsonRpcRequestCallback {
82
+ (
83
+ err: JsonRpcError | null,
84
+ result?: JsonRpcResponsePayload | (JsonRpcResponsePayload | null)[] | null
85
+ ): void;
86
+ }
87
+
88
+ export interface JsonRpcResponsePayload {
89
+ result?: Array<any> | null;
90
+ error?: JsonRpcError | null;
91
+ jsonrpc?: string;
92
+ id?: string | number;
93
+ }
94
+
95
+ export type Provider = {
96
+ request: (request: RequestArguments) => Promise<any>;
97
+ on: (event: string, listener: (...args: any[]) => void) => void;
98
+ removeListener: (event: string, listener: (...args: any[]) => void) => void;
99
+ isPassport: boolean;
100
+ };
101
+
102
+ export enum ProviderEvent {
103
+ ACCOUNTS_CHANGED = 'accountsChanged',
104
+ }
105
+
106
+ export type AccountsChangedEvent = Array<string>;
107
+
108
+ export interface ProviderEventMap extends Record<string, any> {
109
+ [ProviderEvent.ACCOUNTS_CHANGED]: [AccountsChangedEvent];
110
+ }
111
+
112
+ /**
113
+ * Event detail from the `eip6963:announceProvider` event.
114
+ */
115
+ export interface EIP6963ProviderDetail {
116
+ info: EIP6963ProviderInfo;
117
+ provider: Provider;
118
+ }
119
+
120
+ /**
121
+ * Metadata of the EIP-1193 Provider.
122
+ */
123
+ export interface EIP6963ProviderInfo {
124
+ icon: `data:image/${string}`; // RFC-2397
125
+ name: string;
126
+ rdns: string;
127
+ uuid: string;
128
+ }
129
+
130
+ /**
131
+ * Event type to announce an EIP-1193 Provider.
132
+ */
133
+ export interface EIP6963AnnounceProviderEvent
134
+ extends CustomEvent<EIP6963ProviderDetail> {
135
+ type: 'eip6963:announceProvider';
136
+ }
@@ -0,0 +1 @@
1
+ export * from './registerZkEvmUser';
@@ -0,0 +1,75 @@
1
+ import { MultiRollupApiClients } from '@imtbl/generated-clients';
2
+ import { signRaw } from '@imtbl/toolkit';
3
+ import { Flow } from '@imtbl/metrics';
4
+ import { Signer, JsonRpcProvider } from 'ethers';
5
+ import { getEip155ChainId } from '../walletHelpers';
6
+ import { AuthManager } from '@imtbl/auth';
7
+ import { JsonRpcError, RpcErrorCode } from '../JsonRpcError';
8
+
9
+ export type RegisterZkEvmUserInput = {
10
+ authManager: AuthManager;
11
+ ethSigner: Signer,
12
+ multiRollupApiClients: MultiRollupApiClients,
13
+ accessToken: string;
14
+ rpcProvider: JsonRpcProvider;
15
+ flow: Flow;
16
+ };
17
+
18
+ const MESSAGE_TO_SIGN = 'Only sign this message from Immutable Passport';
19
+
20
+ export async function registerZkEvmUser({
21
+ authManager,
22
+ ethSigner,
23
+ multiRollupApiClients,
24
+ accessToken,
25
+ rpcProvider,
26
+ flow,
27
+ }: RegisterZkEvmUserInput): Promise<string> {
28
+ // Parallelize the operations that can happen concurrently
29
+ const getAddressPromise = ethSigner.getAddress();
30
+ getAddressPromise.then(() => flow.addEvent('endGetAddress'));
31
+
32
+ const signRawPromise = signRaw(MESSAGE_TO_SIGN, ethSigner);
33
+ signRawPromise.then(() => flow.addEvent('endSignRaw'));
34
+
35
+ const detectNetworkPromise = rpcProvider.getNetwork();
36
+ detectNetworkPromise.then(() => flow.addEvent('endDetectNetwork'));
37
+
38
+ const listChainsPromise = multiRollupApiClients.chainsApi.listChains();
39
+ listChainsPromise.then(() => flow.addEvent('endListChains'));
40
+
41
+ const [ethereumAddress, ethereumSignature, network, chainListResponse] = await Promise.all([
42
+ getAddressPromise,
43
+ signRawPromise,
44
+ detectNetworkPromise,
45
+ listChainsPromise,
46
+ ]);
47
+
48
+ const eipChainId = getEip155ChainId(Number(network.chainId));
49
+ const chainName = chainListResponse.data?.result?.find((chain) => chain.id === eipChainId)?.name;
50
+ if (!chainName) {
51
+ throw new JsonRpcError(
52
+ RpcErrorCode.INTERNAL_ERROR,
53
+ `Chain name does not exist on for chain id ${network.chainId}`,
54
+ );
55
+ }
56
+
57
+ try {
58
+ const registrationResponse = await multiRollupApiClients.passportApi.createCounterfactualAddressV2({
59
+ chainName,
60
+ createCounterfactualAddressRequest: {
61
+ ethereum_address: ethereumAddress,
62
+ ethereum_signature: ethereumSignature,
63
+ },
64
+ }, {
65
+ headers: { Authorization: `Bearer ${accessToken}` },
66
+ });
67
+ flow.addEvent('endCreateCounterfactualAddress');
68
+
69
+ authManager.forceUserRefreshInBackground();
70
+
71
+ return registrationResponse.data.counterfactual_address;
72
+ } catch (error) {
73
+ throw new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, `Failed to create counterfactual address: ${error}`);
74
+ }
75
+ }