Ethersv5 API

Common Utilities

Essential utilities for building on ZKsync Era.

The utilities library contains essential utilities for building on ZKsync Era.

Use the library

Access the library by importing it into your scripts.

import { utils } from "zksync-ethers";

Constants

Interfaces

ZKsync Era main contract

The ABI for the ZkSync interface.

const ZKSYNC_MAIN_ABI = new ethers.Interface(IZkSyncABI);

Bridgehub

The ABI of the Bridgehub interface.

const BRIDGEHUB_ABI = new ethers.Interface(IBridgehubABI);

Contract deployer

The ABI for the IContractDeployer interface, which is utilized for deploying smart contracts.

const CONTRACT_DEPLOYER = new ethers.Interface(IContractDeployerABI);

L1 messenger

The ABI for the IL1Messenger interface, which is utilized for sending messages from the L2 to L1.

const L1_MESSENGER = new ethers.Interface(IL1MessengerABI);

IERC20

The ABI for the IERC20 interface, which is utilized for interacting with ERC20 tokens.

const IERC20 = new ethers.Interface(IERC20ABI);

IERC1271

The ABI for the IERC1271 interface, which is utilized for signature validation by contracts.

const IERC1271 = new ethers.Interface(IERC1271ABI);

L1 and L2 bridges

The ABI for the IL1Bridge interface, which is utilized for transferring ERC20 tokens from L1 to L2.

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.

const L2_BRIDGE_ABI = new ethers.Interface(IL2BridgeABI);

NonceHolder

The ABI for the INonceHolder interface, which is utilized for managing deployment nonces.

const NONCE_HOLDER_ABI = new ethers.Interface(INonceHolderABI);

L1 to L2 alias offset

Used for applying and undoing aliases on addresses during bridging from L1 to L2.

const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111";

Magic value

The value returned from isEIP1271SignatureCorrect to confirm signature correctness.

const EIP1271_MAGIC_VALUE = "0x1626ba7e";

EIP712 transaction type

Constant representing an EIP712 transaction type.

const EIP712_TX_TYPE = 0x71;

EIP712 structures

Collection of EIP712 structures with their types.

const EIP712_TYPES = {
  Transaction: [
    { name: "txType", type: "uint256" },
    { name: "from", type: "uint256" },
    { name: "to", type: "uint256" },
    { name: "gasLimit", type: "uint256" },
    { name: "gasPerPubdataByteLimit", type: "uint256" },
    { name: "maxFeePerGas", type: "uint256" },
    { name: "maxPriorityFeePerGas", type: "uint256" },
    { name: "paymaster", type: "uint256" },
    { name: "nonce", type: "uint256" },
    { name: "value", type: "uint256" },
    { name: "data", type: "bytes" },
    { name: "factoryDeps", type: "bytes32[]" },
    { name: "paymasterInput", type: "bytes" },
  ],
};

Priority op transaction on L2

Constant representing a priority transaction operation on L2.

const PRIORITY_OPERATION_L2_TX_TYPE = 0xff;

Max bytecode length

Used for ensuring bytecode length is not over the maximum allowed.

const MAX_BYTECODE_LEN_BYTES = ((1 << 16) - 1) * 32;

Useful addresses

ETH token

const ETH_ADDRESS = "0x0000000000000000000000000000000000000000";

Legacy ETH token

const LEGACY_ETH_ADDRESS = "0x0000000000000000000000000000000000000000";

ETH token in contracts

const ETH_ADDRESS_IN_CONTRACTS: Address = '0x0000000000000000000000000000000000000001';

ETH token alias on ZkSync Era

Deprecated in favor of L2_BASE_TOKEN_ADDRESS
const L2_ETH_TOKEN_ADDRESS = "0x000000000000000000000000000000000000800a";

L2 base token

const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a';

Bootloader

const BOOTLOADER_FORMAL_ADDRESS = "0x0000000000000000000000000000000000008001";

Contract deployer

const CONTRACT_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008006";

L1 messenger

const L1_MESSENGER_ADDRESS = "0x0000000000000000000000000000000000008008";

Nonce holder

const NONCE_HOLDER_ADDRESS = "0x0000000000000000000000000000000000008003";

Gas

DEFAULT_GAS_PER_PUBDATA_LIMIT

  • Use a large amount of gas per pubdata for signing on layer 2.
  • The amount ensures any reasonable limit is accepted.
The operator is NOT required to use the actual value and can use any value up to that signed by the user.
const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50000;

REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT

The current required gas per pubdata for L1->L2 transactions.

const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800;

Functions

applyL1ToL2Alias

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.

More info
  • 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).

Inputs

ParameterTypeDescription
addressstringContract address.
function applyL1ToL2Alias(address: string): string;

Example

const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb";
const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress);
// l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC"
See also undol1tol2alias.

checkBaseCost

Checks if the transaction's base cost is greater than the provided value, which covers the transaction's cost. Throws an error if it is not.

Inputs

ParameterTypeDescription
baseCostBigNumberishtransaction's base cost.
valueBigNumberishvalue which covers the transaction's cost.
async function checkBaseCost(baseCost: ethers.BigNumberish, value: ethers.BigNumberish | Promise<ethers.BigNumberish>): Promise<void>;

Example

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}`,
}

create2Address

Generates a future-proof contract address using salt plus bytecode which allows determination of an address before deployment.

The ZKsync Era implementation is slightly different from Ethereum.

Inputs

ParameterTypeDescription
senderAddressSender address.
bytecodeHashBytesLikeOutput from zkSolc.
saltBytesLikeRandomization element.
inputBytesLikeABI encoded constructor arguments.
function create2Address(sender: Address, bytecodeHash: BytesLike, salt: BytesLike, input: BytesLike): string;

Example

const address = utils.create2Address("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", "0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41", "0x01", "0x01");
// address = "0x29bac3E5E8FFE7415F97C956BFA106D70316ad50"
The prefix is equal to keccak256("zksyncCreate").

createAddress

Generates a contract address from deployer's account and nonce.

Inputs

ParameterTypeDescription
senderAddressSender address.
senderNonceBigNumberishSender nonce.
function createAddress(sender: Address, senderNonce: BigNumberish): string;

Example

const address = utils.createAddress("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", 1);
// address = "0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021"

eip712TxHash

Returns the hash of an EIP712 transaction.

Inputs

ParameterTypeDescription
transactionanyEIP-712 transaction.
ethSignature?EthereumSignatureECDSA signature of the transaction.
function eip712TxHash(transaction: any, ethSignature?: EthereumSignature): string;

estimateDefaultBridgeDepositL2Gas

Returns an estimation of L2 gas required for token bridging via the default ERC20 bridge.

Check out the default bridges documentation.

Inputs

ParameterTypeDescription
providerL1ProviderEthers provider.
providerL2ProviderZKsync provider.
tokenAddressToken address.
amountBigNumberishDeposit amount.
toAddressRecipient address.
from?AddressSender address.
gasPerPubdataByte?BigNumberishCurrent gas per byte of pubdata.
async function estimateDefaultBridgeDepositL2Gas(
  providerL1: ethers.providers.Provider,
  providerL2: Provider,
  token: Address,
  amount: BigNumberish,
  to: Address,
  from?: Address,
  gasPerPubdataByte?: BigNumberish
): Promise<bigint>;

getDeployedContracts

Returns a log containing details of all deployed contracts related to a transaction receipt parameter.

Inputs

ParameterTypeDescription
receiptTransactionReceiptTransaction receipt.
function getDeployedContracts(receipt: ethers.providers.TransactionReceipt): DeploymentInfo[];

getERC20BridgeCalldata

Returns the calldata sent by an L1 ERC20 bridge to its L2 counterpart during token-bridging.

Inputs

ParameterTypeDescription
l1TokenAddressAddressToken address on L1.
l1SenderAddressSender address on L1.
l2ReceiverAddressRecipient address on L2.
amountBigNumberishGas fee for the number of tokens to bridge.
bridgeDataBytesLikeData
async function getERC20BridgeCalldata(l1TokenAddress: string, l1Sender: string, l2Receiver: string, amount: BigNumberish, bridgeData: BytesLike): Promise<string>;

getHashedL2ToL1Msg

Returns a keccak encoded message with a given sender address and block number from the L1 messenger contract.

Inputs

ParameterTypeDescription
senderAddressThe sender of the message on L2.
msgBytesLikeEncoded message.
txNumberInBlocknumberIndex of the transaction in the block.
function getHashedL2ToL1Msg(sender: Address, msg: BytesLike, txNumberInBlock: number): string;

Example

const withdrawETHMessage = "0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600";
const withdrawETHMessageHash = utils.getHashedL2ToL1Msg("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", withdrawETHMessage, 0);
// withdrawETHMessageHash = "0xd8c80ecb64619e343f57c3b133c6c6d8dd0572dd3488f1ca3276c5b7fd3a938d"

getL2HashFromPriorityOp

Returns the hash of the L2 priority operation from a given transaction receipt and L2 address.

Inputs

ParameterTypeDescription
txReceiptTransactionReceiptReceipt of the L1 transaction.
zkSyncAddressAddressAddress of ZKsync Era main contract.
function getL2HashFromPriorityOp(txReceipt: ethers.providers.TransactionReceipt, zkSyncAddress: Address): string;

hashBytecode

Returns the hash of given bytecode.

Inputs

ParameterTypeDescription
bytecodeBytesLikeBytecode.
function hashBytecode(bytecode: ethers.BytesLike): Uint8Array;

Examples

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,
  ]),
);
 */

isAddressEq

Compares stringified addresses, taking into account the fact that addresses might be represented in different casing.

Inputs

ParameterTypeDescription
aAddressThe first address to compare.
bAddressThe second address to compare.
function isAddressEq(a: Address, b: Address): boolean

Example

import { Wallet, utils } from "zksync-ethers";

utils.isAddressEq("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", "0x36615cf349d7f6344891b1e7ca7c72883f5dc049")
// true

isECDSASignatureCorrect

Validates signatures from non-contract account addresses (EOA). Provides similar functionality in ethers.js but returns true if the validation process succeeds, otherwise returns false.

Called from isSignatureCorrect for non-contract account addresses.

Inputs

ParameterTypeDescription
addressstringAddress which signs msgHash.
msgHashAddressHash of the message.
signatureSignatureLikeEthers signature.
function isECDSASignatureCorrect(address: string, msgHash: string, signature: SignatureLike): boolean;

Examples

import { Wallet, utils } from "zksync-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
For development and testing, it is recommended to use burner wallets. Avoid using real private keys to prevent security risks.
See also isMessageSignatureCorrect() and isTypedDataSignatureCorrect() to validate signatures.

isEIP1271SignatureCorrect

Called from isSignatureCorrect for contract account addresses, the function returns true if the validation process results in the EIP1271_MAGIC_VALUE.

Inputs

ParameterTypeDescription
providerProviderProvider.
addressstringSender address.
msgHashstringThe hash of the message.
signatureSignatureLikeEthers signature.
async function isEIP1271SignatureCorrect(provider: Provider, address: string, msgHash: string, signature: SignatureLike): Promise<boolean>;
See also isMessageSignatureCorrect() and isTypedDataSignatureCorrect() to validate signatures.

isETH

Returns true if token represents ETH on L1 or L2.

Inputs

ParameterTypeDescription
tokenAddressThe token address.
function isETH(token: Address);

Example

const isL1ETH = utils.isETH(utils.ETH_ADDRESS); // true
const isL2ETH = utils.isETH(utils.L2_ETH_TOKEN_ADDRESS); // true

isMessageSignatureCorrect

Returns true if account abstraction signature is correct.

Inputs

ParameterTypeDescription
providerProviderProvider.
addressstringSender address.
messagestringThe hash of the message.
signatureSignatureLikeEthers signature.
async function isMessageSignatureCorrect(provider: Provider, address: string, message: ethers.Bytes | string, signature: SignatureLike): Promise<boolean>;

Example

import { Wallet, utils, Provider } from "zksync-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

isSignatureCorrect

Called from isMessageSignatureCorrect and isTypedDataSignatureCorrect. Returns true if account abstraction EIP712 signature is correct.

Inputs

ParameterTypeDescription
providerProviderProvider.
addressstringSender address.
msgHashstringThe hash of the message.
signatureSignatureLikeEthers signature.
async function isSignatureCorrect(provider: Provider, address: string, msgHash: string, signature: SignatureLike): Promise<boolean>;

isTypedDataSignatureCorrect

Returns true if account abstraction EIP712 signature is correct.

Inputs

ParameterTypeDescription
providerProviderProvider.
addressstringSender address.
domainTypedDataDomainDomain data.
typesMap<string, TypedDataField>Map of records pointing from field name to field type.
valueRecord<string, any>A single record value.
signatureSignatureLikeEthers signature.
async function isTypedDataSignatureCorrect(
  provider: Provider,
  address: string,
  domain: TypedDataDomain,
  types: Record<string, Array<TypedDataField>>,
  value: Record<string, any>,
  signature: SignatureLike
): Promise<boolean>;

Example

import { Wallet, utils, Provider, EIP712Signer } from "zksync-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: BigInt(7_000_000),
};

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

parseTransaction

Parses an EIP712 transaction from a payload.

Inputs

ParameterTypeDescription
payloadBytesLikePayload.
function parseTransaction(payload: ethers.BytesLike): ethers.Transaction;

Example

import { types } from "zksync-ethers";

const serializedTx =
  "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0";
const tx: types.TransactionLike = utils.parseEip712(serializedTx);
/*
tx = {
  type: 113,
  nonce: 0,
  maxPriorityFeePerGas: BigNumber.from(0),
  maxFeePerGas: BigNumber.from(0),
  gasLimit: BigNumber.from(0),
  to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618",
  value: BigNumber.from(1000000),
  data: "0x",
  chainId: BigNumber.from(270),
  from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
  customData: {
    gasPerPubdata: BigNumber.from(50000),
    factoryDeps: [],
    customSignature: "0x",
    paymasterParams: null,
  },
  hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee",
};
 */

serialize

Serializes an EIP712 transaction and include a signature if is provided. 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.

Inputs

ParameterTypeDescription
transactionTransactionRequestTransaction that needs to be serialized.
signature?SignatureLikeEthers signature that needs to be included in transactions.
function serialize(transaction: ethers.providers.TransactionRequest, signature?: SignatureLike);

Examples

Serialize EIP712 transaction without signature.

const serializedTx = utils.serializeEip712({ chainId: 270, from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" }, null);

// serializedTx = "0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"

Serialize EIP712 transaction with signature.

const signature = ethers.Signature.from("0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a");

const serializedTx = utils.serializeEip712(
  {
    chainId: 270,
    from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
    to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618",
    value: 1_000_000,
  },
  signature
);
// serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"

sleep

Common sleep function that pauses execution for a number of milliseconds.

Inputs

ParameterTypeDescription
millisnumberNumber of milliseconds.
function sleep(millis: number);

toJSON

Creates a JSON string from an object, including support for serializing bigint types.

function toJSON(object: any): string;

Inputs

ParameterTypeDescription
objectanyAny object.

Example

import { utils } from "zksync-ethers";

const json = utils.toJSON({gasLimit: 1_000n})
// {"gasLimit": 1000}

undoL1ToL2Alias

Converts and returns the msg.sender viewed on L2 to the address that submitted a transaction to the inbox on L1.

Inputs

ParameterTypeDescription
addressstringSender address.
function undoL1ToL2Alias(address: string): string;

Example

const l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC";
const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress);
// const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb"

Made with ❤️ by the ZKsync Community