diff --git a/.changeset/spicy-bugs-compete.md b/.changeset/spicy-bugs-compete.md new file mode 100644 index 0000000000..c473f1efea --- /dev/null +++ b/.changeset/spicy-bugs-compete.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +**zkSync Extension:** Added support for `zks_` namespace + actions. diff --git a/.env.example b/.env.example index 68302194ac..85235afd5a 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,12 @@ VITE_ACCOUNT_PRIVATE_KEY= VITE_ANVIL_FORK_URL= VITE_ANVIL_FORK_URL_OPTIMISM= +VITE_ANVIL_BLOCK_TIME=1 +VITE_ANVIL_BLOCK_NUMBER=16280770 +VITE_ANVIL_BLOCK_NUMBER_OPTIMISM=112157024 +VITE_ANVIL_PORT=8545 +VITE_ANVIL_PORT_OPTIMISM=8645 VITE_BATCH_JSON_RPC=false VITE_BATCH_MULTICALL=false -VITE_NETWORK_TRANSPORT_MODE=http \ No newline at end of file +VITE_NETWORK_TRANSPORT_MODE=http +VITE_RPC_URL_OPTIMISM= \ No newline at end of file diff --git a/site/pages/zksync/actions/estimateFee.md b/site/pages/zksync/actions/estimateFee.md new file mode 100644 index 0000000000..3ed39df2a8 --- /dev/null +++ b/site/pages/zksync/actions/estimateFee.md @@ -0,0 +1,198 @@ +--- +description: Returns an estimated Fee for requested transaction. +--- + +# estimateFee + +Returns an estimated Fee for requested transaction. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const fee = await client.estimateFee({ + account: '0x636A122e48079f750d44d13E5b39804227E1467e', + to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + value: 0n +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Fee` + +The fee values. + +## Parameters + +### account + +- **Type:** `Account | Address` + +The Account to send the transaction from. + +Accepts a [JSON-RPC Account](/docs/clients/wallet#json-rpc-accounts) or [Local Account (Private Key, etc)](/docs/clients/wallet#local-accounts-private-key-mnemonic-etc). + +```ts +const fee = await walletClient.estimateFee({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n +}) +``` + +### to + +- **Type:** `0x${string}` + +The transaction recipient or contract address. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + value: 1000000000000000000n, + nonce: 69 +}) +``` + +### data (optional) + +- **Type:** `0x${string}` + +A contract hashed method call with encoded args. + +```ts +const fee = await walletClient.estimateFee({ + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n +}) +``` + +### gasPrice (optional) + +- **Type:** `bigint` + +The price (in wei) to pay per gas. Only applies to [Legacy Transactions](/docs/glossary/terms#legacy-transaction). + +```ts +const fee = await walletClient.estimateFee({ + account, + gasPrice: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n +}) +``` + +### nonce (optional) + +- **Type:** `number` + +Unique number identifying this transaction. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n, + nonce: 69 // [!code focus] +}) +``` + +### value (optional) + +- **Type:** `bigint` + +Value in wei sent with this transaction. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), // [!code focus] + nonce: 69 +}) +``` + +### gasPerPubdata (optional) + +- **Type:** `bigint` + +The amount of gas for publishing one byte of data on Ethereum. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + gasPerPubdata: 50000, // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` + +### factoryDeps (optional) + +- **Type:** `[0x${string}]` + +Contains bytecode of the deployed contract. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + factoryDeps: ['0xcde...'], // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` + +### paymaster (optional) + +- **Type:** `Account | Address` + +Address of the paymaster account that will pay the fees. The `paymasterInput` field is required with this one. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + paymaster: '0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021', // [!code focus] + paymasterInput: '0x8c5a...' // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` + +### paymasterInput (optional) + +- **Type:** `0x${string}` + +Input data to the paymaster. The `paymaster` field is required with this one. + +```ts +const fee = await walletClient.estimateFee({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + paymaster: '0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021', // [!code focus] + paymasterInput: '0x8c5a...' // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/estimateGasL1ToL2.md b/site/pages/zksync/actions/estimateGasL1ToL2.md new file mode 100644 index 0000000000..4f415cbf31 --- /dev/null +++ b/site/pages/zksync/actions/estimateGasL1ToL2.md @@ -0,0 +1,197 @@ +--- +description: Returns an estimated gas for L1 to L2 execution. +--- + +# estimateGasL1ToL2 + +Returns an estimated gas for L1 to L2 execution + +## Usage + +:::code-group +```ts [example.ts] +import { client } from './config' + +const gas = await client.estimateGasL1ToL2({ + account: '0x636A122e48079f750d44d13E5b39804227E1467e', + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 0n +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`bigint` + +The estimated gas value. + +## Parameters + +### account + +- **Type:** `Account | Address` + +The Account to send the transaction from. + +Accepts a [JSON-RPC Account](/docs/clients/wallet#json-rpc-accounts) or [Local Account (Private Key, etc)](/docs/clients/wallet#local-accounts-private-key-mnemonic-etc). + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n +}) +``` + +### to + +- **Type:** `0x${string}` + +The transaction recipient or contract address. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + value: 1000000000000000000n, + nonce: 69 +}) +``` + +### data (optional) + +- **Type:** `0x${string}` + +A contract hashed method call with encoded args. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n +}) +``` + +### gasPrice (optional) + +- **Type:** `bigint` + +The price (in wei) to pay per gas. Only applies to [Legacy Transactions](/docs/glossary/terms#legacy-transaction). + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + gasPrice: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n +}) +``` + +### nonce (optional) + +- **Type:** `number` + +Unique number identifying this transaction. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: 1000000000000000000n, + nonce: 69 // [!code focus] +}) +``` + +### value (optional) + +- **Type:** `bigint` + +Value in wei sent with this transaction. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), // [!code focus] + nonce: 69 +}) +``` + +### gasPerPubdata (optional) + +- **Type:** `bigint` + +The amount of gas for publishing one byte of data on Ethereum. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + gasPerPubdata: 50000, // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` + +### factoryDeps (optional) + +- **Type:** `[0x${string}]` + +Contains bytecode of the deployed contract. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + factoryDeps: ['0xcde...'], // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` + +### paymaster (optional) + +- **Type:** `Account | Address` + +Address of the paymaster account that will pay the gass. The `paymasterInput` field is required with this one. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + paymaster: '0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021', // [!code focus] + paymasterInput: '0x8c5a...' // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` + +### paymasterInput (optional) + +- **Type:** `0x${string}` + +Input data to the paymaster. The `paymaster` field is required with this one. + +```ts +const gas = await walletClient.estimateGasL1ToL2({ + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + paymaster: '0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021', // [!code focus] + paymasterInput: '0x8c5a...' // [!code focus] + nonce: 69, + value: 1000000000000000000n +}) +``` diff --git a/site/pages/zksync/actions/getAllBalances.md b/site/pages/zksync/actions/getAllBalances.md new file mode 100644 index 0000000000..7b3e930db1 --- /dev/null +++ b/site/pages/zksync/actions/getAllBalances.md @@ -0,0 +1,59 @@ +--- +description: Returns all known balances for a given account. +--- + +# getAllBalances + +Returns all known balances for a given account. + +## Usage + +:::code-group + +```ts [example.ts] +import { client, account } from './config' + +const balances = await client.getAllBalances({ + account +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) + +// JSON-RPC Account +export const account = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' +// Local Account +export const account = privateKeyToAccount(...) + +``` +::: + +## Returns + +`GetAllBalancesReturnType` + +Array of all known balances for an address. + +## Parameters + +### account + +- **Type:** `Account | Address` + +The Account used for check. + +Accepts a [JSON-RPC Account](/docs/clients/wallet#json-rpc-accounts) or [Local Account (Private Key, etc)](/docs/clients/wallet#local-accounts-private-key-mnemonic-etc). + +```ts +const balances = await client.getAllBalances({ + account: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" // [!code focus] +}); +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/getBaseTokenL1Address.md b/site/pages/zksync/actions/getBaseTokenL1Address.md new file mode 100644 index 0000000000..0951c8b7a1 --- /dev/null +++ b/site/pages/zksync/actions/getBaseTokenL1Address.md @@ -0,0 +1,35 @@ +--- +description: Returns the base token L1 address. +--- + +# getBaseTokenL1Address + +Returns the address of the base L1 token. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const address = await client.getBaseTokenL1Address(); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Address` + +Base Token L1 address. \ No newline at end of file diff --git a/site/pages/zksync/actions/getBlockDetails.md b/site/pages/zksync/actions/getBlockDetails.md new file mode 100644 index 0000000000..9b1c3da769 --- /dev/null +++ b/site/pages/zksync/actions/getBlockDetails.md @@ -0,0 +1,51 @@ +--- +description: Returns additional zkSync-specific information about the L2 block. +--- + +# getBlockDetails + +Returns additional zkSync-specific information about the L2 block. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const blockDetails = await client.getBlockDetails({ + number: 1 +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`BaseBlockDetails` + +Structure that represent zkSync-specific information about L2 block. + +## Parameters + +### number + +Block Number + +- **Type** `number` + +```ts +const blockDetails = await client.getBlockDetails({ + number: 1 // [!code focus] +}); +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/getBridgehubContractAddress.md b/site/pages/zksync/actions/getBridgehubContractAddress.md new file mode 100644 index 0000000000..837738b5e4 --- /dev/null +++ b/site/pages/zksync/actions/getBridgehubContractAddress.md @@ -0,0 +1,35 @@ +--- +description: Returns the Bridgehub smart contract address. +--- + +# getBridgehubContractAddress + +Returns the Bridgehub smart contract address. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const address = await client.getBridgehubContractAddress(); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Address` + +Bridgehub smart contract address. \ No newline at end of file diff --git a/site/pages/zksync/actions/getDefaultBridgeAddress.md b/site/pages/zksync/actions/getDefaultBridgeAddress.md new file mode 100644 index 0000000000..5276b8dfb9 --- /dev/null +++ b/site/pages/zksync/actions/getDefaultBridgeAddress.md @@ -0,0 +1,35 @@ +--- +description: Returns the addresses of the default zkSync Era bridge contracts on both L1 and L2. +--- + +# getDefaultBridgeAddresses + +Returns the addresses of the default zkSync Era bridge contracts on both L1 and L2. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const addresses = await client.getDefaultBridgeAddresses(); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`GetDefaultBridgeAddressesReturnType` + +Addresses of the default zkSync Era bridge contracts on both L1 and L2. diff --git a/site/pages/zksync/actions/getL1BatchBlockRange.md b/site/pages/zksync/actions/getL1BatchBlockRange.md new file mode 100644 index 0000000000..1f3d1bc08b --- /dev/null +++ b/site/pages/zksync/actions/getL1BatchBlockRange.md @@ -0,0 +1,51 @@ +--- +description: Returns the range of blocks contained within a batch given by batch number. +--- + +# getL1BatchBlockRange + +Returns the range of blocks contained within a batch given by batch number. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const batchBlockRange = await client.getL1BatchBlockRange({ + number: 1 +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`GetL1BatchBlockRangeReturnType` + +Array of two elements representing the range of blocks within a batch. + +## Parameters + +### number + +L1 Batch Number + +- **Type** `number` + +```ts +const batchBlockRange = await client.getL1BatchBlockRange({ + number: 1 // [!code focus] +}); +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/getL1BatchDetails.md b/site/pages/zksync/actions/getL1BatchDetails.md new file mode 100644 index 0000000000..4aef5bce4b --- /dev/null +++ b/site/pages/zksync/actions/getL1BatchDetails.md @@ -0,0 +1,51 @@ +--- +description: Returns data pertaining to a given batch. +--- + +# getL1BatchDetails + +Returns data pertaining to a given batch. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const batchDetails = await client.getL1BatchDetails({ + number: 1 +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`GetL1BatchDetailsReturnType` + +Batch details. + +## Parameters + +### number + +L1 Batch Number + +- **Type** `number` + +```ts +const batchDetails = await client.getL1BatchDetails({ + number: 1 // [!code focus] +}); +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/getL1BatchNumber.md b/site/pages/zksync/actions/getL1BatchNumber.md new file mode 100644 index 0000000000..6feee9b2a0 --- /dev/null +++ b/site/pages/zksync/actions/getL1BatchNumber.md @@ -0,0 +1,34 @@ +--- +description: Returns the latest L1 batch number. +--- + +# getL1BatchNumber + +Returns the latest L1 batch number. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const latestNumber = await client.getL1BatchNumber(); +``` +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Hex` + +Latest L1 batch number. \ No newline at end of file diff --git a/site/pages/zksync/actions/getL1ChainId.md b/site/pages/zksync/actions/getL1ChainId.md new file mode 100644 index 0000000000..1d0cc398f3 --- /dev/null +++ b/site/pages/zksync/actions/getL1ChainId.md @@ -0,0 +1,35 @@ +--- +description: Returns the Chain Id of underlying L1 network. +--- + +# getL1ChainId + +Returns the Chain Id of underlying L1 network. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const chainId = await client.getL1ChainId(); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Hex` + +L1 Chain ID. \ No newline at end of file diff --git a/site/pages/zksync/actions/getLogProof.md b/site/pages/zksync/actions/getLogProof.md new file mode 100644 index 0000000000..bc9e351366 --- /dev/null +++ b/site/pages/zksync/actions/getLogProof.md @@ -0,0 +1,63 @@ +--- +description: Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log. +--- + +# getLogProof + +Returns the proof for the corresponding L2 to L1 log. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const proof = await client.getLogProof({ + txHash: '0x...', + index: 1 +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`GetLogProofReturnType` + +Proof of the corresponding L2 to L1 log + +## Parameters + +### txHash + +Hash of the L2 transaction the L2 to L1 log was produced within. + +```ts +const proof = await client.getLogProof({ + txHash: '0x...', // [!code focus] + index: 1 +}); +``` + +### index (optional) + +The index of the L2 to L1 log in the transaction. + +```ts +const proof = await client.getLogProof({ + txHash: '0x...', + index: 1 // [!code focus] +}); + +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/getMainContractAddress.md b/site/pages/zksync/actions/getMainContractAddress.md new file mode 100644 index 0000000000..3cd1ca3b28 --- /dev/null +++ b/site/pages/zksync/actions/getMainContractAddress.md @@ -0,0 +1,35 @@ +--- +description: Returns the address of a Main zkSync Contract. +--- + +# getMainContractAddress + +Returns the address of a Main zkSync Contract. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const address = await client.getMainContractAddress(); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Address` + +Main zkSync Era smart contract address. \ No newline at end of file diff --git a/site/pages/zksync/actions/getRawBlockTransactions.md b/site/pages/zksync/actions/getRawBlockTransactions.md new file mode 100644 index 0000000000..c164e1b899 --- /dev/null +++ b/site/pages/zksync/actions/getRawBlockTransactions.md @@ -0,0 +1,50 @@ +--- +description: Returns data of transactions in a block. +--- + +# getRawBlockTransaction + +Returns data of transactions in a block. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + + +const rawTx = await client.getRawBlockTransaction({ + number: 1 +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`RawBlockTransactions` + +Data of transactions in a block. + +## Parameters + +### number + +Block number. + +```ts +const rawTx = await client.getRawBlockTransaction({ + number: 1 // [!code focus] +}); +``` \ No newline at end of file diff --git a/site/pages/zksync/actions/getTestnetPaymasterAddress.md b/site/pages/zksync/actions/getTestnetPaymasterAddress.md new file mode 100644 index 0000000000..d6a839e989 --- /dev/null +++ b/site/pages/zksync/actions/getTestnetPaymasterAddress.md @@ -0,0 +1,34 @@ +--- +description: Returns the address of a Paymaster on a Testnet. +--- + +# getTestnetPaymasterAddress + +Returns the address of a Paymaster on a Testnet. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' +const address = await client.getTestnetPaymasterAddress(); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`Address | null` + +Testnet paymaster address if available, or `null`. \ No newline at end of file diff --git a/site/pages/zksync/actions/getTransactionDetails.md b/site/pages/zksync/actions/getTransactionDetails.md new file mode 100644 index 0000000000..3ecd419d1d --- /dev/null +++ b/site/pages/zksync/actions/getTransactionDetails.md @@ -0,0 +1,51 @@ +--- +description: Returns data from a specific transaction given by the transaction hash. +--- + +# getTransactionDetails + +Returns data from a specific transaction given by the transaction hash. + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const details = await client.getTransactionDetails({ + txHash: '0x...' +}); +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zkSync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zkSync, + transport: http(), +}).extend(publicActionsL2()) +``` +::: + +## Returns + +`TransactionDetails` + +Data from a specific transaction given by the transaction hash. + +## Parameters + +`GetTransactionDetailsParameters` + +### hash + +Transaction hash + +```ts +const details = await client.getTransactionDetails({ + txHash: '0x...' // [!code focus] +}); +``` diff --git a/site/sidebar.ts b/site/sidebar.ts index 2616a87985..84ad9123bc 100644 --- a/site/sidebar.ts +++ b/site/sidebar.ts @@ -1353,20 +1353,94 @@ export const sidebar = { text: 'Actions', items: [ { - text: 'deployContract', - link: '/zksync/actions/deployContract', - }, - { - text: 'sendTransaction', - link: '/zksync/actions/sendTransaction', - }, - { - text: 'signTransaction', - link: '/zksync/actions/signTransaction', + text: 'EIP-712 Actions', + items: [ + { + text: 'deployContract', + link: '/zksync/actions/deployContract', + }, + { + text: 'sendTransaction', + link: '/zksync/actions/sendTransaction', + }, + { + text: 'signTransaction', + link: '/zksync/actions/signTransaction', + }, + { + text: 'writeContract', + link: '/zksync/actions/writeContract', + }, + ], }, { - text: 'writeContract', - link: '/zksync/actions/writeContract', + text: 'L2 Public Actions', + items: [ + { + text: 'estimateFee', + link: '/zksync/actions/estimateFee', + }, + { + text: 'estimateGasL1ToL2', + link: '/zksync/actions/estimateGasL1ToL2', + }, + { + text: 'getAllBalances', + link: '/zksync/actions/getAllBalances', + }, + { + text: 'getBaseTokenL1Address', + link: '/zksync/actions/getBaseTokenL1Address', + }, + { + text: 'getBlockDetails', + link: '/zksync/actions/getBlockDetails', + }, + { + text: 'getBridgehubContractAddress', + link: '/zksync/actions/getBridgehubContractAddress', + }, + { + text: 'getDefaultBridgeAddress', + link: '/zksync/actions/getDefaultBridgeAddress', + }, + { + text: 'getL1BatchDetails', + link: '/zksync/actions/getL1BatchDetails', + }, + { + text: 'getL1BatchBlockRange', + link: '/zksync/actions/getL1BatchBlockRange', + }, + { + text: 'getL1BatchNumber', + link: '/zksync/actions/getL1BatchNumber', + }, + { + text: 'getL1ChainId', + link: '/zksync/actions/getL1ChainId', + }, + { + text: 'getLogProof', + link: '/zksync/actions/getLogProof', + }, + { + text: 'getMainContractAddress', + link: '/zksync/actions/getMainContractAddress', + }, + { + text: 'getRawBlockTransaction', + link: '/zksync/actions/getRawBlockTransactions', + }, + { + text: 'getTestnetPaymasterAddress', + link: '/zksync/actions/getTestnetPaymasterAddress', + }, + { + text: 'getTransactionDetails', + link: '/zksync/actions/getTransactionDetails', + }, + ], }, ], }, diff --git a/src/zksync/actions/estimateFee.test.ts b/src/zksync/actions/estimateFee.test.ts new file mode 100644 index 0000000000..c5be5b11b2 --- /dev/null +++ b/src/zksync/actions/estimateFee.test.ts @@ -0,0 +1,48 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, + zkSyncClientLocalNodeWithAccount, +} from '../../../test/src/zksync.js' +import { estimateFee } from './estimateFee.js' + +const client = { ...zkSyncClientLocalNode } + +const clientWithAccount = { ...zkSyncClientLocalNodeWithAccount } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const fee = await estimateFee(client, { + account: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + to: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + value: 0n, + }) + + expect(fee).toMatchInlineSnapshot(` + { + "gasLimit": 163901n, + "gasPerPubdataLimit": 66n, + "maxFeePerGas": 250000000n, + "maxPriorityFeePerGas": 0n, + } + `) +}) + +mockClientPublicActionsL2(clientWithAccount) + +test('default with account', async () => { + const fee = await estimateFee(clientWithAccount, { + to: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + value: 0n, + }) + + expect(fee).toMatchInlineSnapshot(` + { + "gasLimit": 163901n, + "gasPerPubdataLimit": 66n, + "maxFeePerGas": 250000000n, + "maxPriorityFeePerGas": 0n, + } + `) +}) diff --git a/src/zksync/actions/estimateFee.ts b/src/zksync/actions/estimateFee.ts new file mode 100644 index 0000000000..d54b626448 --- /dev/null +++ b/src/zksync/actions/estimateFee.ts @@ -0,0 +1,46 @@ +import { parseAccount } from '../../accounts/utils/parseAccount.js' +import type { SendTransactionParameters } from '../../actions/wallet/sendTransaction.js' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import { hexToBigInt } from '../../utils/encoding/fromHex.js' +import type { ChainEIP712 } from '../types/chain.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' +import type { ZkSyncFee } from '../types/fee.js' + +export type EstimateFeeParameters< + chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, + account extends Account | undefined = Account | undefined, + chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, +> = SendTransactionParameters + +export type EstimateFeeReturnType = ZkSyncFee + +export async function estimateFee< + chain extends ChainEIP712 | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: EstimateFeeParameters, +): Promise { + const { account: account_, ...request } = parameters + const account = account_ ? parseAccount(account_) : client.account + + const formatters = client.chain?.formatters + const formatted = formatters?.transactionRequest?.format({ + ...request, + from: account?.address, + }) + + const result = await client.request({ + method: 'zks_estimateFee', + params: [formatted], + }) + + return { + gasLimit: hexToBigInt(result.gas_limit), + gasPerPubdataLimit: hexToBigInt(result.gas_per_pubdata_limit), + maxPriorityFeePerGas: hexToBigInt(result.max_priority_fee_per_gas), + maxFeePerGas: hexToBigInt(result.max_fee_per_gas), + } +} diff --git a/src/zksync/actions/estimateGasL1ToL2.test.ts b/src/zksync/actions/estimateGasL1ToL2.test.ts new file mode 100644 index 0000000000..f9ece9ebe3 --- /dev/null +++ b/src/zksync/actions/estimateGasL1ToL2.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, + zkSyncClientLocalNodeWithAccount, +} from '../../../test/src/zksync.js' +import { estimateGasL1ToL2 } from './estimateGasL1ToL2.js' + +const client = { ...zkSyncClientLocalNode } + +const clientWithAccount = { ...zkSyncClientLocalNodeWithAccount } + +mockClientPublicActionsL2(client) +mockClientPublicActionsL2(clientWithAccount) + +test('default with account', async () => { + const value = await estimateGasL1ToL2(clientWithAccount, { + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 70n, + }) + expect(value > 0).to.be.true +}) + +test('default without account', async () => { + const value = await estimateGasL1ToL2(client, { + account: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 70n, + }) + expect(value > 0).to.be.true +}) diff --git a/src/zksync/actions/estimateGasL1ToL2.ts b/src/zksync/actions/estimateGasL1ToL2.ts new file mode 100644 index 0000000000..8d89e1778e --- /dev/null +++ b/src/zksync/actions/estimateGasL1ToL2.ts @@ -0,0 +1,38 @@ +import type { SendTransactionParameters } from '../../actions/wallet/sendTransaction.js' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import { parseAccount } from '../../utils/accounts.js' +import type { ChainEIP712 } from '../types/chain.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type EstimateGasL1ToL2Parameters< + chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, + account extends Account | undefined = Account | undefined, +> = SendTransactionParameters + +export type EstimateGasL1ToL2ReturnType = bigint + +export async function estimateGasL1ToL2< + chain extends ChainEIP712 | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: EstimateGasL1ToL2Parameters, +): Promise { + const { account: account_, ...request } = parameters + const account = account_ ? parseAccount(account_) : client.account + + const formatters = client.chain?.formatters + const formatted = formatters?.transactionRequest?.format({ + ...request, + from: account?.address, + }) + + const result = await client.request({ + method: 'zks_estimateGasL1ToL2', + params: [formatted], + }) + + return result +} diff --git a/src/zksync/actions/getAllBalances.test.ts b/src/zksync/actions/getAllBalances.test.ts new file mode 100644 index 0000000000..fe111377e5 --- /dev/null +++ b/src/zksync/actions/getAllBalances.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from 'vitest' +import { + mockAddress, + mockClientPublicActionsL2, + zkSyncClientLocalNode, + zkSyncClientLocalNodeWithAccount, +} from '../../../test/src/zksync.js' +import { getAllBalances } from './getAllBalances.js' + +const client = { ...zkSyncClientLocalNode } +const clientWithAccount = zkSyncClientLocalNodeWithAccount + +mockClientPublicActionsL2(client) +mockClientPublicActionsL2(clientWithAccount) + +test('default with account hoisting', async () => { + const balances = await getAllBalances(clientWithAccount, {}) + + expect(balances).toMatchInlineSnapshot(` + { + "0x0000000000000000000000000000000000000000": 1000000000000000000n, + "0x0000000000000000000000000000000000000001": 2000000000000000000n, + "0x0000000000000000000000000000000000000002": 3500000000000000000n, + } + `) +}) + +test('default without account hoisting', async () => { + const balances = await getAllBalances(client, { + account: mockAddress, + }) + + expect(balances).toMatchInlineSnapshot(` + { + "0x0000000000000000000000000000000000000000": 1000000000000000000n, + "0x0000000000000000000000000000000000000001": 2000000000000000000n, + "0x0000000000000000000000000000000000000002": 3500000000000000000n, + } + `) +}) diff --git a/src/zksync/actions/getAllBalances.ts b/src/zksync/actions/getAllBalances.ts new file mode 100644 index 0000000000..f1c1c6a6b5 --- /dev/null +++ b/src/zksync/actions/getAllBalances.ts @@ -0,0 +1,33 @@ +import type { Address } from 'abitype' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account, GetAccountParameter } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import { parseAccount } from '../../utils/accounts.js' +import { hexToBigInt } from '../../utils/encoding/fromHex.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetAllBalancesParameters< + TAccount extends Account | undefined = Account | undefined, +> = GetAccountParameter + +export type GetAllBalancesReturnType = { [key: Address]: bigint } + +export async function getAllBalances< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetAllBalancesParameters, +): Promise { + const { account: account_ } = parameters + const account = account_ ? parseAccount(account_) : client.account + const balances = await client.request({ + method: 'zks_getAllAccountBalances', + params: [account!.address], + }) + const convertedBalances: GetAllBalancesReturnType = {} + for (const token in balances) + (convertedBalances as any)[token] = hexToBigInt((balances as any)[token]) + return convertedBalances +} diff --git a/src/zksync/actions/getBaseTokenL1Address.test.ts b/src/zksync/actions/getBaseTokenL1Address.test.ts new file mode 100644 index 0000000000..ac9640163d --- /dev/null +++ b/src/zksync/actions/getBaseTokenL1Address.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' +import { + mockBaseTokenL1Address, + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' + +import { getBaseTokenL1Address } from './getBaseTokenL1Address.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const address = await getBaseTokenL1Address(client) + expect(address).to.equal(mockBaseTokenL1Address) +}) diff --git a/src/zksync/actions/getBaseTokenL1Address.ts b/src/zksync/actions/getBaseTokenL1Address.ts new file mode 100644 index 0000000000..d82299775b --- /dev/null +++ b/src/zksync/actions/getBaseTokenL1Address.ts @@ -0,0 +1,18 @@ +import type { Address } from 'abitype' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetBaseTokenL1AddressReturnType = Address + +export async function getBaseTokenL1Address< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const result = await client.request({ method: 'zks_getBaseTokenL1Address' }) + return result +} diff --git a/src/zksync/actions/getBlockDetails.test.ts b/src/zksync/actions/getBlockDetails.test.ts new file mode 100644 index 0000000000..408f0c9623 --- /dev/null +++ b/src/zksync/actions/getBlockDetails.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getBlockDetails } from './getBlockDetails.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const details = await getBlockDetails(client, { number: 0 }) + expect(details).toMatchInlineSnapshot(` + { + "baseSystemContractsHashes": { + "bootloader": "0x010008bb22aea1e22373cb8d807b15c67eedd65523e9cba4cc556adfa504f7b8", + "default_aa": "0x010008bb22aea1e22373cb8d807b15c67eedd65523e9cba4cc556adfa504f7b8", + }, + "l1BatchNumber": 0, + "l1TxCount": 2, + "l2TxCount": 3, + "number": 0, + "operatorAddress": "0xde03a0b5963f75f1c8485b355ff6d30f3093bde7", + "protocolVersion": "Version19", + "status": "verified", + "timestamp": 1713435780, + } + `) +}) diff --git a/src/zksync/actions/getBlockDetails.ts b/src/zksync/actions/getBlockDetails.ts new file mode 100644 index 0000000000..687d5629fc --- /dev/null +++ b/src/zksync/actions/getBlockDetails.ts @@ -0,0 +1,27 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { + ZkSyncBlockDetails, + ZkSyncNumberParameter, +} from '../types/block.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetBlockDetailsParameters = ZkSyncNumberParameter + +export type GetBlockDetailsReturnType = ZkSyncBlockDetails + +export async function getBlockDetails< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetBlockDetailsParameters, +): Promise { + const result = await client.request({ + method: 'zks_getBlockDetails', + params: [parameters.number], + }) + return result +} diff --git a/src/zksync/actions/getBridgehubContractAddress.test.ts b/src/zksync/actions/getBridgehubContractAddress.test.ts new file mode 100644 index 0000000000..ab2e5c4a70 --- /dev/null +++ b/src/zksync/actions/getBridgehubContractAddress.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getBridgehubContractAddress } from './getBridgehubContractAddress.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const bridgeHubContractAddress = await getBridgehubContractAddress(client) + expect(bridgeHubContractAddress).toMatchInlineSnapshot( + `"0x173999892363ba18c9dc60f8c57152fc914bce89"`, + ) +}) diff --git a/src/zksync/actions/getBridgehubContractAddress.ts b/src/zksync/actions/getBridgehubContractAddress.ts new file mode 100644 index 0000000000..38ed2252f6 --- /dev/null +++ b/src/zksync/actions/getBridgehubContractAddress.ts @@ -0,0 +1,18 @@ +import type { Address } from 'abitype' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetBridgehubContractAddressReturnType = Address + +export async function getBridgehubContractAddress< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const result = await client.request({ method: 'zks_getBridgehubContract' }) + return result +} diff --git a/src/zksync/actions/getDefaultBridgeAddresses.test.ts b/src/zksync/actions/getDefaultBridgeAddresses.test.ts new file mode 100644 index 0000000000..6e9895a925 --- /dev/null +++ b/src/zksync/actions/getDefaultBridgeAddresses.test.ts @@ -0,0 +1,21 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getDefaultBridgeAddresses } from './getDefaultBridgeAddresses.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const addresses = await getDefaultBridgeAddresses(client) + expect(addresses).toMatchInlineSnapshot(` + { + "erc20L1": "0xbe270c78209cfda84310230aaa82e18936310b2e", + "sharedL1": "0x648afeaf09a3db988ac41b786001235bbdbc7640", + "sharedL2": "0xfd61c893b903fa133908ce83dfef67c4c2350dd8", + } + `) +}) diff --git a/src/zksync/actions/getDefaultBridgeAddresses.ts b/src/zksync/actions/getDefaultBridgeAddresses.ts new file mode 100644 index 0000000000..3af1e05f40 --- /dev/null +++ b/src/zksync/actions/getDefaultBridgeAddresses.ts @@ -0,0 +1,22 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { BridgeContractAddresses } from '../types/contract.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetDefaultBridgeAddressesReturnType = BridgeContractAddresses + +export async function getDefaultBridgeAddresses< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const addresses = await client.request({ method: 'zks_getBridgeContracts' }) + return { + erc20L1: addresses.l1Erc20DefaultBridge, + sharedL1: addresses.l1SharedDefaultBridge, + sharedL2: addresses.l2SharedDefaultBridge, + } +} diff --git a/src/zksync/actions/getL1BatchBlockRange.test.ts b/src/zksync/actions/getL1BatchBlockRange.test.ts new file mode 100644 index 0000000000..6bbda532a6 --- /dev/null +++ b/src/zksync/actions/getL1BatchBlockRange.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getL1BatchBlockRange } from './getL1BatchBlockRange.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const blockRange = await getL1BatchBlockRange(client, { l1BatchNumber: 0 }) + expect(blockRange).toBeDefined() + expect(blockRange.length === 2) + expect(blockRange[0]).to.equal(0) + expect(blockRange[1]).to.equal(5) +}) diff --git a/src/zksync/actions/getL1BatchBlockRange.ts b/src/zksync/actions/getL1BatchBlockRange.ts new file mode 100644 index 0000000000..264d1fd0c5 --- /dev/null +++ b/src/zksync/actions/getL1BatchBlockRange.ts @@ -0,0 +1,26 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import { hexToNumber } from '../../utils/encoding/fromHex.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetL1BatchBlockRangeParameters = { + l1BatchNumber: number +} + +export type GetL1BatchBlockRangeReturnParameters = [number, number] + +export async function getL1BatchBlockRange< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetL1BatchBlockRangeParameters, +): Promise { + const [number_1, number_2] = await client.request({ + method: 'zks_getL1BatchBlockRange', + params: [parameters.l1BatchNumber], + }) + return [hexToNumber(number_1), hexToNumber(number_2)] +} diff --git a/src/zksync/actions/getL1BatchDetails.test.ts b/src/zksync/actions/getL1BatchDetails.test.ts new file mode 100644 index 0000000000..34bb05d382 --- /dev/null +++ b/src/zksync/actions/getL1BatchDetails.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + mockDetails, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' + +import { getL1BatchDetails } from './getL1BatchDetails.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const details = await getL1BatchDetails(client, { number: 0 }) + expect(details).toBeDefined() + expect(details).to.equal(mockDetails) +}) diff --git a/src/zksync/actions/getL1BatchDetails.ts b/src/zksync/actions/getL1BatchDetails.ts new file mode 100644 index 0000000000..edf187e19c --- /dev/null +++ b/src/zksync/actions/getL1BatchDetails.ts @@ -0,0 +1,27 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { + ZkSyncBatchDetails, + ZkSyncNumberParameter, +} from '../types/block.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetL1BatchDetailsParameters = ZkSyncNumberParameter + +export type GetL1BatchDetailsReturnType = ZkSyncBatchDetails + +export async function getL1BatchDetails< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetL1BatchDetailsParameters, +): Promise { + const result = await client.request({ + method: 'zks_getL1BatchDetails', + params: [parameters.number], + }) + return result +} diff --git a/src/zksync/actions/getL1BatchNumber.test.ts b/src/zksync/actions/getL1BatchNumber.test.ts new file mode 100644 index 0000000000..fecea6d181 --- /dev/null +++ b/src/zksync/actions/getL1BatchNumber.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + mockedL1BatchNumber, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getL1BatchNumber } from './getL1BatchNumber.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const hex = await getL1BatchNumber(client) + expect(hex).to.be.equal(mockedL1BatchNumber) +}) diff --git a/src/zksync/actions/getL1BatchNumber.ts b/src/zksync/actions/getL1BatchNumber.ts new file mode 100644 index 0000000000..a0fd98cc01 --- /dev/null +++ b/src/zksync/actions/getL1BatchNumber.ts @@ -0,0 +1,18 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { Hex } from '../../types/misc.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetL1BatchNumberReturnType = Hex + +export async function getL1BatchNumber< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const result = await client.request({ method: 'zks_L1BatchNumber' }) + return result +} diff --git a/src/zksync/actions/getL1ChainId.test.ts b/src/zksync/actions/getL1ChainId.test.ts new file mode 100644 index 0000000000..04cb45c040 --- /dev/null +++ b/src/zksync/actions/getL1ChainId.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from 'vitest' +import { + mockChainId, + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getL1ChainId } from './getL1ChainId.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const chainId = await getL1ChainId(client) + expect(chainId).to.equal(mockChainId) +}) diff --git a/src/zksync/actions/getL1ChainId.ts b/src/zksync/actions/getL1ChainId.ts new file mode 100644 index 0000000000..8b2bc91974 --- /dev/null +++ b/src/zksync/actions/getL1ChainId.ts @@ -0,0 +1,18 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { Hex } from '../../types/misc.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetL1ChainIdReturnType = Hex + +export async function getL1ChainId< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const result = await client.request({ method: 'zks_L1ChainId' }) + return result +} diff --git a/src/zksync/actions/getLogProof.test.ts b/src/zksync/actions/getLogProof.test.ts new file mode 100644 index 0000000000..8069ae8b16 --- /dev/null +++ b/src/zksync/actions/getLogProof.test.ts @@ -0,0 +1,65 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getLogProof } from './getLogProof.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const proof = await getLogProof(client, { + txHash: + '0x1f698500b32c325f46f008eda30df9057d54619f0d92b703952c333847a21ded', + }) + + expect(proof).toMatchInlineSnapshot(` + { + "id": 112, + "proof": [ + "0x3d999d6a5bacdc5c8c01ad0917c1dca03c632fc486ac623a8857804374b0d1b1", + "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", + "0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111", + "0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa", + "0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89", + "0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272", + "0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d", + "0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c", + "0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db", + "0x124b05ec272cecd7538fdafe53b6628d31188ffb6f345139aac3c3c1fd2e470f", + "0xc3be9cbd19304d84cca3d045e06b8db3acd68c304fc9cd4cbffe6d18036cb13f", + ], + "root": "0x443ddd5b010069db588a5f21e9145f94a93dd8109c72cc70d79281f1c19db2c8", + } + `) +}) + +test('args: index', async () => { + const proof = await getLogProof(client, { + txHash: + '0x1f698500b32c325f46f008eda30df9057d54619f0d92b703952c333847a21ded', + index: 5, + }) + + expect(proof).toMatchInlineSnapshot(` + { + "id": 112, + "proof": [ + "0x3d999d6a5bacdc5c8c01ad0917c1dca03c632fc486ac623a8857804374b0d1b1", + "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", + "0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111", + "0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa", + "0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89", + "0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272", + "0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d", + "0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c", + "0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db", + "0x124b05ec272cecd7538fdafe53b6628d31188ffb6f345139aac3c3c1fd2e470f", + "0xc3be9cbd19304d84cca3d045e06b8db3acd68c304fc9cd4cbffe6d18036cb13f", + ], + "root": "0x443ddd5b010069db588a5f21e9145f94a93dd8109c72cc70d79281f1c19db2c8", + } + `) +}) diff --git a/src/zksync/actions/getLogProof.ts b/src/zksync/actions/getLogProof.ts new file mode 100644 index 0000000000..fcfd0de8f9 --- /dev/null +++ b/src/zksync/actions/getLogProof.ts @@ -0,0 +1,28 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { Hash } from '../../types/misc.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' +import type { MessageProof } from '../types/proof.js' + +export type GetLogProofParameters = { + txHash: Hash + index?: number | undefined +} + +export type GetLogProofReturnType = MessageProof | null + +export async function getLogProof< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetLogProofParameters, +): Promise { + const result = await client.request({ + method: 'zks_getL2ToL1LogProof', + params: [parameters.txHash, parameters.index], + }) + return result +} diff --git a/src/zksync/actions/getMainContractAddress.test.ts b/src/zksync/actions/getMainContractAddress.test.ts new file mode 100644 index 0000000000..788c06020d --- /dev/null +++ b/src/zksync/actions/getMainContractAddress.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getMainContractAddress } from './getMainContractAddress.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const address = await getMainContractAddress(client) + expect(address).toMatchInlineSnapshot( + `"0x9fab5aec650f1ce6e35ec60a611af0a1345927c8"`, + ) +}) diff --git a/src/zksync/actions/getMainContractAddress.ts b/src/zksync/actions/getMainContractAddress.ts new file mode 100644 index 0000000000..1aabd037ce --- /dev/null +++ b/src/zksync/actions/getMainContractAddress.ts @@ -0,0 +1,18 @@ +import type { Address } from 'abitype' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetMainContractAddressReturnType = Address + +export async function getMainContractAddress< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const address = await client.request({ method: 'zks_getMainContract' }) + return address +} diff --git a/src/zksync/actions/getRawBlockTransaction.test.ts b/src/zksync/actions/getRawBlockTransaction.test.ts new file mode 100644 index 0000000000..925e5f3977 --- /dev/null +++ b/src/zksync/actions/getRawBlockTransaction.test.ts @@ -0,0 +1,70 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getRawBlockTransactions } from './getRawBlockTransaction.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const transactions = await getRawBlockTransactions(client, { + number: 1, + }) + + expect(transactions).toMatchInlineSnapshot(` + [ + { + "commonData": { + "L1": { + "canonicalTxHash": "0x9376f805ccd40186a73672a4d0db064060956e70c4ae486ab205291986439343", + "deadlineBlock": 0, + "ethBlock": 125, + "ethHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "fullFee": "0x0", + "gasLimit": "0x44aa200", + "gasPerPubdataLimit": "0x320", + "layer2TipFee": "0x0", + "maxFeePerGas": "0x1dcd6500", + "opProcessingType": "Common", + "priorityQueueType": "Deque", + "refundRecipient": "0xde03a0b5963f75f1c8485b355ff6d30f3093bde7", + "sender": "0xde03a0b5963f75f1c8485b355ff6d30f3093bde7", + "serialId": 0, + "toMint": "0x7fe5cf2bea0000", + }, + "L2": { + "fee": { + "gasLimit": "0x2803d", + "gasPerPubdataLimit": "0x42", + "maxFeePerGas": "0xee6b280", + "maxPriorityFeePerGas": "0x0", + }, + "initiatorAddress": "0x000000000000000000000000000000000000800b", + "input": { + "data": {}, + "hash": "0x", + }, + "nonce": 0, + "paymasterParams": { + "paymaster": "0x0a67078A35745947A37A552174aFe724D8180c25", + "paymasterInput": {}, + }, + "signature": {}, + "transactionType": "ProtocolUpgrade", + }, + }, + "execute": { + "calldata": "0xef0e2ff4000000000000000000000000000000000000000000000000000000000000010e", + "contractAddress": "0x000000000000000000000000000000000000800b", + "factoryDeps": "0x", + "value": 0n, + }, + "rawBytes": "", + "receivedTimestampMs": 1713436617435, + }, + ] + `) +}) diff --git a/src/zksync/actions/getRawBlockTransaction.ts b/src/zksync/actions/getRawBlockTransaction.ts new file mode 100644 index 0000000000..94287e2c50 --- /dev/null +++ b/src/zksync/actions/getRawBlockTransaction.ts @@ -0,0 +1,26 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { ZkSyncNumberParameter } from '../types/block.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' +import type { ZkSyncRawBlockTransactions } from '../types/transaction.js' +import { camelCaseKeys } from '../utils/camelCaseKeys.js' + +export type GetRawBlockTransactionParameters = ZkSyncNumberParameter + +export type GetRawBlockTransactionReturnType = ZkSyncRawBlockTransactions + +export async function getRawBlockTransactions< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetRawBlockTransactionParameters, +): Promise { + const result = await client.request({ + method: 'zks_getRawBlockTransactions', + params: [parameters.number], + }) + return camelCaseKeys(result) as GetRawBlockTransactionReturnType +} diff --git a/src/zksync/actions/getTestnetPaymasterAddress.test.ts b/src/zksync/actions/getTestnetPaymasterAddress.test.ts new file mode 100644 index 0000000000..c1b4da9e0a --- /dev/null +++ b/src/zksync/actions/getTestnetPaymasterAddress.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from 'vitest' +import { + mockAddress, + mockClientPublicActionsL2, + mockTestnetPaymasterAddress, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import type { EIP1193RequestFn } from '../../types/eip1193.js' +import { getTestnetPaymasterAddress } from './getTestnetPaymasterAddress.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const address = await getTestnetPaymasterAddress(client) + expect(address).to.equal(mockTestnetPaymasterAddress) +}) + +test('returns null as address', async () => { + client.request = (async ({ method }) => { + if (method === 'zks_getTestnetPaymaster') return null + return mockAddress + }) as EIP1193RequestFn + + const address = await getTestnetPaymasterAddress(client) + expect(address).to.equal(null) +}) diff --git a/src/zksync/actions/getTestnetPaymasterAddress.ts b/src/zksync/actions/getTestnetPaymasterAddress.ts new file mode 100644 index 0000000000..617ed416ee --- /dev/null +++ b/src/zksync/actions/getTestnetPaymasterAddress.ts @@ -0,0 +1,18 @@ +import type { Address } from 'abitype' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' + +export type GetTestnetPaymasterAddressReturnType = Address | null + +export async function getTestnetPaymasterAddress< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, +): Promise { + const result = await client.request({ method: 'zks_getTestnetPaymaster' }) + return result +} diff --git a/src/zksync/actions/getTransactionDetails.test.ts b/src/zksync/actions/getTransactionDetails.test.ts new file mode 100644 index 0000000000..1b5c359acf --- /dev/null +++ b/src/zksync/actions/getTransactionDetails.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest' +import { + mockClientPublicActionsL2, + zkSyncClientLocalNode, +} from '../../../test/src/zksync.js' +import { getTransactionDetails } from './getTransactionDetails.js' + +const client = { ...zkSyncClientLocalNode } + +mockClientPublicActionsL2(client) + +test('default', async () => { + const details = await getTransactionDetails(client, { + txHash: + '0xcf89f4076eae08127daa1b2b9bab94a910d232b4a78b116554f7b29af19e35a4', + }) + expect(details).toMatchInlineSnapshot(` + { + "fee": 10n, + "gasPerPubdata": 50000n, + "initiatorAddress": "0x000000000000000000000000000000000000800b", + "isL1Originated": true, + "receivedAt": 2024-04-18T10:36:57.435Z, + "status": "validated", + } + `) +}) diff --git a/src/zksync/actions/getTransactionDetails.ts b/src/zksync/actions/getTransactionDetails.ts new file mode 100644 index 0000000000..b9f382c98e --- /dev/null +++ b/src/zksync/actions/getTransactionDetails.ts @@ -0,0 +1,27 @@ +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { Hash } from '../../types/misc.js' +import type { PublicZkSyncRpcSchema } from '../types/eip1193.js' +import type { ZkSyncTransactionDetails } from '../types/transaction.js' + +export type GetTransactionDetailsParameters = { + txHash: Hash +} + +export type GetTransactionDetailsReturnType = ZkSyncTransactionDetails + +export async function getTransactionDetails< + TChain extends Chain | undefined, + TAccount extends Account | undefined, +>( + client: Client, + parameters: GetTransactionDetailsParameters, +): Promise { + const result = await client.request({ + method: 'zks_getTransactionDetails', + params: [parameters.txHash], + }) + return result +} diff --git a/src/zksync/decorators/publicL2.test.ts b/src/zksync/decorators/publicL2.test.ts new file mode 100644 index 0000000000..aa93d4a12f --- /dev/null +++ b/src/zksync/decorators/publicL2.test.ts @@ -0,0 +1,210 @@ +import { expect, test } from 'vitest' + +import type { Address } from 'abitype' +import { zkSyncLocalNode } from '../../../src/chains/index.js' +import { getZksyncMockProvider } from '../../../test/src/zksync.js' +import { + mockAccountBalances, + mockAddress, + mockBaseTokenL1Address, + mockDetails, + mockMainContractAddress, + mockProofValues, + mockRequestReturnData, + mockTestnetPaymasterAddress, + mockTransactionDetails, + mockedGasEstimation, + mockedL1BatchNumber, +} from '../../../test/src/zksync.js' +import { createPublicClient } from '../../clients/createPublicClient.js' +import { custom } from '../../clients/transports/custom.js' +import { estimateFee } from '../actions/estimateFee.js' +import { estimateGasL1ToL2 } from '../actions/estimateGasL1ToL2.js' +import type { GetAllBalancesReturnType } from '../actions/getAllBalances.js' +import { getLogProof } from '../actions/getLogProof.js' +import { getTransactionDetails } from '../actions/getTransactionDetails.js' +import { publicActionsL2 } from './publicL2.js' + +const mockedZksyncClient = createPublicClient({ + transport: custom( + getZksyncMockProvider(async ({ method }) => mockRequestReturnData(method)), + ), + chain: zkSyncLocalNode, +}).extend(publicActionsL2()) + +test('getL1ChainId', async () => { + const chainId = await mockedZksyncClient.getL1ChainId() + expect(chainId).to.be.equal('0x9') +}) + +test('getDefaultBridgeAddresses', async () => { + const addresses = await mockedZksyncClient.getDefaultBridgeAddresses() + + const returnedAddresses = { + erc20L1: '0xbe270c78209cfda84310230aaa82e18936310b2e', + sharedL1: '0x648afeaf09a3db988ac41b786001235bbdbc7640', + sharedL2: '0xfd61c893b903fa133908ce83dfef67c4c2350dd8', + } + + expect(addresses).to.deep.equal(returnedAddresses) +}) + +test('getTestnetPaymasterAddress', async () => { + const address = await mockedZksyncClient.getTestnetPaymasterAddress() + expect(address).to.equal(mockTestnetPaymasterAddress) +}) + +test('getMainContractAddress', async () => { + const mainContractAddress = await mockedZksyncClient.getMainContractAddress() + expect(mainContractAddress).to.equal(mockMainContractAddress) +}) + +test('getAllBalances', async () => { + const balances = await mockedZksyncClient.getAllBalances({ + account: mockAddress, + }) + + const mockAccountBalancesBigInt: GetAllBalancesReturnType = {} + const entries = Object.entries(mockAccountBalances) + for (const [key, value] of entries) { + mockAccountBalancesBigInt[key as Address] = BigInt(value) + } + + expect(balances).to.deep.equal(mockAccountBalancesBigInt) +}) + +test('getRawBlockTransaction', async () => { + const result = await mockedZksyncClient.getRawBlockTransaction({ + number: 1, + }) + expect(result).toMatchInlineSnapshot(` + [ + { + "commonData": { + "L1": { + "canonicalTxHash": "0x9376f805ccd40186a73672a4d0db064060956e70c4ae486ab205291986439343", + "deadlineBlock": 0, + "ethBlock": 125, + "ethHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "fullFee": "0x0", + "gasLimit": "0x44aa200", + "gasPerPubdataLimit": "0x320", + "layer2TipFee": "0x0", + "maxFeePerGas": "0x1dcd6500", + "opProcessingType": "Common", + "priorityQueueType": "Deque", + "refundRecipient": "0xde03a0b5963f75f1c8485b355ff6d30f3093bde7", + "sender": "0xde03a0b5963f75f1c8485b355ff6d30f3093bde7", + "serialId": 0, + "toMint": "0x7fe5cf2bea0000", + }, + "L2": { + "fee": { + "gasLimit": "0x2803d", + "gasPerPubdataLimit": "0x42", + "maxFeePerGas": "0xee6b280", + "maxPriorityFeePerGas": "0x0", + }, + "initiatorAddress": "0x000000000000000000000000000000000000800b", + "input": { + "data": {}, + "hash": "0x", + }, + "nonce": 0, + "paymasterParams": { + "paymaster": "0x0a67078A35745947A37A552174aFe724D8180c25", + "paymasterInput": {}, + }, + "signature": {}, + "transactionType": "ProtocolUpgrade", + }, + }, + "execute": { + "calldata": "0xef0e2ff4000000000000000000000000000000000000000000000000000000000000010e", + "contractAddress": "0x000000000000000000000000000000000000800b", + "factoryDeps": "0x", + "value": 0n, + }, + "rawBytes": "", + "receivedTimestampMs": 1713436617435, + }, + ] + `) +}) + +test('getL1BatchDetails', async () => { + const details = await mockedZksyncClient.getL1BatchDetails({ + number: 0, + }) + expect(details).to.equal(mockDetails) +}) + +test('getL1BatchBlockRange', async () => { + const blockRange = await mockedZksyncClient.getL1BatchBlockRange({ + l1BatchNumber: 0, + }) + expect(blockRange).toMatchInlineSnapshot(` + [ + 0, + 5, + ] + `) +}) + +test('getL1BatchNumber', async () => { + const number = await mockedZksyncClient.getL1BatchNumber() + expect(number).to.be.equal(mockedL1BatchNumber) +}) + +test('getBridgehubContract', async () => { + const bridgeHubContractAddress = + await mockedZksyncClient.getBridgehubContractAddress() + expect(bridgeHubContractAddress).to.equal(mockAddress) +}) + +test('getBaseTokenL1Address', async () => { + const address = await mockedZksyncClient.getBaseTokenL1Address() + expect(address).to.equal(mockBaseTokenL1Address) +}) + +test('estimateFee', async () => { + const fee = await estimateFee(mockedZksyncClient, { + account: mockAddress, + }) + + expect(fee).toMatchInlineSnapshot(` + { + "gasLimit": 163901n, + "gasPerPubdataLimit": 66n, + "maxFeePerGas": 250000000n, + "maxPriorityFeePerGas": 0n, + } + `) +}) + +test('estimateGasL1ToL2', async () => { + const gas = await estimateGasL1ToL2(mockedZksyncClient, { + account: mockAddress, + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 70n, + }) + + expect(gas).to.deep.equal(mockedGasEstimation) +}) + +test('getTransactionDetails', async () => { + const details = await getTransactionDetails(mockedZksyncClient, { + txHash: + '0xcf89f4076eae08127daa1b2b9bab94a910d232b4a78b116554f7b29af19e35a4', + }) + expect(details).to.equal(mockTransactionDetails) +}) + +test('getLogProof', async () => { + const fee = await getLogProof(mockedZksyncClient, { + txHash: '0x', + index: 5, + }) + + expect(fee).to.deep.equal(mockProofValues) +}) diff --git a/src/zksync/decorators/publicL2.ts b/src/zksync/decorators/publicL2.ts new file mode 100644 index 0000000000..3ba9fd446f --- /dev/null +++ b/src/zksync/decorators/publicL2.ts @@ -0,0 +1,425 @@ +import type { Address } from 'abitype' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Hex } from '../../types/misc.js' +import { estimateFee } from '../actions/estimateFee.js' +import type { + EstimateFeeParameters, + EstimateFeeReturnType, +} from '../actions/estimateFee.js' +import { + type EstimateGasL1ToL2Parameters, + estimateGasL1ToL2, +} from '../actions/estimateGasL1ToL2.js' +import { + type GetAllBalancesParameters, + type GetAllBalancesReturnType, + getAllBalances, +} from '../actions/getAllBalances.js' +import { getBaseTokenL1Address } from '../actions/getBaseTokenL1Address.js' +import { + type GetBlockDetailsParameters, + type GetBlockDetailsReturnType, + getBlockDetails, +} from '../actions/getBlockDetails.js' +import { getBridgehubContractAddress } from '../actions/getBridgehubContractAddress.js' +import { + type GetDefaultBridgeAddressesReturnType, + getDefaultBridgeAddresses, +} from '../actions/getDefaultBridgeAddresses.js' +import { + type GetL1BatchBlockRangeParameters, + type GetL1BatchBlockRangeReturnParameters, + getL1BatchBlockRange, +} from '../actions/getL1BatchBlockRange.js' +import { + type GetL1BatchDetailsParameters, + type GetL1BatchDetailsReturnType, + getL1BatchDetails, +} from '../actions/getL1BatchDetails.js' +import { getL1BatchNumber } from '../actions/getL1BatchNumber.js' +import { getL1ChainId } from '../actions/getL1ChainId.js' +import { + type GetLogProofParameters, + type GetLogProofReturnType, + getLogProof, +} from '../actions/getLogProof.js' +import { getMainContractAddress } from '../actions/getMainContractAddress.js' +import { + type GetRawBlockTransactionParameters, + type GetRawBlockTransactionReturnType, + getRawBlockTransactions, +} from '../actions/getRawBlockTransaction.js' +import { getTestnetPaymasterAddress } from '../actions/getTestnetPaymasterAddress.js' +import { + type GetTransactionDetailsParameters, + type GetTransactionDetailsReturnType, + getTransactionDetails, +} from '../actions/getTransactionDetails.js' +import type { ChainEIP712 } from '../types/chain.js' + +export type PublicActionsL2< + TChain extends ChainEIP712 | undefined = ChainEIP712 | undefined, + TAccount extends Account | undefined = Account | undefined, +> = { + /** + * Returns the addresses of the default zkSync Era bridge contracts on both L1 and L2. + * + * @returns The Addresses of the default zkSync Era bridge contracts on both L1 and L2. {@link DefaultBridgeAddressesReturnType} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const addresses = await client.getDefaultBridgeAddresses(); + */ + getDefaultBridgeAddresses: () => Promise + + /** + * Returns Testnet Paymaster Address. + * + * @returns The Address. {@link Address} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await client.getTestnetPaymasterAddress(); + */ + getTestnetPaymasterAddress: () => Promise
+ + /** + * Returns the Chain Id of underlying L1 network. + * + * @returns number + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const chainId = await client.getL1ChainId(); + */ + + getL1ChainId: () => Promise + + /** + * Returns the address of a Main zkSync Contract. + * + * @returns The Address. {@link Address} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await client.getMainContractAddress(); + */ + getMainContractAddress: () => Promise
+ + /** + * Returns all known balances for a given account. + * + * @returns The balances for a given account. {@link GetAllBalancesReturnType} + * @param args - {@link GetAllBalancesParameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const balances = await client.getAllBalances({account: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"}); + */ + getAllBalances: ( + args: GetAllBalancesParameters, + ) => Promise + + /** + * Returns data of transactions in a block. + * + * @returns data of transactions {@link RawBlockTransactions} + * @param args - {@link GetRawBlockTransactionParameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const rawTx = await client.getRawBlockTransaction({number: 1}); + */ + getRawBlockTransaction: ( + args: GetRawBlockTransactionParameters, + ) => Promise + + /** + * Returns additional zkSync-specific information about the L2 block. + * + * @returns zkSync-specific information about the L2 block {@link BaseBlockDetails} + * @param args - {@link GetBlockDetailsParameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const blockDetails = await client.getBlockDetails({number: 1}); + */ + getBlockDetails: ( + args: GetBlockDetailsParameters, + ) => Promise + + /** + * Returns data pertaining to a given batch. + * + * @returns data pertaining to a given batch {@link BatchDetails} + * @param args - {@link GetL1BatchDetailsParameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const batchDetails = await client.getL1BatchDetails({number: 1}); + */ + getL1BatchDetails: ( + args: GetL1BatchDetailsParameters, + ) => Promise + + /** + * Returns the range of blocks contained within a batch given by batch number. + * + * @returns range of blocks contained withing a batch {@link GetL1BatchBlockRangeReturnParameters} + * @param args - {@link GetL1BatchBlockRangeParameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const batchBlockRange = await client.getL1BatchBlockRange({number: 1}); + */ + getL1BatchBlockRange: ( + args: GetL1BatchBlockRangeParameters, + ) => Promise + + /** + * Returns the latest L1 batch number + * + * @returns latest L1 batch number + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const latestNumber = await client.getL1BatchNumber({number: 1}); + */ + getL1BatchNumber: () => Promise + + /** + * Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log. + * + * @returns the proof for the corresponding L2 to L1 log. + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const proof = await client.getLogProof({txHash: "0x...",index:1}); + */ + getLogProof: (args: GetLogProofParameters) => Promise + + /** + * Returns data from a specific transaction given by the transaction hash + * + * @returns data from a specific transaction given by the transaction hash + * + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const details = await client.getTransactionDetails({txHash: "0x..."}); + */ + getTransactionDetails: ( + args: GetTransactionDetailsParameters, + ) => Promise + + /** + * Returns an estimated Fee for requested transaction. + * + * @returns an estimated {@link Fee} for requested transaction. + * @param args - {@link EstimateFeeParameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const details = await client.estimateFee({transactionRequest: {...}}}); + */ + estimateFee: ( + args: EstimateFeeParameters, + ) => Promise + + /** + * Returns an estimated gas for L1 to L2 execution. + * + * @returns an estimated gas. + * @param args - {@link EstimateGasL1ToL2Parameters} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const details = await client.estimateGasL1ToL2({transactionRequest: {...}}}); + */ + estimateGasL1ToL2: ( + args: EstimateGasL1ToL2Parameters, + ) => Promise + + /** + * Returns the Bridgehub smart contract address. + * + * @returns address of the Bridgehub smart contract address. + * + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await client.getBridgehubContractAddress(); + */ + getBridgehubContractAddress: () => Promise
+ + /** + * Returns the address of the base L1 token. + * + * @returns address of the base L1 token. + * + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zkSyncLocalNode } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zkSyncLocalNode, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await client.getBaseTokenL1Address(); + */ + getBaseTokenL1Address: () => Promise
+} + +export function publicActionsL2() { + return < + TTransport extends Transport = Transport, + TChain extends ChainEIP712 | undefined = ChainEIP712 | undefined, + TAccount extends Account | undefined = Account | undefined, + >( + client: Client, + ): PublicActionsL2 => { + return { + estimateGasL1ToL2: (args) => estimateGasL1ToL2(client, args), + getDefaultBridgeAddresses: () => getDefaultBridgeAddresses(client), + getTestnetPaymasterAddress: () => getTestnetPaymasterAddress(client), + getL1ChainId: () => getL1ChainId(client), + getMainContractAddress: () => getMainContractAddress(client), + getAllBalances: (args) => getAllBalances(client, args), + getRawBlockTransaction: (args) => getRawBlockTransactions(client, args), + getBlockDetails: (args) => getBlockDetails(client, args), + getL1BatchDetails: (args) => getL1BatchDetails(client, args), + getL1BatchBlockRange: (args) => getL1BatchBlockRange(client, args), + getL1BatchNumber: () => getL1BatchNumber(client), + getLogProof: (args) => getLogProof(client, args), + getTransactionDetails: (args) => getTransactionDetails(client, args), + estimateFee: (args) => estimateFee(client, args), + getBridgehubContractAddress: () => getBridgehubContractAddress(client), + getBaseTokenL1Address: () => getBaseTokenL1Address(client), + } + } +} diff --git a/src/zksync/index.ts b/src/zksync/index.ts index 1cba0215c9..01e4f07af6 100644 --- a/src/zksync/index.ts +++ b/src/zksync/index.ts @@ -1,3 +1,58 @@ +export { + type DeployContractErrorType, + type DeployContractParameters, + type DeployContractReturnType, + deployContract, +} from './actions/deployContract.js' +export { + type EstimateFeeParameters, + type EstimateFeeReturnType, + estimateFee, +} from './actions/estimateFee.js' +export { + type GetAllBalancesParameters, + type GetAllBalancesReturnType, + getAllBalances, +} from './actions/getAllBalances.js' +export { + type GetBlockDetailsParameters, + type GetBlockDetailsReturnType, + getBlockDetails, +} from './actions/getBlockDetails.js' +export { + type GetDefaultBridgeAddressesReturnType, + getDefaultBridgeAddresses, +} from './actions/getDefaultBridgeAddresses.js' +export { getBridgehubContractAddress } from './actions/getBridgehubContractAddress.js' +export { + type GetL1BatchBlockRangeParameters, + type GetL1BatchBlockRangeReturnParameters, + getL1BatchBlockRange, +} from './actions/getL1BatchBlockRange.js' +export { + type GetL1BatchDetailsParameters, + type GetL1BatchDetailsReturnType, + getL1BatchDetails, +} from './actions/getL1BatchDetails.js' +export { getL1BatchNumber } from './actions/getL1BatchNumber.js' +export { getL1ChainId } from './actions/getL1ChainId.js' +export { + type GetLogProofReturnType, + type GetLogProofParameters, + getLogProof, +} from './actions/getLogProof.js' +export { getMainContractAddress } from './actions/getMainContractAddress.js' +export { + type GetRawBlockTransactionParameters, + type GetRawBlockTransactionReturnType, + getRawBlockTransactions, +} from './actions/getRawBlockTransaction.js' +export { getTestnetPaymasterAddress } from './actions/getTestnetPaymasterAddress.js' +export { + type GetTransactionDetailsParameters, + type GetTransactionDetailsReturnType, + getTransactionDetails, +} from './actions/getTransactionDetails.js' export { type SendTransactionErrorType, type SendTransactionParameters, @@ -22,12 +77,6 @@ export { type SignTransactionReturnType, signTransaction, } from './actions/signTransaction.js' -export { - type DeployContractErrorType, - type DeployContractParameters, - type DeployContractReturnType, - deployContract, -} from './actions/deployContract.js' export { zkSync, @@ -44,6 +93,11 @@ export { export { serializeTransaction } from './serializers.js' +export { + publicActionsL2, + type PublicActionsL2, +} from './decorators/publicL2.js' + export type { ZkSyncBlock, ZkSyncBlockOverrides, @@ -66,6 +120,7 @@ export type { export type { TransactionRequestEIP712, ZkSyncEIP712TransactionSignable, + ZkSyncRawBlockTransactions, ZkSyncRpcTransaction, ZkSyncRpcTransactionEIP712, ZkSyncRpcTransactionPriority, @@ -83,6 +138,7 @@ export type { ZkSyncTransactionSerialized, ZkSyncTransactionSerializedEIP712, ZkSyncTransactionType, + ZkSyncTransactionDetails, } from './types/transaction.js' export { diff --git a/src/zksync/types/block.ts b/src/zksync/types/block.ts index 868860c45a..7f6f4baf51 100644 --- a/src/zksync/types/block.ts +++ b/src/zksync/types/block.ts @@ -1,8 +1,17 @@ +import type { Address } from 'abitype' import type { Block, BlockTag } from '../../types/block.js' -import type { Hex } from '../../types/misc.js' +import type { Hash, Hex } from '../../types/misc.js' import type { RpcBlock } from '../../types/rpc.js' import type { ZkSyncRpcTransaction, ZkSyncTransaction } from './transaction.js' +export type ZkSyncBatchDetails = Omit< + ZkSyncBlockDetails, + 'operatorAddress' | 'protocolVersion' +> & { + l1GasPrice: number + l2FairGasPrice: number +} + export type ZkSyncBlockOverrides = { l1BatchNumber: bigint | null l1BatchTimestamp: bigint | null @@ -19,10 +28,33 @@ export type ZkSyncBlock< > & ZkSyncBlockOverrides +export type ZkSyncBlockDetails = { + number: number + timestamp: number + l1BatchNumber: number + l1TxCount: number + l2TxCount: number + rootHash?: Hash + status: string + commitTxHash?: Hash + committedAt?: Date + proveTxHash?: Hash + provenAt?: Date + executeTxHash?: Hash + executedAt?: Date + baseSystemContractsHashes: { + bootloader: Hash + default_aa: Hash + } + operatorAddress: Address + protocolVersion?: string +} + export type ZkSyncRpcBlockOverrides = { l1BatchNumber: Hex | null l1BatchTimestamp: Hex | null } + export type ZkSyncRpcBlock< TBlockTag extends BlockTag = BlockTag, TIncludeTransactions extends boolean = boolean, @@ -32,3 +64,7 @@ export type ZkSyncRpcBlock< ZkSyncRpcTransaction > & ZkSyncRpcBlockOverrides + +export type ZkSyncNumberParameter = { + number: number +} diff --git a/src/zksync/types/contract.ts b/src/zksync/types/contract.ts index 5a0d31930d..15062cfbdc 100644 --- a/src/zksync/types/contract.ts +++ b/src/zksync/types/contract.ts @@ -1,3 +1,11 @@ +import type { Address } from 'abitype' + +export type BridgeContractAddresses = { + erc20L1: Address + sharedL1: Address + sharedL2: Address +} + export type ContractDeploymentType = | 'create' | 'create2' diff --git a/src/zksync/types/eip1193.ts b/src/zksync/types/eip1193.ts new file mode 100644 index 0000000000..cfd18cdae2 --- /dev/null +++ b/src/zksync/types/eip1193.ts @@ -0,0 +1,155 @@ +import type { Address } from 'abitype' +import type { Hash, Hex } from '../../types/misc.js' +import type { ZkSyncBatchDetails, ZkSyncBlockDetails } from './block.js' +import type { ZkSyncFee } from './fee.js' +import type { MessageProof } from './proof.js' +import type { + TransactionRequest, + ZkSyncTransactionDetails, +} from './transaction.js' + +export type CommonDataRawBlockTransaction = { + sender: Address + maxFeePerGas: Hex + gasLimit: Hex + gasPerPubdataLimit: Hex + ethHash: Hash + ethBlock: number + canonicalTxHash: Hash + toMint: Hex + refundRecipient: Address +} + +export type RawBlockTransactions = { + common_data: { + L1?: { + serialId: number + deadlineBlock: number + layer2TipFee: Hex + fullFee: Hex + opProcessingType: string + priorityQueueType: string + } & CommonDataRawBlockTransaction + L2?: { + nonce: number + fee: ZkSyncFee + initiatorAddress: Address + signature: Uint8Array + transactionType: string + input?: { + hash: Hash + data: Uint8Array + } + paymasterParams: { + paymaster: Address + paymasterInput: Uint8Array + } + } + ProtocolUpgrade?: { + upgradeId: string + } & CommonDataRawBlockTransaction + } + execute: { + calldata: Hash + contractAddress: Address + factoryDeps?: Hash + value: bigint + } + received_timestamp_ms: number + raw_bytes?: string +}[] + +export type PublicZkSyncRpcSchema = [ + { + Method: 'zks_estimateFee' + Parameters: [TransactionRequest] + ReturnType: { + gas_limit: Hex + gas_per_pubdata_limit: Hex + max_fee_per_gas: Hex + max_priority_fee_per_gas: Hex + } + }, + { + Method: 'zks_estimateGasL1ToL2' + Parameters: [TransactionRequest] + ReturnType: bigint + }, + { + Method: 'zks_getBridgeContracts' + Parameters?: undefined + ReturnType: { + l1Erc20DefaultBridge: Address + l2Erc20DefaultBridge: Address + l1WethBridge: Address + l2WethBridge: Address + l1SharedDefaultBridge: Address + l2SharedDefaultBridge: Address + } + }, + { + Method: 'zks_getAllAccountBalances' + Parameters: [Address] + ReturnType: { [key: Address]: Hex } + }, + { + Method: 'zks_getTestnetPaymaster' + Parameters: undefined + ReturnType: Address + }, + { + Method: 'zks_L1ChainId' + Parameters: undefined + ReturnType: Hex + }, + { + Method: 'zks_getMainContract' + Parameters: undefined + ReturnType: Address + }, + { + Method: 'zks_L1BatchNumber' + Parameters: undefined + ReturnType: Hex + }, + { + Method: 'zks_getL2ToL1LogProof' + Parameters: [Hash, number | undefined] + ReturnType: MessageProof + }, + { + Method: 'zks_getL1BatchBlockRange' + Parameters: [number] + ReturnType: [Hex, Hex] + }, + { + Method: 'zks_getL1BatchDetails' + Parameters: [number] + ReturnType: ZkSyncBatchDetails + }, + { + Method: 'zks_getRawBlockTransactions' + Parameters: [number] + ReturnType: RawBlockTransactions + }, + { + Method: 'zks_getBlockDetails' + Parameters: [number] + ReturnType: ZkSyncBlockDetails + }, + { + Method: 'zks_getTransactionDetails' + Parameters: [Hash] + ReturnType: ZkSyncTransactionDetails + }, + { + Method: 'zks_getBridgehubContract' + Parameters: undefined + ReturnType: Address + }, + { + Method: 'zks_getBaseTokenL1Address' + Parameters: undefined + ReturnType: Address + }, +] diff --git a/src/zksync/types/fee.ts b/src/zksync/types/fee.ts index 8794df81b2..54d2245032 100644 --- a/src/zksync/types/fee.ts +++ b/src/zksync/types/fee.ts @@ -1,3 +1,10 @@ +export type ZkSyncFee = { + gasLimit: TQuantity + gasPerPubdataLimit: TQuantity + maxPriorityFeePerGas: TQuantity + maxFeePerGas: TQuantity +} + export type ZkSyncFeeValues = { gasPrice: TQuantity maxFeePerGas: TQuantity diff --git a/src/zksync/types/proof.ts b/src/zksync/types/proof.ts new file mode 100644 index 0000000000..f7e1483c73 --- /dev/null +++ b/src/zksync/types/proof.ts @@ -0,0 +1,7 @@ +import type { Hash } from '../../types/misc.js' + +export type MessageProof = { + id: number + proof: Hash[] + root: Hash +} diff --git a/src/zksync/types/transaction.ts b/src/zksync/types/transaction.ts index a0a8f24346..f8bb446e2a 100644 --- a/src/zksync/types/transaction.ts +++ b/src/zksync/types/transaction.ts @@ -1,6 +1,6 @@ import type { Address } from 'abitype' import type { FeeValuesEIP1559 } from '../../types/fee.js' -import type { Hex } from '../../types/misc.js' +import type { Hash, Hex } from '../../types/misc.js' import type { Index, Quantity, @@ -22,7 +22,7 @@ import type { } from '../../types/transaction.js' import type { ExactPartial, OneOf, UnionOmit } from '../../types/utils.js' import type { ZkSyncEip712Meta } from './eip712.js' -import type { ZkSyncFeeValues } from './fee.js' +import type { ZkSyncFee, ZkSyncFeeValues } from './fee.js' import type { ZkSyncL2ToL1Log, ZkSyncLog, @@ -117,7 +117,10 @@ export type ZkSyncRpcTransaction = // Transaction Request // https://era.zksync.io/docs/reference/concepts/transactions -type TransactionRequest = TransactionRequest_ & { +export type TransactionRequest< + TQuantity = bigint, + TIndex = number, +> = TransactionRequest_ & { gasPerPubdata?: undefined customSignature?: undefined paymaster?: undefined @@ -125,10 +128,10 @@ type TransactionRequest = TransactionRequest_ & { factoryDeps?: undefined } -export type ZkSyncTransactionRequestEIP712 = Omit< - TransactionRequestBase, - 'type' -> & +export type ZkSyncTransactionRequestEIP712< + TQuantity = bigint, + TIndex = number, +> = Omit, 'type'> & ExactPartial & { gasPerPubdata?: bigint | undefined customSignature?: Hex | undefined @@ -139,9 +142,9 @@ export type ZkSyncTransactionRequestEIP712 = Omit< | { paymaster?: undefined; paymasterInput?: undefined } ) -export type ZkSyncTransactionRequest = - | TransactionRequest - | ZkSyncTransactionRequestEIP712 +export type ZkSyncTransactionRequest = + | TransactionRequest + | ZkSyncTransactionRequestEIP712 type RpcTransactionRequest = RpcTransactionRequest_ & { eip712Meta?: undefined } @@ -243,3 +246,74 @@ export type TransactionRequestEIP712< customSignature?: Hex | undefined type?: TTransactionType | undefined } + +type CommonDataRawBlockTransaction = { + sender: Address + maxFeePerGas: Hex + gasLimit: Hex + gasPerPubdataLimit: Hex + ethHash: Hash + ethBlock: number + canonicalTxHash: Hash + toMint: Hex + refundRecipient: Address +} + +export type ZkSyncRawBlockTransactions = { + commonData: { + L1?: + | ({ + serialId: number + deadlineBlock: number + layer2TipFee: Hex + fullFee: Hex + opProcessingType: string + priorityQueueType: string + } & CommonDataRawBlockTransaction) + | undefined + L2?: + | { + nonce: number + fee: ZkSyncFee + initiatorAddress: Address + signature: Uint8Array + transactionType: string + input?: + | { + hash: Hash + data: Uint8Array + } + | undefined + paymasterParams: { + paymaster: Address + paymasterInput: Uint8Array + } + } + | undefined + ProtocolUpgrade?: + | ({ + upgradeId: string + } & CommonDataRawBlockTransaction) + | undefined + } + execute: { + calldata: Hash + contractAddress: Address + factoryDeps?: Hash + value: bigint + } + receivedTimestampMs: number + rawBytes?: string | undefined +}[] + +export type ZkSyncTransactionDetails = { + isL1Originated: boolean + status: string + fee: bigint + gasPerPubdata: bigint + initiatorAddress: Address + receivedAt: Date + ethCommitTxHash?: Hash | undefined + ethProveTxHash?: Hash | undefined + ethExecuteTxHash?: Hash | undefined +} diff --git a/src/zksync/utils/camelCaseKeys.test.ts b/src/zksync/utils/camelCaseKeys.test.ts new file mode 100644 index 0000000000..14e6b93f4e --- /dev/null +++ b/src/zksync/utils/camelCaseKeys.test.ts @@ -0,0 +1,57 @@ +import { expect, test } from 'vitest' +import { camelCaseKeys } from './camelCaseKeys.js' + +test('default', () => { + expect( + camelCaseKeys({ + foo_bar: 'baz', + }), + ).toMatchInlineSnapshot(` + { + "fooBar": "baz", + } + `) + + expect( + camelCaseKeys([ + { + foo_bar: 'baz', + }, + ]), + ).toMatchInlineSnapshot(` + [ + { + "fooBar": "baz", + }, + ] + `) + + expect( + camelCaseKeys({ + foo_bar: [{ baz_bar: 'baz' }], + }), + ).toMatchInlineSnapshot(` + { + "fooBar": [ + { + "bazBar": "baz", + }, + ], + } + `) + + expect( + camelCaseKeys({ + foo_bar: [{ baz_bar: 'baz' }, null], + }), + ).toMatchInlineSnapshot(` + { + "fooBar": [ + { + "bazBar": "baz", + }, + null, + ], + } + `) +}) diff --git a/src/zksync/utils/camelCaseKeys.ts b/src/zksync/utils/camelCaseKeys.ts new file mode 100644 index 0000000000..247e06b3d6 --- /dev/null +++ b/src/zksync/utils/camelCaseKeys.ts @@ -0,0 +1,11 @@ +export function camelCaseKeys(response: object): object { + if (!response) return response + if (typeof response !== 'object') return response + if (Array.isArray(response)) return response.map(camelCaseKeys) + return Object.fromEntries( + Object.entries(response).map(([key, value]) => [ + key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()), + camelCaseKeys(value), + ]), + ) +} diff --git a/src/zksync/utils/paymaster/getApprovalBasedPaymasterInput.ts b/src/zksync/utils/paymaster/getApprovalBasedPaymasterInput.ts index 12ae3d6478..01357ad066 100644 --- a/src/zksync/utils/paymaster/getApprovalBasedPaymasterInput.ts +++ b/src/zksync/utils/paymaster/getApprovalBasedPaymasterInput.ts @@ -1,7 +1,10 @@ import type { Address } from 'abitype' import type { ByteArray, Hex } from '../../../types/misc.js' -import type { EncodeFunctionDataReturnType } from '../../../utils/abi/encodeFunctionData.js' -import { bytesToHex, encodeFunctionData } from '../../../utils/index.js' +import { + type EncodeFunctionDataReturnType, + encodeFunctionData, +} from '../../../utils/abi/encodeFunctionData.js' +import { bytesToHex } from '../../../utils/encoding/toHex.js' import { paymasterAbi } from '../../constants/abis.js' export type GetApprovalBasedPaymasterInputParameters = { diff --git a/src/zksync/utils/paymaster/getGeneralPaymasterInput.ts b/src/zksync/utils/paymaster/getGeneralPaymasterInput.ts index 2ce2e5fb71..71f963893a 100644 --- a/src/zksync/utils/paymaster/getGeneralPaymasterInput.ts +++ b/src/zksync/utils/paymaster/getGeneralPaymasterInput.ts @@ -1,6 +1,9 @@ import type { ByteArray, Hex } from '../../../types/misc.js' -import type { EncodeFunctionDataReturnType } from '../../../utils/abi/encodeFunctionData.js' -import { bytesToHex, encodeFunctionData } from '../../../utils/index.js' +import { + type EncodeFunctionDataReturnType, + encodeFunctionData, +} from '../../../utils/abi/encodeFunctionData.js' +import { bytesToHex } from '../../../utils/encoding/toHex.js' import { paymasterAbi } from '../../constants/abis.js' export type GetGeneralPaymasterInputParameters = { diff --git a/test/setup.ts b/test/setup.ts index 9377d7915f..0f98e41dfa 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -44,28 +44,3 @@ afterAll(async () => { jsonRpcUrl: anvilMainnet.forkUrl, }) }) - -afterEach((context) => { - // Print the last log entries from anvil after each test. - context.onTestFailed(async (result) => { - try { - const response = await fetchLogs('http://127.0.0.1:8545', poolId) - const logs = response.slice(-20) - - if (logs.length === 0) { - return - } - - // Try to append the log messages to the vitest error message if possible. Otherwise, print them to the console. - const error = result.errors?.[0] - - if (error !== undefined) { - error.message += - '\n\nAnvil log output\n=======================================\n' - error.message += `\n${logs.join('\n')}` - } else { - console.log(...logs) - } - } catch {} - }) -}) diff --git a/test/src/zksync.ts b/test/src/zksync.ts new file mode 100644 index 0000000000..fe4da0e5e3 --- /dev/null +++ b/test/src/zksync.ts @@ -0,0 +1,211 @@ +import { zkSyncLocalNode } from '~viem/chains/index.js' +import { createClient } from '~viem/clients/createClient.js' +import { http } from '~viem/index.js' +import { accounts } from './constants.js' + +export const zkSyncClientLocalNode = createClient({ + chain: zkSyncLocalNode, + transport: http(), +}) + +export const zkSyncClientLocalNodeWithAccount = createClient({ + account: accounts[0].address, + chain: zkSyncLocalNode, + transport: http(), +}) + +export function getZksyncMockProvider( + request: ({ + method, + params, + }: { method: string; params?: unknown }) => Promise, +) { + return { + on: () => null, + removeListener: () => null, + request: ({ method, params }: any) => request({ method, params }), + } +} + +export const mockedL1BatchNumber = '0x2012' + +export const mockFeeValues = { + gas_limit: '0x2803d', + gas_per_pubdata_limit: '0x42', + max_fee_per_gas: '0xee6b280', + max_priority_fee_per_gas: '0x0', +} + +export const mockAccountBalances = { + '0x0000000000000000000000000000000000000000': '1000000000000000000', + '0x0000000000000000000000000000000000000001': '2000000000000000000', + '0x0000000000000000000000000000000000000002': '3500000000000000000', +} + +export const mockBaseTokenL1Address = + '0x0000000000000000000000000000000000000000' + +export const mockBlockDetails = { + number: 0, + timestamp: 1713435780, + l1BatchNumber: 0, + l1TxCount: 2, + l2TxCount: 3, + status: 'verified', + baseSystemContractsHashes: { + bootloader: + '0x010008bb22aea1e22373cb8d807b15c67eedd65523e9cba4cc556adfa504f7b8', + default_aa: + '0x010008bb22aea1e22373cb8d807b15c67eedd65523e9cba4cc556adfa504f7b8', + }, + operatorAddress: '0xde03a0b5963f75f1c8485b355ff6d30f3093bde7', + protocolVersion: 'Version19', +} + +export const mockAddress = '0x173999892363ba18c9dc60f8c57152fc914bce89' + +export const mockAddresses = { + l1SharedDefaultBridge: '0x648afeaf09a3db988ac41b786001235bbdbc7640', + l2SharedDefaultBridge: '0xfd61c893b903fa133908ce83dfef67c4c2350dd8', + l1Erc20DefaultBridge: '0xbe270c78209cfda84310230aaa82e18936310b2e', + l2Erc20DefaultBridge: '0xfc073319977e314f251eae6ae6be76b0b3baeecf', + l1WethBridge: '0x5e6d086f5ec079adff4fb3774cdf3e8d6a34f7e9', + l2WethBridge: '0x5e6d086f5ec079adff4fb3774cdf3e8d6a34f7e9', +} + +export const mockRange = [0, 5] + +export const mockDetails = { + number: 0, + timestamp: 0, + l1TxCount: 0, + l2TxCount: 0, + l1BatchNumber: 0, + status: 'verified', + l1GasPrice: 0, + l2FairGasPrice: 0, + baseSystemContractsHashes: { + bootloader: + '0x010008bb22aea1e22373cb8d807b15c67eedd65523e9cba4cc556adfa504f7b8', + default_aa: + '0x01000563a7f32f1d97b4697f3bc996132433314b9b17351a7f7cd6073f618569', + }, +} + +export const mockChainId = '0x9' + +export const mockProofValues = { + id: 112, + proof: [ + '0x3d999d6a5bacdc5c8c01ad0917c1dca03c632fc486ac623a8857804374b0d1b1', + '0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0', + '0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111', + '0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa', + '0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89', + '0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272', + '0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d', + '0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c', + '0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db', + '0x124b05ec272cecd7538fdafe53b6628d31188ffb6f345139aac3c3c1fd2e470f', + '0xc3be9cbd19304d84cca3d045e06b8db3acd68c304fc9cd4cbffe6d18036cb13f', + ], + root: '0x443ddd5b010069db588a5f21e9145f94a93dd8109c72cc70d79281f1c19db2c8', +} + +export const mockMainContractAddress = + '0x9fab5aec650f1ce6e35ec60a611af0a1345927c8' + +export const mockRawBlockTransaction = [ + { + common_data: { + L1: { + sender: '0xde03a0b5963f75f1c8485b355ff6d30f3093bde7', + serialId: 0, + deadlineBlock: 0, + layer2TipFee: '0x0', + fullFee: '0x0', + maxFeePerGas: '0x1dcd6500', + gasLimit: '0x44aa200', + gasPerPubdataLimit: '0x320', + opProcessingType: 'Common', + priorityQueueType: 'Deque', + ethHash: + '0x0000000000000000000000000000000000000000000000000000000000000000', + ethBlock: 125, + canonicalTxHash: + '0x9376f805ccd40186a73672a4d0db064060956e70c4ae486ab205291986439343', + toMint: '0x7fe5cf2bea0000', + refundRecipient: '0xde03a0b5963f75f1c8485b355ff6d30f3093bde7', + }, + L2: { + nonce: 0, + fee: { + gas_limit: '0x2803d', + gas_per_pubdata_limit: '0x42', + max_fee_per_gas: '0xee6b280', + max_priority_fee_per_gas: '0x0', + }, + initiatorAddress: '0x000000000000000000000000000000000000800b', + signature: new Uint8Array(), + transactionType: 'ProtocolUpgrade', + input: { + hash: '0x', + data: new Uint8Array(), + }, + paymasterParams: { + paymaster: '0x0a67078A35745947A37A552174aFe724D8180c25', + paymasterInput: new Uint8Array(), + }, + }, + }, + execute: { + calldata: + '0xef0e2ff4000000000000000000000000000000000000000000000000000000000000010e', + contractAddress: '0x000000000000000000000000000000000000800b', + factoryDeps: '0x', + value: BigInt(0), + }, + received_timestamp_ms: 1713436617435, + raw_bytes: '', + }, +] + +export const mockTestnetPaymasterAddress = + '0x0a67078A35745947A37A552174aFe724D8180c25' + +export const mockTransactionDetails = { + isL1Originated: true, + status: 'validated', + fee: 10n, + gasPerPubdata: 50000n, + initiatorAddress: '0x000000000000000000000000000000000000800b', + receivedAt: new Date(1713436617435), +} + +export const mockedGasEstimation = 123456789n + +export const mockRequestReturnData = async (method: string) => { + if (method === 'zks_L1ChainId') return mockChainId + if (method === 'zks_estimateFee') return mockFeeValues + if (method === 'zks_getAllAccountBalances') return mockAccountBalances + if (method === 'zks_getBaseTokenL1Address') return mockBaseTokenL1Address + if (method === 'zks_getBlockDetails') return mockBlockDetails + if (method === 'zks_getBridgehubContract') return mockAddress + if (method === 'zks_getBridgeContracts') return mockAddresses + if (method === 'zks_getL1BatchBlockRange') return mockRange + if (method === 'zks_getL1BatchDetails') return mockDetails + if (method === 'zks_getL2ToL1LogProof') return mockProofValues + if (method === 'zks_getMainContract') return mockMainContractAddress + if (method === 'zks_getRawBlockTransactions') return mockRawBlockTransaction + if (method === 'zks_getTestnetPaymaster') return mockTestnetPaymasterAddress + if (method === 'zks_getTransactionDetails') return mockTransactionDetails + if (method === 'zks_L1BatchNumber') return mockedL1BatchNumber + if (method === 'zks_estimateGasL1ToL2') return mockedGasEstimation + return undefined +} + +export function mockClientPublicActionsL2(client: any) { + client.request = async ({ method }: any) => { + return mockRequestReturnData(method) + } +}