@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,243 @@
1
+ import { walletContracts } from '@0xsequence/abi';
2
+ import { v1 as sequenceCoreV1 } from '@0xsequence/core';
3
+ import {
4
+ BigNumberish, Contract, getBytes, hashMessage,
5
+ Interface, keccak256, Signer, solidityPacked, ZeroAddress,
6
+ TypedDataEncoder, JsonRpcProvider, AbiCoder,
7
+ isError,
8
+ } from 'ethers';
9
+ import { MetaTransaction, MetaTransactionNormalised, TypedDataPayload } from './types';
10
+
11
+ const SIGNATURE_WEIGHT = 1; // Weight of a single signature in the multi-sig
12
+ const TRANSACTION_SIGNATURE_THRESHOLD = 1; // Total required weight in the multi-sig for a transaction
13
+ const PACKED_SIGNATURE_THRESHOLD = 2; // Total required weight in the multi-sig for data signing
14
+
15
+ const ETH_SIGN_FLAG = '02';
16
+ const ETH_SIGN_PREFIX = '\x19\x01';
17
+ const META_TRANSACTIONS_TYPE = `tuple(
18
+ bool delegateCall,
19
+ bool revertOnError,
20
+ uint256 gasLimit,
21
+ address target,
22
+ uint256 value,
23
+ bytes data
24
+ )[]`;
25
+
26
+ export const getNormalisedTransactions = (txs: MetaTransaction[]): MetaTransactionNormalised[] => txs.map((t) => ({
27
+ delegateCall: t.delegateCall === true,
28
+ revertOnError: t.revertOnError === true,
29
+ gasLimit: t.gasLimit ?? BigInt(0),
30
+ target: t.to ?? ZeroAddress,
31
+ value: t.value ?? BigInt(0),
32
+ data: t.data ?? '0x',
33
+ }));
34
+
35
+ export const digestOfTransactionsAndNonce = (
36
+ nonce: BigNumberish,
37
+ normalisedTransactions: MetaTransactionNormalised[],
38
+ ): string => {
39
+ const packMetaTransactionsNonceData = AbiCoder.defaultAbiCoder().encode(
40
+ ['uint256', META_TRANSACTIONS_TYPE],
41
+ [nonce, normalisedTransactions],
42
+ );
43
+ return keccak256(packMetaTransactionsNonceData);
44
+ };
45
+
46
+ export const encodedTransactions = (
47
+ normalisedTransactions: MetaTransactionNormalised[],
48
+ ): string => AbiCoder.defaultAbiCoder().encode(
49
+ [META_TRANSACTIONS_TYPE],
50
+ [normalisedTransactions],
51
+ );
52
+
53
+ /**
54
+ * This helper function is used to coerce the type <BigNumber | undefined> to BigNumber for the
55
+ * getNonce function above.
56
+ * @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
57
+ * @returns {BigNumber} The passed in nonceSpace or instead initialises the nonce to 0.
58
+ */
59
+ export const coerceNonceSpace = (nonceSpace?: bigint): bigint => nonceSpace || 0n;
60
+
61
+ /**
62
+ * This helper function is used to encode the nonce into a 256 bit value where the space is encoded into
63
+ * the first 160 bits, and the nonce the remaining 96 bits.
64
+ * @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
65
+ * @param nonce {BigNumber} nonce - Sequential number starting at 0, and incrementing in single steps e.g. 0,1,2,...
66
+ * @returns {BigNumber} The encoded value where the space is left shifted 96 bits, and the nonce is in the first 96 bits.
67
+ */
68
+ export const encodeNonce = (nonceSpace: bigint, nonce: bigint): bigint => {
69
+ const shiftedSpace = BigInt(nonceSpace) * (2n ** 96n);
70
+ return BigInt(nonce) + (shiftedSpace);
71
+ };
72
+
73
+ /**
74
+ * When we retrieve a nonce for a smart contract wallet we can retrieve the nonce in a given 256 bit space.
75
+ * Nonces in each 256 bit space need to be sequential per wallet address. Nonces across 256 bit spaces per
76
+ * wallet address do not. This function overload can be used to invoke transactions in parallel per smart
77
+ * contract wallet if required.
78
+ */
79
+ export const getNonce = async (
80
+ rpcProvider: JsonRpcProvider,
81
+ smartContractWalletAddress: string,
82
+ nonceSpace?: bigint,
83
+ ): Promise<bigint> => {
84
+ try {
85
+ const contract = new Contract(
86
+ smartContractWalletAddress,
87
+ walletContracts.mainModule.abi,
88
+ rpcProvider,
89
+ );
90
+ const space: bigint = coerceNonceSpace(nonceSpace); // Default nonce space is 0
91
+ const result = await contract.readNonce(space);
92
+ if (typeof result === 'bigint') {
93
+ return encodeNonce(space, result);
94
+ }
95
+ throw new Error('Unexpected result from contract.nonce() call.');
96
+ } catch (error) {
97
+ if (isError(error, 'BAD_DATA')) {
98
+ // The most likely reason for a BAD_DATA error is that the smart contract wallet
99
+ // has not been deployed yet, so we should default to a nonce of 0.
100
+ return BigInt(0);
101
+ }
102
+
103
+ throw error;
104
+ }
105
+ };
106
+
107
+ export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, digest: string): string => (
108
+ solidityPacked(
109
+ ['string', 'uint256', 'address', 'bytes32'],
110
+ [ETH_SIGN_PREFIX, chainId, walletAddress, digest],
111
+ )
112
+ );
113
+
114
+ export const signMetaTransactions = async (
115
+ metaTransactions: MetaTransaction[],
116
+ nonce: BigNumberish,
117
+ chainId: bigint,
118
+ walletAddress: string,
119
+ signer: Signer,
120
+ ): Promise<string> => {
121
+ const normalisedMetaTransactions = getNormalisedTransactions(metaTransactions);
122
+
123
+ // Get the hash
124
+ const digest = digestOfTransactionsAndNonce(nonce, normalisedMetaTransactions);
125
+ const completePayload = encodeMessageSubDigest(chainId, walletAddress, digest);
126
+
127
+ const hash = keccak256(completePayload);
128
+
129
+ // Sign the digest
130
+ const hashArray = getBytes(hash);
131
+ const ethsigNoType = await signer.signMessage(hashArray);
132
+ const signedDigest = `${ethsigNoType}${ETH_SIGN_FLAG}`;
133
+
134
+ // Add metadata
135
+ const encodedSignature = sequenceCoreV1.signature.encodeSignature({
136
+ version: 1,
137
+ threshold: TRANSACTION_SIGNATURE_THRESHOLD,
138
+ signers: [
139
+ {
140
+ isDynamic: false,
141
+ unrecovered: true,
142
+ weight: SIGNATURE_WEIGHT,
143
+ signature: signedDigest,
144
+ },
145
+ ],
146
+ });
147
+
148
+ // Encode the transaction;
149
+ const walletInterface = new Interface(walletContracts.mainModule.abi);
150
+ return walletInterface.encodeFunctionData(walletInterface.getFunction('execute') ?? '', [
151
+ normalisedMetaTransactions,
152
+ nonce,
153
+ encodedSignature,
154
+ ]);
155
+ };
156
+
157
+ const decodeRelayerSignature = (relayerSignature: string) => {
158
+ const signatureWithThreshold = `0x0000${relayerSignature}`;
159
+ return sequenceCoreV1.signature.decodeSignature(signatureWithThreshold);
160
+ };
161
+
162
+ export const packSignatures = (
163
+ EOASignature: string,
164
+ EOAAddress: string,
165
+ relayerSignature: string,
166
+ ): string => {
167
+ const signedDigest = `${EOASignature}${ETH_SIGN_FLAG}`;
168
+
169
+ // Combine the relayer and user signatures; sort by address to match the imageHash order
170
+ const { signers: relayerSigners } = decodeRelayerSignature(relayerSignature);
171
+ const combinedSigners = [
172
+ ...relayerSigners,
173
+ {
174
+ isDynamic: false,
175
+ unrecovered: true,
176
+ weight: SIGNATURE_WEIGHT,
177
+ signature: signedDigest,
178
+ address: EOAAddress,
179
+ },
180
+ ];
181
+ const sortedSigners = combinedSigners.sort((a, b) => {
182
+ const bigA = BigInt(a.address ?? 0);
183
+ const bigB = BigInt(b.address ?? 0);
184
+
185
+ if (bigA <= bigB) {
186
+ return -1;
187
+ } if (bigA === bigB) {
188
+ return 0;
189
+ }
190
+ return 1;
191
+ });
192
+
193
+ return sequenceCoreV1.signature.encodeSignature({
194
+ version: 1,
195
+ threshold: PACKED_SIGNATURE_THRESHOLD,
196
+ signers: sortedSigners,
197
+ });
198
+ };
199
+
200
+ export const signAndPackTypedData = async (
201
+ typedData: TypedDataPayload,
202
+ relayerSignature: string,
203
+ chainId: bigint,
204
+ walletAddress: string,
205
+ signer: Signer,
206
+ ): Promise<string> => {
207
+ // Ethers auto-generates the EIP712Domain type in the TypedDataEncoder, and so it needs to be removed
208
+ const types = { ...typedData.types };
209
+ // @ts-ignore
210
+ delete types.EIP712Domain;
211
+
212
+ // Hash the EIP712 payload and generate the complete payload
213
+ const typedDataHash = TypedDataEncoder.hash(typedData.domain, types, typedData.message);
214
+ const messageSubDigest = encodeMessageSubDigest(chainId, walletAddress, typedDataHash);
215
+ const hash = keccak256(messageSubDigest);
216
+
217
+ // Sign the sub digest
218
+ // https://github.com/immutable/wallet-contracts/blob/7824b5f24b2e0eb2dc465ecb5cd71f3984556b73/src/contracts/modules/commons/ModuleAuth.sol#L155
219
+ const hashArray = getBytes(hash);
220
+ const eoaSignature = await signer.signMessage(hashArray);
221
+ const eoaAddress = await signer.getAddress();
222
+
223
+ return packSignatures(eoaSignature, eoaAddress, relayerSignature);
224
+ };
225
+
226
+ export const signERC191Message = async (
227
+ chainId: bigint,
228
+ payload: string,
229
+ signer: Signer,
230
+ walletAddress: string,
231
+ ): Promise<string> => {
232
+ // Generate digest
233
+ const digest = hashMessage(payload);
234
+
235
+ // Generate subDigest
236
+ const subDigest = encodeMessageSubDigest(chainId, walletAddress, digest);
237
+ const subDigestHash = keccak256(subDigest);
238
+ const subDigestHashArray = getBytes(subDigestHash);
239
+
240
+ return signer.signMessage(subDigestHashArray);
241
+ };
242
+
243
+ export const getEip155ChainId = (chainId: number) => `eip155:${chainId}`;
@@ -0,0 +1,453 @@
1
+ import { MultiRollupApiClients } from '@imtbl/generated-clients';
2
+ import {
3
+ Flow, identify, trackError, trackFlow,
4
+ } from '@imtbl/metrics';
5
+ import {
6
+ JsonRpcProvider, Signer, toBeHex,
7
+ } from 'ethers';
8
+ import {
9
+ Provider,
10
+ ProviderEvent,
11
+ ProviderEventMap,
12
+ RequestArguments,
13
+ } from './types';
14
+ import { AuthManager } from '@imtbl/auth';
15
+ import TypedEventEmitter from '../utils/typedEventEmitter';
16
+ import { WalletConfiguration } from '../config';
17
+ import {
18
+ PassportEventMap, PassportEvents, User, UserZkEvm,
19
+ } from '../types';
20
+ import { RelayerClient } from './relayerClient';
21
+ import { JsonRpcError, ProviderErrorCode, RpcErrorCode } from './JsonRpcError';
22
+ import { registerZkEvmUser } from './user';
23
+ import { sendTransaction } from './sendTransaction';
24
+ import GuardianClient from '../guardian';
25
+ import { signTypedDataV4 } from './signTypedDataV4';
26
+ import { personalSign } from './personalSign';
27
+ import { trackSessionActivity } from './sessionActivity/sessionActivity';
28
+ import { getNonce } from './walletHelpers';
29
+ import { sendDeployTransactionAndPersonalSign } from './sendDeployTransactionAndPersonalSign';
30
+ import { signEjectionTransaction } from './signEjectionTransaction';
31
+
32
+ export type ZkEvmProviderInput = {
33
+ authManager: AuthManager;
34
+ config: WalletConfiguration;
35
+ multiRollupApiClients: MultiRollupApiClients;
36
+ passportEventEmitter: TypedEventEmitter<PassportEventMap>;
37
+ guardianClient: GuardianClient;
38
+ ethSigner: Signer;
39
+ user: User | null;
40
+ };
41
+
42
+ const isZkEvmUser = (user: User): user is UserZkEvm => 'zkEvm' in user;
43
+
44
+ export class ZkEvmProvider implements Provider {
45
+ readonly #authManager: AuthManager;
46
+
47
+ readonly #config: WalletConfiguration;
48
+
49
+ /**
50
+ * intended to emit EIP-1193 events
51
+ */
52
+ readonly #providerEventEmitter: TypedEventEmitter<ProviderEventMap>;
53
+
54
+ /**
55
+ * intended to emit internal Passport events
56
+ */
57
+ readonly #passportEventEmitter: TypedEventEmitter<ProviderEventMap>;
58
+
59
+ readonly #guardianClient: GuardianClient;
60
+
61
+ readonly #rpcProvider: JsonRpcProvider; // Used for read
62
+
63
+ readonly #multiRollupApiClients: MultiRollupApiClients;
64
+
65
+ readonly #relayerClient: RelayerClient;
66
+
67
+ readonly #ethSigner: Signer;
68
+
69
+ public readonly isPassport: boolean = true;
70
+
71
+ constructor({
72
+ authManager,
73
+ config,
74
+ multiRollupApiClients,
75
+ passportEventEmitter,
76
+ guardianClient,
77
+ ethSigner,
78
+ user,
79
+ }: ZkEvmProviderInput) {
80
+ this.#authManager = authManager;
81
+ this.#config = config;
82
+ this.#guardianClient = guardianClient;
83
+ this.#passportEventEmitter = passportEventEmitter;
84
+ this.#ethSigner = ethSigner;
85
+
86
+ this.#rpcProvider = new JsonRpcProvider(this.#config.zkEvmRpcUrl, undefined, {
87
+ staticNetwork: true,
88
+ });
89
+
90
+ this.#relayerClient = new RelayerClient({
91
+ config: this.#config,
92
+ rpcProvider: this.#rpcProvider,
93
+ authManager: this.#authManager,
94
+ });
95
+
96
+ this.#multiRollupApiClients = multiRollupApiClients;
97
+ this.#providerEventEmitter = new TypedEventEmitter<ProviderEventMap>();
98
+
99
+ if (user && isZkEvmUser(user)) {
100
+ this.#callSessionActivity(user.zkEvm.ethAddress);
101
+ }
102
+
103
+ passportEventEmitter.on(PassportEvents.LOGGED_IN, (loggedInUser: User) => {
104
+ if (isZkEvmUser(loggedInUser)) {
105
+ this.#callSessionActivity(loggedInUser.zkEvm.ethAddress);
106
+ }
107
+ });
108
+ passportEventEmitter.on(PassportEvents.LOGGED_OUT, this.#handleLogout);
109
+ passportEventEmitter.on(
110
+ PassportEvents.ACCOUNTS_REQUESTED,
111
+ trackSessionActivity,
112
+ );
113
+ }
114
+
115
+ #handleLogout = () => {
116
+ this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, []);
117
+ };
118
+
119
+ async #callSessionActivity(zkEvmAddress: string, clientId?: string) {
120
+ // SessionActivity requests are processed in nonce space 1, where as all
121
+ // other sendTransaction requests are processed in nonce space 0. This means
122
+ // we can submit a session activity request per SCW in parallel without a SCW
123
+ // INVALID_NONCE error.
124
+ const nonceSpace: bigint = BigInt(1);
125
+ const sendTransactionClosure = async (params: Array<any>, flow: Flow) => await sendTransaction({
126
+ params,
127
+ ethSigner: this.#ethSigner,
128
+ guardianClient: this.#guardianClient,
129
+ rpcProvider: this.#rpcProvider,
130
+ relayerClient: this.#relayerClient,
131
+ zkEvmAddress,
132
+ flow,
133
+ nonceSpace,
134
+ isBackgroundTransaction: true,
135
+ });
136
+ this.#passportEventEmitter.emit(PassportEvents.ACCOUNTS_REQUESTED, {
137
+ environment: this.#config.environment,
138
+ sendTransaction: sendTransactionClosure,
139
+ walletAddress: zkEvmAddress,
140
+ passportClient: clientId || 'wallet',
141
+ });
142
+ }
143
+
144
+ // Used to get the registered zkEvm address from the User session
145
+ async #getZkEvmAddress() {
146
+ try {
147
+ const user = await this.#authManager.getUser();
148
+ if (user && isZkEvmUser(user)) {
149
+ return user.zkEvm.ethAddress;
150
+ }
151
+ return undefined;
152
+ } catch {
153
+ return undefined;
154
+ }
155
+ }
156
+
157
+ async #performRequest(request: RequestArguments): Promise<any> {
158
+ // This is required for sending session activity events
159
+
160
+ switch (request.method) {
161
+ case 'eth_requestAccounts': {
162
+ const zkEvmAddress = await this.#getZkEvmAddress();
163
+ if (zkEvmAddress) return [zkEvmAddress];
164
+
165
+ const flow = trackFlow('passport', 'ethRequestAccounts');
166
+
167
+ try {
168
+ const user = await this.#authManager.getUserOrLogin();
169
+ flow.addEvent('endGetUserOrLogin');
170
+
171
+ let userZkEvmEthAddress: string | undefined;
172
+
173
+ if (!isZkEvmUser(user)) {
174
+ flow.addEvent('startUserRegistration');
175
+
176
+ userZkEvmEthAddress = await registerZkEvmUser({
177
+ ethSigner: this.#ethSigner,
178
+ authManager: this.#authManager,
179
+ multiRollupApiClients: this.#multiRollupApiClients,
180
+ accessToken: user.accessToken,
181
+ rpcProvider: this.#rpcProvider,
182
+ flow,
183
+ });
184
+ flow.addEvent('endUserRegistration');
185
+ } else {
186
+ userZkEvmEthAddress = user.zkEvm.ethAddress;
187
+ }
188
+
189
+ this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, [
190
+ userZkEvmEthAddress,
191
+ ]);
192
+ identify({
193
+ passportId: user.profile.sub,
194
+ });
195
+ this.#callSessionActivity(userZkEvmEthAddress);
196
+ return [userZkEvmEthAddress];
197
+ } catch (error) {
198
+ if (error instanceof Error) {
199
+ trackError('passport', 'ethRequestAccounts', error, { flowId: flow.details.flowId });
200
+ } else {
201
+ flow.addEvent('errored');
202
+ }
203
+ throw error;
204
+ } finally {
205
+ flow.addEvent('End');
206
+ }
207
+ }
208
+ case 'eth_sendTransaction': {
209
+ const zkEvmAddress = await this.#getZkEvmAddress();
210
+ if (!zkEvmAddress) {
211
+ throw new JsonRpcError(
212
+ ProviderErrorCode.UNAUTHORIZED,
213
+ 'Unauthorised - call eth_requestAccounts first',
214
+ );
215
+ }
216
+
217
+ const flow = trackFlow('passport', 'ethSendTransaction');
218
+
219
+ try {
220
+ return await this.#guardianClient.withConfirmationScreen({
221
+ width: 480,
222
+ height: 720,
223
+ })(async () => await sendTransaction({
224
+ params: request.params || [],
225
+ ethSigner: this.#ethSigner,
226
+ guardianClient: this.#guardianClient,
227
+ rpcProvider: this.#rpcProvider,
228
+ relayerClient: this.#relayerClient,
229
+ zkEvmAddress,
230
+ flow,
231
+ }));
232
+ } catch (error) {
233
+ if (error instanceof Error) {
234
+ trackError('passport', 'eth_sendTransaction', error, { flowId: flow.details.flowId });
235
+ } else {
236
+ flow.addEvent('errored');
237
+ }
238
+ throw error;
239
+ } finally {
240
+ flow.addEvent('End');
241
+ }
242
+ }
243
+ case 'eth_accounts': {
244
+ const zkEvmAddress = await this.#getZkEvmAddress();
245
+ return zkEvmAddress ? [zkEvmAddress] : [];
246
+ }
247
+ case 'personal_sign': {
248
+ const zkEvmAddress = await this.#getZkEvmAddress();
249
+ if (!zkEvmAddress) {
250
+ throw new JsonRpcError(
251
+ ProviderErrorCode.UNAUTHORIZED,
252
+ 'Unauthorised - call eth_requestAccounts first',
253
+ );
254
+ }
255
+
256
+ const flow = trackFlow('passport', 'personalSign');
257
+
258
+ try {
259
+ return await this.#guardianClient.withConfirmationScreen({
260
+ width: 480,
261
+ height: 720,
262
+ })(async () => {
263
+ if (this.#config.forceScwDeployBeforeMessageSignature) {
264
+ // Check if the smart contract wallet has been deployed
265
+ const nonce = await getNonce(this.#rpcProvider, zkEvmAddress);
266
+ if (!(nonce > BigInt(0))) {
267
+ // If the smart contract wallet has not been deployed,
268
+ // submit a transaction before signing the message
269
+ return await sendDeployTransactionAndPersonalSign({
270
+ params: request.params || [],
271
+ zkEvmAddress,
272
+ ethSigner: this.#ethSigner,
273
+ rpcProvider: this.#rpcProvider,
274
+ guardianClient: this.#guardianClient,
275
+ relayerClient: this.#relayerClient,
276
+ flow,
277
+ });
278
+ }
279
+ }
280
+
281
+ return await personalSign({
282
+ params: request.params || [],
283
+ zkEvmAddress,
284
+ ethSigner: this.#ethSigner,
285
+ rpcProvider: this.#rpcProvider,
286
+ guardianClient: this.#guardianClient,
287
+ relayerClient: this.#relayerClient,
288
+ flow,
289
+ });
290
+ });
291
+ } catch (error) {
292
+ if (error instanceof Error) {
293
+ trackError('passport', 'personal_sign', error, { flowId: flow.details.flowId });
294
+ } else {
295
+ flow.addEvent('errored');
296
+ }
297
+ throw error;
298
+ } finally {
299
+ flow.addEvent('End');
300
+ }
301
+ }
302
+ case 'eth_signTypedData':
303
+ case 'eth_signTypedData_v4': {
304
+ const zkEvmAddress = await this.#getZkEvmAddress();
305
+ if (!zkEvmAddress) {
306
+ throw new JsonRpcError(
307
+ ProviderErrorCode.UNAUTHORIZED,
308
+ 'Unauthorised - call eth_requestAccounts first',
309
+ );
310
+ }
311
+
312
+ const flow = trackFlow('passport', 'ethSignTypedDataV4');
313
+
314
+ try {
315
+ return await this.#guardianClient.withConfirmationScreen({
316
+ width: 480,
317
+ height: 720,
318
+ })(async () => await signTypedDataV4({
319
+ method: request.method,
320
+ params: request.params || [],
321
+ ethSigner: this.#ethSigner,
322
+ rpcProvider: this.#rpcProvider,
323
+ relayerClient: this.#relayerClient,
324
+ guardianClient: this.#guardianClient,
325
+ flow,
326
+ }));
327
+ } catch (error) {
328
+ if (error instanceof Error) {
329
+ trackError('passport', 'eth_signTypedData', error, { flowId: flow.details.flowId });
330
+ } else {
331
+ flow.addEvent('errored');
332
+ }
333
+ throw error;
334
+ } finally {
335
+ flow.addEvent('End');
336
+ }
337
+ }
338
+ case 'eth_chainId': {
339
+ // Call detect network to fetch the chainId so to take advantage of
340
+ // the caching layer provided by StaticJsonRpcProvider.
341
+ // In case Passport is changed from StaticJsonRpcProvider to a
342
+ // JsonRpcProvider, this function will still work as expected given
343
+ // that detectNetwork call _uncachedDetectNetwork which will force
344
+ // the provider to re-fetch the chainId from remote.
345
+ const { chainId } = await this.#rpcProvider.getNetwork();
346
+ return toBeHex(chainId);
347
+ }
348
+ // Pass through methods
349
+ case 'eth_getBalance':
350
+ case 'eth_getCode':
351
+ case 'eth_getTransactionCount': {
352
+ const [address, blockNumber] = request.params || [];
353
+ return this.#rpcProvider.send(request.method, [
354
+ address,
355
+ blockNumber || 'latest',
356
+ ]);
357
+ }
358
+ case 'eth_getStorageAt': {
359
+ const [address, storageSlot, blockNumber] = request.params || [];
360
+ return this.#rpcProvider.send(request.method, [
361
+ address,
362
+ storageSlot,
363
+ blockNumber || 'latest',
364
+ ]);
365
+ }
366
+ case 'eth_call':
367
+ case 'eth_estimateGas': {
368
+ const [transaction, blockNumber] = request.params || [];
369
+ return this.#rpcProvider.send(request.method, [
370
+ transaction,
371
+ blockNumber || 'latest',
372
+ ]);
373
+ }
374
+ case 'eth_gasPrice':
375
+ case 'eth_blockNumber':
376
+ case 'eth_getBlockByHash':
377
+ case 'eth_getBlockByNumber':
378
+ case 'eth_getTransactionByHash':
379
+ case 'eth_getTransactionReceipt': {
380
+ return this.#rpcProvider.send(request.method, request.params || []);
381
+ }
382
+ case 'im_signEjectionTransaction': {
383
+ const zkEvmAddress = await this.#getZkEvmAddress();
384
+ if (!zkEvmAddress) {
385
+ throw new JsonRpcError(
386
+ ProviderErrorCode.UNAUTHORIZED,
387
+ 'Unauthorised - call eth_requestAccounts first',
388
+ );
389
+ }
390
+
391
+ const flow = trackFlow('passport', 'imSignEjectionTransaction');
392
+
393
+ try {
394
+ return await signEjectionTransaction({
395
+ params: request.params || [],
396
+ ethSigner: this.#ethSigner,
397
+ zkEvmAddress,
398
+ flow,
399
+ });
400
+ } catch (error) {
401
+ if (error instanceof Error) {
402
+ trackError('passport', 'imSignEjectionTransaction', error, { flowId: flow.details.flowId });
403
+ } else {
404
+ flow.addEvent('errored');
405
+ }
406
+ throw error;
407
+ } finally {
408
+ flow.addEvent('End');
409
+ }
410
+ }
411
+ case 'im_addSessionActivity': {
412
+ const [clientId] = request.params || [];
413
+ const zkEvmAddress = await this.#getZkEvmAddress();
414
+ if (zkEvmAddress) {
415
+ this.#callSessionActivity(zkEvmAddress, clientId);
416
+ }
417
+ return null;
418
+ }
419
+ default: {
420
+ throw new JsonRpcError(
421
+ ProviderErrorCode.UNSUPPORTED_METHOD,
422
+ 'Method not supported',
423
+ );
424
+ }
425
+ }
426
+ }
427
+
428
+ public async request(request: RequestArguments): Promise<any> {
429
+ try {
430
+ return this.#performRequest(request);
431
+ } catch (error: unknown) {
432
+ if (error instanceof JsonRpcError) {
433
+ throw error;
434
+ }
435
+ if (error instanceof Error) {
436
+ throw new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, error.message);
437
+ }
438
+
439
+ throw new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, 'Internal error');
440
+ }
441
+ }
442
+
443
+ public on(event: string, listener: (...args: any[]) => void): void {
444
+ this.#providerEventEmitter.on(event, listener);
445
+ }
446
+
447
+ public removeListener(
448
+ event: string,
449
+ listener: (...args: any[]) => void,
450
+ ): void {
451
+ this.#providerEventEmitter.removeListener(event, listener);
452
+ }
453
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDirs": ["src"],
6
+ "customConditions": ["development"],
7
+ "types": ["node"]
8
+ },
9
+ "include": ["src", "src/types.ts"],
10
+ "exclude": [
11
+ "node_modules",
12
+ "dist",
13
+ ]
14
+ }
15
+