UNPKG

zksync-ethers

Version:

A Web3 library for interacting with the ZkSync Layer 2 scaling solution.

1,484 lines (1,381 loc) 57.1 kB
import {AbiCoder, BigNumberish, BytesLike, ethers, SignatureLike} from 'ethers'; import { Address, DeploymentInfo, Eip712Meta, EthereumSignature, PaymasterParams, PriorityOpTree, PriorityQueueType, Transaction, TransactionLike, TransactionReceipt, TransactionRequest, } from './types'; import {Provider} from './provider'; import {EIP712Signer} from './signer'; import {IERC20__factory, IL1NativeTokenVault} from './typechain'; import IZkSyncABI from '../abi/IZkSyncHyperchain.json'; import IBridgehubABI from '../abi/IBridgehub.json'; import IContractDeployerABI from '../abi/IContractDeployer.json'; import Contract2FactoryABI from '../abi/Contract2Factory.json'; import IL1MessengerABI from '../abi/IL1Messenger.json'; import IERC20ABI from '../abi/IERC20.json'; import IERC1271ABI from '../abi/IERC1271.json'; import IL1BridgeABI from '../abi/IL1ERC20Bridge.json'; import IL2BridgeABI from '../abi/IL2Bridge.json'; import INonceHolderABI from '../abi/INonceHolder.json'; export * from './paymaster-utils'; export * from './smart-account-utils'; export {EIP712_TYPES} from './signer'; /** * The ABI for the `ZKsync` interface. * @readonly */ export const ZKSYNC_MAIN_ABI = new ethers.Interface(IZkSyncABI); /** * The ABI of the `Bridgehub` interface. * @readonly */ export const BRIDGEHUB_ABI = new ethers.Interface(IBridgehubABI); /** * The ABI for the `IContractDeployer` interface, which is utilized for deploying smart contracts. * @readonly */ export const CONTRACT_DEPLOYER = new ethers.Interface(IContractDeployerABI); /** * The ABI for the `Contract2Factory` interface, which is utilized for deploying smart contracts using CREATE2 and CREATE2ACCOUNT. * @readonly */ export const CONTRACT_2_FACTORY = new ethers.Interface(Contract2FactoryABI); /** * The ABI for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. * @readonly */ export const L1_MESSENGER = new ethers.Interface(IL1MessengerABI); /** * The ABI for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. * @readonly */ export const IERC20 = new ethers.Interface(IERC20ABI); /** * The ABI for the `IERC1271` interface, which is utilized for signature validation by contracts. * @readonly */ export const IERC1271 = new ethers.Interface(IERC1271ABI); /** * The ABI for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. * @readonly */ export const L1_BRIDGE_ABI = new ethers.Interface(IL1BridgeABI); /** * The ABI for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. * @readonly */ export const L2_BRIDGE_ABI = new ethers.Interface(IL2BridgeABI); /** * The ABI for the `INonceHolder` interface, which is utilized for managing deployment nonces. * @readonly */ export const NONCE_HOLDER_ABI = new ethers.Interface(INonceHolderABI); /** * The address of the L1 `ETH` token. * @readonly */ export const ETH_ADDRESS: Address = '0x0000000000000000000000000000000000000000'; /** * The address of the L1 `ETH` token. * @readonly */ export const LEGACY_ETH_ADDRESS: Address = '0x0000000000000000000000000000000000000000'; /** * In the contracts the zero address can not be used, use one instead * @readonly */ export const ETH_ADDRESS_IN_CONTRACTS: Address = '0x0000000000000000000000000000000000000001'; /** * The formal address for the `Bootloader`. * @readonly */ export const BOOTLOADER_FORMAL_ADDRESS: Address = '0x0000000000000000000000000000000000008001'; /** * The address of the Contract deployer. * @readonly */ export const CONTRACT_DEPLOYER_ADDRESS: Address = '0x0000000000000000000000000000000000008006'; /** * The address of the Contract2Factory. * @readonly */ export const CONTRACT_2_FACTORY_ADDRESS: Address = '0x0000000000000000000000000000000000010000'; /** * The address of the L1 messenger. * @readonly */ export const L1_MESSENGER_ADDRESS: Address = '0x0000000000000000000000000000000000008008'; /** * The address of the L2 `ETH` token. * @readonly * @deprecated In favor of {@link L2_BASE_TOKEN_ADDRESS}. */ export const L2_ETH_TOKEN_ADDRESS: Address = '0x000000000000000000000000000000000000800a'; /** * The address of the base token. * @readonly */ export const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; /** * The address of the Nonce holder. * @readonly */ export const NONCE_HOLDER_ADDRESS: Address = '0x0000000000000000000000000000000000008003'; /** * Used for applying and undoing aliases on addresses during bridging from L1 to L2. * @readonly */ export const L1_TO_L2_ALIAS_OFFSET: Address = '0x1111000000000000000000000000000000001111'; export const L2_ASSET_ROUTER_ADDRESS: Address = '0x0000000000000000000000000000000000010003'; export const L2_NATIVE_TOKEN_VAULT_ADDRESS: Address = '0x0000000000000000000000000000000000010004'; /** * The EIP1271 magic value used for signature validation in smart contracts. * This predefined constant serves as a standardized indicator to signal successful * signature validation by the contract. * * @readonly */ export const EIP1271_MAGIC_VALUE = '0x1626ba7e'; /** * Represents an EIP712 transaction type. * * @readonly */ export const EIP712_TX_TYPE = 0x71; /** * Represents a priority transaction operation on L2. * * @readonly */ export const PRIORITY_OPERATION_L2_TX_TYPE = 0xff; /** * The maximum bytecode length in bytes that can be deployed. * * @readonly */ export const MAX_BYTECODE_LEN_BYTES: number = ((1 << 16) - 1) * 32; /** * Numerator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. * * This constant is part of a coefficient calculation to adjust the gas limit to account for variations * in the SDK estimation, ensuring the transaction will be accepted. * * @readonly */ export const L1_FEE_ESTIMATION_COEF_NUMERATOR = 12; /** * Denominator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. * * This constant is part of a coefficient calculation to adjust the gas limit to account for variations * in the SDK estimation, ensuring the transaction will be accepted. * * @readonly */ export const L1_FEE_ESTIMATION_COEF_DENOMINATOR = 10; /** * Gas limit used for displaying the error messages when the * users do not have enough fee when depositing ERC20 token from L1 to L2. * * @readonly */ export const L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT = 1_000_000; /** * Gas limit used for displaying the error messages when the * users do not have enough fee when depositing `ETH` token from L1 to L2. * * @readonly */ export const L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT = 200_000; /** * Default gas per pubdata byte for L2 transactions. * This value is utilized when inserting a default value for type 2 * and EIP712 type transactions. * * @readonly */ // It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; /** * The `L1->L2` transactions are required to have the following gas per pubdata byte. * * @readonly */ export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; /** * Returns true if token represents ETH on L1 or L2. * * @param token The token address. * * @example * * import { utils } from "zksync-ethers"; * * const isL1ETH = utils.isETH(utils.ETH_ADDRESS); // true * const isL2ETH = utils.isETH(utils.ETH_ADDRESS_IN_CONTRACTS); // true */ export function isETH(token: Address) { return ( isAddressEq(token, LEGACY_ETH_ADDRESS) || isAddressEq(token, L2_BASE_TOKEN_ADDRESS) || isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS) ); } /** * Pauses execution for a specified number of milliseconds. * * @param millis The number of milliseconds to pause execution. * * @example * * import { utils } from "zksync-ethers"; * * await utils.sleep(1_000); */ export function sleep(millis: number): Promise<unknown> { return new Promise(resolve => setTimeout(resolve, millis)); } /** * Returns the default settings for L1 transactions. */ export function layer1TxDefaults(): { queueType: PriorityQueueType.Deque; opTree: PriorityOpTree.Full; } { return { queueType: PriorityQueueType.Deque, opTree: PriorityOpTree.Full, }; } /** * Returns a `keccak` encoded message with a given sender address and block number from the L1 messenger contract. * * @param sender The sender of the message on L2. * @param msg The encoded message. * @param txNumberInBlock The index of the transaction in the block. * @returns The hashed `L2->L1` message. * * @example * * import { utils } from "zksync-ethers"; * * const withdrawETHMessage = "0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600"; * const withdrawETHMessageHash = utils.getHashedL2ToL1Msg("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", withdrawETHMessage, 0); * // withdrawETHMessageHash = "0xd8c80ecb64619e343f57c3b133c6c6d8dd0572dd3488f1ca3276c5b7fd3a938d" */ export function getHashedL2ToL1Msg( sender: Address, msg: BytesLike, txNumberInBlock: number ): string { const encodedMsg = new Uint8Array([ 0, // l2ShardId 1, // isService ...ethers.getBytes(ethers.toBeHex(txNumberInBlock, 2)), ...ethers.getBytes(L1_MESSENGER_ADDRESS), ...ethers.getBytes(ethers.zeroPadValue(sender, 32)), ...ethers.getBytes(ethers.keccak256(msg)), ]); return ethers.keccak256(encodedMsg); } /** * Returns a log containing details of all deployed contracts related to a transaction receipt. * * @param receipt The transaction receipt containing deployment information. * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const deployTx = "<DEPLOY TRANSACTION>"; * const receipt = await provider.getTransactionReceipt(deployTx); * const deploymentInfo = utils.getDeployedContracts(receipt as ethers.TransactionReceipt); */ export function getDeployedContracts( receipt: TransactionReceipt ): DeploymentInfo[] { const addressBytesLen = 40; return ( receipt.logs .filter( log => log.topics[0] === ethers.id('ContractDeployed(address,bytes32,address)') && isAddressEq(log.address, CONTRACT_DEPLOYER_ADDRESS) ) // Take the last topic (deployed contract address as U256) and extract address from it (U160). .map(log => { const sender = `0x${log.topics[1].slice( log.topics[1].length - addressBytesLen )}`; const bytecodeHash = log.topics[2]; const address = `0x${log.topics[3].slice( log.topics[3].length - addressBytesLen )}`; return { sender: ethers.getAddress(sender), bytecodeHash: bytecodeHash, deployedAddress: ethers.getAddress(address), }; }) ); } /** * Generates a future-proof contract address using a salt plus bytecode, allowing the determination of an address before deployment. * * @param sender The sender's address. * @param bytecodeHash The hash of the bytecode, typically the output from `zkSolc`. * @param salt A randomization element used to create the contract address. * @param input The ABI-encoded constructor arguments, if any. * * @remarks The implementation of `create2Address` in ZKsync Era may differ slightly from Ethereum. * * @example * * import { utils } from "zksync-ethers"; * * const address = utils.create2Address("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", "0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41", "0x01", "0x01"); * // address = "0x29bac3E5E8FFE7415F97C956BFA106D70316ad50" */ export function create2Address( sender: Address, bytecodeHash: BytesLike, salt: BytesLike, input: BytesLike = '' ): string { const prefix = ethers.keccak256(ethers.toUtf8Bytes('zksyncCreate2')); const inputHash = ethers.keccak256(input); const addressBytes = ethers .keccak256( ethers.concat([ prefix, ethers.zeroPadValue(sender, 32), salt, bytecodeHash, inputHash, ]) ) .slice(26); return ethers.getAddress(addressBytes); } /** * Generates a contract address from the deployer's account and nonce. * * @param sender The address of the deployer's account. * @param senderNonce The nonce of the deployer's account. * * @example * * import { utils } from "zksync-ethers"; * * const address = utils.createAddress("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", 1); * // address = "0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021" */ export function createAddress( sender: Address, senderNonce: BigNumberish ): string { const prefix = ethers.keccak256(ethers.toUtf8Bytes('zksyncCreate')); const addressBytes = ethers .keccak256( ethers.concat([ prefix, ethers.zeroPadValue(sender, 32), ethers.toBeHex(senderNonce, 32), ]) ) .slice(26); return ethers.getAddress(addressBytes); } /** * Checks if the transaction's base cost is greater than the provided value, which covers the transaction's cost. * * @param baseCost The base cost of the transaction. * @param value The value covering the transaction's cost. * @throws {Error} The base cost must be greater than the provided value. * * @example * * import { utils } from "zksync-ethers"; * * const baseCost = 100; * const value = 99; * try { * await utils.checkBaseCost(baseCost, value); * } catch (e) { * // e.message = `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${baseCost}, provided value: ${value}`, * } */ export async function checkBaseCost( baseCost: ethers.BigNumberish, value: ethers.BigNumberish | Promise<ethers.BigNumberish> ): Promise<void> { if (baseCost > (await value)) { throw new Error( 'The base cost of performing the priority operation is higher than the provided value parameter ' + `for the transaction: baseCost: ${baseCost}, provided value: ${value}!` ); } } /** * Serializes an EIP712 transaction and includes a signature if provided. * * @param transaction The transaction that needs to be serialized. * @param [signature] Ethers signature to be included in the transaction. * @throws {Error} Throws an error if: * - `transaction.customData.customSignature` is an empty string. The transaction should be signed, and the `transaction.customData.customSignature` field should be populated with the signature. It should not be specified if the transaction is not signed. * - `transaction.chainId` is not provided. * - `transaction.from` is not provided. * * @example Serialize EIP712 transaction without signature. * * import { utils } from "zksync-ethers"; * * const serializedTx = utils.serializeEip712({ chainId: 270, from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" }, null); * * // serializedTx = "0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" * * @example Serialize EIP712 transaction with signature. * * import { utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const signature = ethers.Signature.from("0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a"); * * const serializedTx = utils.serializeEip712( * { * chainId: 270, * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", * value: 1_000_000, * }, * signature * ); * // serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" */ export function serializeEip712( transaction: TransactionLike, signature?: ethers.SignatureLike ): string { if (!transaction.chainId) { throw Error("Transaction chainId isn't set!"); } if (!transaction.from) { throw new Error( 'Explicitly providing `from` field is required for EIP712 transactions!' ); } const from = transaction.from; const meta: Eip712Meta = transaction.customData ?? {}; const maxFeePerGas = transaction.maxFeePerGas || transaction.gasPrice || 0; const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; const fields: any[] = [ ethers.toBeArray(transaction.nonce || 0), ethers.toBeArray(maxPriorityFeePerGas), ethers.toBeArray(maxFeePerGas), ethers.toBeArray(transaction.gasLimit || 0), transaction.to ? ethers.getAddress(transaction.to) : '0x', ethers.toBeArray(transaction.value || 0), transaction.data || '0x', ]; if (signature) { const sig = ethers.Signature.from(signature); fields.push(ethers.toBeArray(sig.yParity)); fields.push(ethers.toBeArray(sig.r)); fields.push(ethers.toBeArray(sig.s)); } else { fields.push(ethers.toBeArray(transaction.chainId)); fields.push('0x'); fields.push('0x'); } fields.push(ethers.toBeArray(transaction.chainId)); fields.push(ethers.getAddress(from)); // Add meta fields.push( ethers.toBeArray(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT) ); fields.push((meta.factoryDeps ?? []).map(dep => ethers.hexlify(dep))); if ( meta.customSignature && ethers.getBytes(meta.customSignature).length === 0 ) { throw new Error('Empty signatures are not supported!'); } fields.push(meta.customSignature || '0x'); if (meta.paymasterParams) { fields.push([ meta.paymasterParams.paymaster, ethers.hexlify(meta.paymasterParams.paymasterInput), ]); } else { fields.push([]); } return ethers.concat([ new Uint8Array([EIP712_TX_TYPE]), ethers.encodeRlp(fields), ]); } /** * Returns the hash of the given bytecode. * * @param bytecode The bytecode to hash. * * @example * * import { utils } from "zksync-ethers"; * * const bytecode = * "0x000200000000000200010000000103550000006001100270000000130010019d0000008001000039000000400010043f0000000101200190000000290000c13d0000000001000031000000040110008c000000420000413d0000000101000367000000000101043b000000e001100270000000150210009c000000310000613d000000160110009c000000420000c13d0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000200310008c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000420000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000490001042e0000000001000416000000000110004c000000420000c13d0000002001000039000001000010044300000120000004430000001401000041000000490001042e0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000000310004c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000440000613d00000000010000190000004a00010430000000000100041a000000800010043f0000001801000041000000490001042e0000004800000432000000490001042e0000004a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000060fe47b18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009c8c8fa789967eb514f3ec9def748480945cc9b10fcbd1a19597d924eb201083"; * const hashedBytecode = utils.hashBytecode(bytecode); * /* * hashedBytecode = new Uint8Array([ * 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, 133, * 160, 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, * ]), * ); * *\/ */ export function hashBytecode(bytecode: ethers.BytesLike): Uint8Array { // For getting the consistent length we first convert the bytecode to UInt8Array const bytecodeAsArray = ethers.getBytes(bytecode); if (bytecodeAsArray.length % 32 !== 0) { throw new Error('The bytecode length in bytes must be divisible by 32!'); } if (bytecodeAsArray.length > MAX_BYTECODE_LEN_BYTES) { throw new Error( `Bytecode can not be longer than ${MAX_BYTECODE_LEN_BYTES} bytes!` ); } const hashStr = ethers.sha256(bytecodeAsArray); const hash = ethers.getBytes(hashStr); // Note that the length of the bytecode // should be provided in 32-byte words. const bytecodeLengthInWords = bytecodeAsArray.length / 32; if (bytecodeLengthInWords % 2 === 0) { throw new Error('Bytecode length in 32-byte words must be odd!'); } const bytecodeLength = ethers.toBeArray(bytecodeLengthInWords); // The bytecode should always take the first 2 bytes of the bytecode hash, // so we pad it from the left in case the length is smaller than 2 bytes. const bytecodeLengthPadded = ethers.getBytes( ethers.zeroPadValue(bytecodeLength, 2) ); const codeHashVersion = new Uint8Array([1, 0]); hash.set(codeHashVersion, 0); hash.set(bytecodeLengthPadded, 2); return hash; } /** * Parses an EIP712 transaction from a payload. * * @param payload The payload to parse. * * @example * * import { utils, types } from "zksync-ethers"; * * const serializedTx = * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; * const tx: types.TransactionLike = utils.parseEip712(serializedTx); * /* * tx: types.TransactionLike = { * type: 113, * nonce: 0, * maxPriorityFeePerGas: 0n, * maxFeePerGas: 0n, * gasLimit: 0n, * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", * value: 1000000n, * data: "0x", * chainId: 270n, * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", * customData: { * gasPerPubdata: 50000n, * factoryDeps: [], * customSignature: "0x", * paymasterParams: null, * }, * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", * }; * *\/ */ export function parseEip712(payload: ethers.BytesLike): TransactionLike { function handleAddress(value: string): string | null { if (value === '0x') { return null; } return ethers.getAddress(value); } function handleNumber(value: string): bigint { if (!value || value === '0x') { return 0n; } return BigInt(value); } function arrayToPaymasterParams(arr: string[]): PaymasterParams | undefined { if (arr.length === 0) { return undefined; } if (arr.length !== 2) { throw new Error( `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!` ); } return { paymaster: ethers.getAddress(arr[0]), paymasterInput: ethers.getBytes(arr[1]), }; } const bytes = ethers.getBytes(payload); const raw = ethers.decodeRlp(bytes.slice(1)) as string[]; const transaction: TransactionLike = { type: EIP712_TX_TYPE, nonce: Number(handleNumber(raw[0])), maxPriorityFeePerGas: handleNumber(raw[1]), maxFeePerGas: handleNumber(raw[2]), gasLimit: handleNumber(raw[3]), to: handleAddress(raw[4]), value: handleNumber(raw[5]), data: raw[6], chainId: handleNumber(raw[10]), from: handleAddress(raw[11]), customData: { gasPerPubdata: handleNumber(raw[12]), factoryDeps: raw[13] as unknown as string[], customSignature: raw[14], paymasterParams: arrayToPaymasterParams(raw[15] as unknown as string[]), }, }; const ethSignature = { v: Number(handleNumber(raw[7])), r: raw[8], s: raw[9], }; if ( (ethers.hexlify(ethSignature.r) === '0x' || ethers.hexlify(ethSignature.s) === '0x') && !transaction.customData?.customSignature ) { return transaction; } if ( ethSignature.v !== 0 && ethSignature.v !== 1 && !transaction.customData?.customSignature ) { throw new Error('Failed to parse signature!'); } if (!transaction.customData?.customSignature) { transaction.signature = ethers.Signature.from(ethSignature); } transaction.hash = eip712TxHash(transaction, ethSignature); return transaction; } /** * Returns the custom signature from EIP712 transaction if provided, * otherwise returns the Ethereum signature in bytes representation. * * @param transaction The EIP712 transaction that may contain a custom signature. * If a custom signature is not present in the transaction, the `ethSignature` parameter will be used. * @param [ethSignature] The Ethereum transaction signature. This parameter is ignored if the transaction * object contains a custom signature. * * @example Get custom signature from the EIP712 transaction. * * import { utils, types } from "zksync-ethers"; * * const tx: types.TransactionLike = { * type: 113, * nonce: 0, * maxPriorityFeePerGas: 0n, * maxFeePerGas: 0n, * gasLimit: 0n, * to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', * value: 1_000_000n, * data: '0x', * chainId: 270n, * from: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', * customData: { * gasPerPubdata: 50_000n, * factoryDeps: [], * customSignature: * '0x307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162', * paymasterParams: { * paymaster: '0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174', * paymasterInput: ethers.getBytes( * '0x949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000' * ), * }, * }, * hash: '0xc0ba55587423e1ef281b06a9d684b481365897f37a6ad611d7619b1b7e0bc908', * }; * * const signature = utils.getSignature(tx); * // signature = '0x307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162' * * @example Get Ethereum transaction signature. * * import { utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ethSignature = ethers.Signature.from( * '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a' * ); * const signature = utils.getSignature(undefined, ethSignature); * // signature = '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a' */ function getSignature( transaction: any, ethSignature?: EthereumSignature ): Uint8Array { if ( transaction?.customData?.customSignature && transaction.customData.customSignature.length ) { return ethers.getBytes(transaction.customData.customSignature); } if (!ethSignature) { throw new Error('No signature provided!'); } const r = ethers.getBytes(ethers.zeroPadValue(ethSignature.r, 32)); const s = ethers.getBytes(ethers.zeroPadValue(ethSignature.s, 32)); const v = ethSignature.v; return new Uint8Array([...r, ...s, v]); } /** * Returns the hash of an EIP712 transaction. If a custom signature is provided in the transaction, * it will be used to form the transaction hash. Otherwise, the Ethereum signature specified in the * `ethSignature` parameter will be used. * * @param transaction The EIP712 transaction that may contain a custom signature. * If a custom signature is not present in the transaction, the `ethSignature` parameter will be used. * @param [ethSignature] The Ethereum transaction signature. This parameter is ignored if the transaction * object contains a custom signature. * * @example Get transaction hash using custom signature from the transaction. * * import { utils } from "zksync-ethers"; * * const tx: types.TransactionRequest = { * type: 113, * nonce: 0, * maxPriorityFeePerGas: 0n, * maxFeePerGas: 0n, * gasLimit: 0n, * to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', * value: 1_000_000n, * data: '0x', * chainId: 270n, * from: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', * customData: { * gasPerPubdata: 50_000n, * factoryDeps: [], * customSignature: * '0x307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162', * paymasterParams: { * paymaster: '0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174', * paymasterInput: ethers.getBytes( * '0x949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000' * ), * }, * }, * }; * * const hash = utils.eip712TxHash(tx); * // hash = '0xc0ba55587423e1ef281b06a9d684b481365897f37a6ad611d7619b1b7e0bc908' * * @example Get transaction hash using Ethereum signature. * * import { utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const tx: types.TransactionRequest = { * chainId: 270n, * from: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', * to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', * value: 1_000_000n, * }; * const signature = ethers.Signature.from( * '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a' * ); * const hash = utils.eip712TxHash(tx, signature); * // hash = '0x8efdc7ce5f5a75ab945976c3e2b0c2a45e9f8e15ff940d05625ac5545cd9f870' */ export function eip712TxHash( transaction: Transaction | TransactionRequest, ethSignature?: EthereumSignature ): string { const signedDigest = EIP712Signer.getSignedDigest(transaction); const hashedSignature = ethers.keccak256( getSignature(transaction, ethSignature) ); return ethers.keccak256(ethers.concat([signedDigest, hashedSignature])); } /** * Returns the hash of the L2 priority operation from a given transaction receipt and L2 address. * * @param txReceipt The receipt of the L1 transaction. * @param zkSyncAddress The address of the ZKsync Era main contract. * * @example * * import { Provider, types, utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * const ethProvider = ethers.getDefaultProvider("sepolia"); * const l1Tx = "0xcca5411f3e514052f4a4ae1c2020badec6e0998adb52c09959c5f5ff15fba3a8"; * const l1TxReceipt = await ethProvider.getTransactionReceipt(l1Tx); * if (l1TxReceipt) { * const l2Hash = getL2HashFromPriorityOp( * receipt as ethers.TransactionReceipt, * await provider.getMainContractAddress() * ); * } */ export function getL2HashFromPriorityOp( txReceipt: ethers.TransactionReceipt, zkSyncAddress: Address ): string { let txHash: string | null = null; for (const log of txReceipt.logs) { if (!isAddressEq(log.address, zkSyncAddress)) { continue; } try { const priorityQueueLog = ZKSYNC_MAIN_ABI.parseLog({ topics: log.topics as string[], data: log.data, }); if (priorityQueueLog && priorityQueueLog.args.txHash !== null) { txHash = priorityQueueLog.args.txHash; } } catch { // skip } } if (!txHash) { throw new Error('Failed to parse tx logs!'); } return txHash; } const ADDRESS_MODULO = 2n ** 160n; /** * Converts the address that submitted a transaction to the inbox on L1 to the `msg.sender` viewed on L2. * Returns the `msg.sender` of the `L1->L2` transaction as the address of the contract that initiated the transaction. * * All available cases: * - During a normal transaction, if contract `A` calls contract `B`, the `msg.sender` is `A`. * - During `L1->L2` communication, if an EOA `X` calls contract `B`, the `msg.sender` is `X`. * - During `L1->L2` communication, if a contract `A` calls contract `B`, the `msg.sender` is `applyL1ToL2Alias(A)`. * * @param address The address of the contract. * @returns The transformed address representing the `msg.sender` on L2. * * @see * {@link undoL1ToL2Alias}. * * @example * * import { utils } from "zksync-ethers"; * * const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb"; * const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); * // l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC" */ export function applyL1ToL2Alias(address: string): string { return ethers.toBeHex( (BigInt(address) + BigInt(L1_TO_L2_ALIAS_OFFSET)) % ADDRESS_MODULO, 20 ); } /** * Converts and returns the `msg.sender` viewed on L2 to the address that submitted a transaction to the inbox on L1. * * @param address The sender address viewed on L2. * * @see * {@link applyL1ToL2Alias}. * * @example * * import { utils } from "zksync-ethers"; * * const l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC"; * const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); * // const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb" */ export function undoL1ToL2Alias(address: string): string { let result = BigInt(address) - BigInt(L1_TO_L2_ALIAS_OFFSET); if (result < 0n) { result += ADDRESS_MODULO; } return ethers.toBeHex(result, 20); } /** * Returns the data needed for correct initialization of an L1 token counterpart on L2. * * @param l1TokenAddress The token address on L1. * @param provider The client that is able to work with contracts on a read-write basis. * @returns The encoded bytes which contains token name, symbol and decimals. * * @example * * import { utils, types } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ethProvider = ethers.getDefaultProvider("sepolia"); * const tokenL1 = "0x56E69Fa1BB0d1402c89E3A4E3417882DeA6B14Be"; * * const calldata = await utils.getERC20DefaultBridgeData(tokenL1, ethProvider); */ export async function getERC20DefaultBridgeData( l1TokenAddress: string, provider: ethers.Provider ): Promise<string> { if (isAddressEq(l1TokenAddress, LEGACY_ETH_ADDRESS)) { l1TokenAddress = ETH_ADDRESS_IN_CONTRACTS; } const token = IERC20__factory.connect(l1TokenAddress, provider); const name = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) ? 'Ether' : await token.name(); const symbol = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) ? 'ETH' : await token.symbol(); const decimals = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) ? 18 : await token.decimals(); const coder = new AbiCoder(); const nameBytes = coder.encode(['string'], [name]); const symbolBytes = coder.encode(['string'], [symbol]); const decimalsBytes = coder.encode(['uint256'], [decimals]); return coder.encode( ['bytes', 'bytes', 'bytes'], [nameBytes, symbolBytes, decimalsBytes] ); } /** * Returns the calldata sent by an L1 ERC20 bridge to its L2 counterpart during token bridging. * * @param l1TokenAddress The token address on L1. * @param l1Sender The sender address on L1. * @param l2Receiver The recipient address on L2. * @param amount The gas fee for the number of tokens to bridge. * @param bridgeData Additional bridge data. * * @example * * import { Provider, utils, types } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const l1TokenAddress = "0x56E69Fa1BB0d1402c89E3A4E3417882DeA6B14Be"; * const l1Sender = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; * const l2Receiver = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; * const amount = 5; * const bridgeData = "0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000 * 0000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000 * 000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000 * 000000000000000000000000000000000000000000000000000000543726f776e0000000000000000000000000000000000000000000000000000 * 000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000 * 0000000000020000000000000000000000000000000000000000000000000000000000000000543726f776e000000000000000000000000000000 * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000 * 00000000000000000000000000000000012"; * * const calldata = await utils.getERC20BridgeCalldata( * l1TokenAddress, * l1Sender, * l2Receiver, * amount, * bridgeData * ); */ export async function getERC20BridgeCalldata( l1TokenAddress: string, l1Sender: string, l2Receiver: string, amount: BigNumberish, bridgeData: BytesLike ): Promise<string> { return L2_BRIDGE_ABI.encodeFunctionData('finalizeDeposit', [ l1Sender, l2Receiver, l1TokenAddress, amount, bridgeData, ]); } /** * Validates signatures from non-contract account addresses (EOA). * Provides similar functionality to `ethers.js` but returns `true` * if the validation process succeeds, otherwise returns `false`. * * Called from {@link isSignatureCorrect} for non-contract account addresses. * * @param address The address which signs the `msgHash`. * @param msgHash The hash of the message. * @param signature The Ethers signature. * * @example * * import { Wallet, utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ADDRESS = "<WALLET_ADDRESS>"; * const PRIVATE_KEY = "<WALLET_PRIVATE_KEY>"; * * const message = "Hello, world!"; * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); * // ethers.Wallet can be used as well * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); * * const isValidSignature = await utils.isECDSASignatureCorrect(ADDRESS, message, signature); * // isValidSignature = true */ function isECDSASignatureCorrect( address: string, msgHash: string, signature: SignatureLike ): boolean { try { return isAddressEq(address, ethers.recoverAddress(msgHash, signature)); } catch { // In case ECDSA signature verification has thrown an error, // we simply consider the signature as incorrect. return false; } } /** * Called from {@link isSignatureCorrect} for contract account addresses. * The function returns `true` if the validation process results * in the {@link EIP1271_MAGIC_VALUE}. * * @param provider The provider. * @param address The sender address. * @param msgHash The hash of the message. * @param signature The Ethers signature. * * @see * {@link isMessageSignatureCorrect} and {@link isTypedDataSignatureCorrect} to validate signatures. * * @example * * import { MultisigECDSASmartAccount, Provider, utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ADDRESS = "<MULTISIG ACCOUNT ADDRESS>"; * const PRIVATE_KEY1 = "<FIRST PRIVATE KEY>; * const PRIVATE_KEY2 = "<SECOND PRIVATE KEY>; * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const account = MultisigECDSASmartAccount.create( * ADDRESS, * [PRIVATE_KEY1, PRIVATE_KEY2], * provider * ); * * const message = "Hello World"; * const signature = await account.signMessage(message); * const magicValue = await utils.isEIP1271SignatureCorrect(provider, ADDRESS, msgHash, signature); * // magicValue = "0x1626ba7e" */ async function isEIP1271SignatureCorrect( provider: Provider, address: string, msgHash: string, signature: SignatureLike ): Promise<boolean> { const accountContract = new ethers.Contract( address, IERC1271.fragments, provider ); // This line may throw an exception if the contract does not implement the EIP1271 correctly. // But it may also throw an exception in case the internet connection is lost. // It is the caller's responsibility to handle the exception. const result = await accountContract.isValidSignature(msgHash, signature); return result === EIP1271_MAGIC_VALUE; } /** * Called from {@link isMessageSignatureCorrect} and {@link isTypedDataSignatureCorrect}. * Returns whether the account abstraction signature is correct. * Signature can be created using EIP1271 or ECDSA. * * @param provider The provider. * @param address The sender address. * @param msgHash The hash of the message. * @param signature The Ethers signature. * * @example * * import { Provider, utils } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ADDRESS = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; * const MSG_HASH = "<WALLET_PRIVATE_KEY>"; * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const isCorrect = await utils.isSignatureCorrect( * provider, * "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", * "0xb453bd4e271eed985cbab8231da609c4ce0a9cf1f763b6c1594e76315510e0f1", * "0xb04f825363596418c630425916f73447d636094a75e47b45e2eb59d8a6c7d5035355f03b903b84700376f0efa23f3b095815776c5c6daf2b371a0a61b5f703451b" * ); * // isCorrect = true */ async function isSignatureCorrect( provider: Provider, address: string, msgHash: string, signature: SignatureLike ): Promise<boolean> { const code = await provider.getCode(address); const isContractAccount = ethers.getBytes(code).length !== 0; if (!isContractAccount) { return isECDSASignatureCorrect(address, msgHash, signature); } else { return await isEIP1271SignatureCorrect( provider, address, msgHash, signature ); } } /** * Returns whether the account abstraction message signature is correct. * Signature can be created using EIP1271 or ECDSA. * * @param provider The provider. * @param address The sender address. * @param message The hash of the message. * @param signature The Ethers signature. * * @example * * import { Wallet, utils, Provider } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ADDRESS = "<WALLET_ADDRESS>"; * const PRIVATE_KEY = "<WALLET_PRIVATE_KEY>"; * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const message = "Hello, world!"; * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); * // ethers.Wallet can be used as well * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); * * const isValidSignature = await utils.isMessageSignatureCorrect(provider, ADDRESS, message, signature); * // isValidSignature = true */ export async function isMessageSignatureCorrect( provider: Provider, address: string, message: Uint8Array | string, signature: SignatureLike ): Promise<boolean> { const msgHash = ethers.hashMessage(message); return await isSignatureCorrect(provider, address, msgHash, signature); } /** * Returns whether the account abstraction EIP712 signature is correct. * * @param provider The provider. * @param address The sender address. * @param domain The domain data. * @param types A map of records pointing from field name to field type. * @param value A single record value. * @param signature The Ethers signature. * * @example * * import { Wallet, utils, Provider, EIP712Signer } from "zksync-ethers"; * import { ethers } from "ethers"; * * const ADDRESS = "<WALLET_ADDRESS>"; * const PRIVATE_KEY = "<WALLET_PRIVATE_KEY>"; * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const tx: types.TransactionRequest = { * type: 113, * chainId: 270, * from: ADDRESS, * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", * value: 7_000_000n, * }; * * const eip712Signer = new EIP712Signer( * new Wallet(PRIVATE_KEY), // or new ethers.Wallet(PRIVATE_KEY), * Number((await provider.getNetwork()).chainId) * ); * * const signature = await eip712Signer.sign(tx); * * const isValidSignature = await utils.isTypedDataSignatureCorrect( * provider, * ADDRESS, * await eip712Signer.getDomain(), * utils.EIP712_TYPES, * EIP712Signer.getSignInput(tx), * signature * ); * // isValidSignature = true */ export async function isTypedDataSignatureCorrect( provider: Provider, address: string, domain: ethers.TypedDataDomain, types: Record<string, Array<ethers.TypedDataField>>, value: Record<string, any>, signature: SignatureLike ): Promise<boolean> { const msgHash = ethers.TypedDataEncoder.hash(domain, types, value); return await isSignatureCorrect(provider, address, msgHash, signature); } /** * @deprecated In favor of {@link provider.estimateDefaultBridgeDepositL2Gas} * * Returns an estimation of the L2 gas required for token bridging via the default ERC20 bridge. * * @param providerL1 The Ethers provider for the L1 network. * @param providerL2 The ZKsync provider for the L2 network. * @param token The address of the token to be bridged. * @param amount The deposit amount. * @param to The recipient address on the L2 network. * @param from The sender address on the L1 network. * @param gasPerPubdataByte The current gas per byte of pubdata. * * @example * * import { Provider, utils, types } from "zksync-ethers"; * import { ethers } from "ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * const ethProvider = ethers.getDefaultProvider("sepolia"); * * const token = "0x0000000000000000000000000000000000000001"; * const amount = 5; * const to = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; * const from = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; * const gasPerPubdataByte = utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; * * const gas = await utils.estimateDefaultBridgeDepositL2Gas( * ethProvider, * provider, * token, * amount, * to, * from, * gasPerPubdataByte * ); * // gas = 355_704 */ export async function estimateDefaultBridgeDepositL2Gas( providerL1: ethers.Provider, providerL2: Provider, token: Address, amount: BigNumberish, to: Address, from?: Address, gasPerPubdataByte?: BigNumberish ): Promise<bigint> { // If the `from` address is not provided, we use a random address, because // due to storage slot aggregation, the gas estimation will depend on the address // and so estimation for the zero address may be smaller than for the sender. from ??= ethers.Wallet.createRandom().address; token = isAddressEq(token, LEGACY_ETH_ADDRESS) ? ETH_ADDRESS_IN_CONTRACTS : token; if (await providerL2.isBaseToken(token)) { return await providerL2.estimateL1ToL2Execute({ contractAddress: to, gasPerPubdataByte: gasPerPubdataByte, caller: from, calldata: '0x', l2Value: amount, }); } else { const bridgeAddresses = await providerL2.getDefaultBridgeAddresses(); const value = 0; const l1BridgeAddress = bridgeAddresses.sharedL1; const l2BridgeAddress = bridgeAddresses.sharedL2; const bridgeData = await getERC20DefaultBridgeData(token, providerL1); return await estimateCustomBridgeDepositL2Gas( providerL2, l1BridgeAddress, l2BridgeAddress, token, amount, to, bridgeData, from, gasPerPubdataByte, value ); } } /** * Scales the provided gas limit using a coefficient to ensure acceptance of L1->L2 transactions. * * This function adjusts the gas limit by multiplying it with a coefficient calculated from the * `L1_FEE_ESTIMATION_COEF_NUMERATOR` and `L1_FEE_ESTIMATION_COEF_DENOMINATOR` constants. * * @param gasLimit - The gas limit to be scaled. * * @example * * import { utils } from "zksync-ethers"; * * const scaledGasLimit = utils.scaleGasLimit(10_000); * // scaledGasLimit = 12_000 */ export function scaleGasLimit(gasLimit: bigint): bigint { return ( (gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR)) / BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR) ); } /** * @deprecated In favor of {@link provider.estimateCustomBridgeDepositL2Gas} * * Returns an estimation of the L2 gas required for token bridging via the custom ERC20 bridge. * * @param providerL2 The ZKsync provider for the L2 network. * @param l1BridgeAddress The address of the custom L1 bridge. * @param l2BridgeAddress The address of the custom L2 bridge. * @param token The address of the token to be bridged. * @param amount The deposit amount. * @param to The recipient address on the L2 network. * @param bridgeData Additional bridge data. * @param from The sender address on the L1 network. * @param gasPerPubdataByte The current gas per byte of pubdata. * @param l2Value The `msg.value` of L2 transaction. * * @example * * import { Provider, utils, types } from "zksync-ethers"; * * const provider = Provider.getDefaul