EthersJS

ZKsync Era Features

Explore ZKsync Era features and custom transactions

ZKsync Era offers several unique features compared to Ethereum, requiring specific handling. This guide will help you understand these differences and how to work with them using the zksync-ethers library.

Key differences

  • Account abstraction: ZKsync supports account abstraction, allowing accounts to have custom validation logic and enabling paymaster support.
  • Deployment transactions: Contracts on ZKsync require bytecode to be passed in a separate field during deployment.
  • Fee system: The fee system in ZKsync differs from Ethereum's, necessitating additional transaction fields.

These differences require extending standard Ethereum transactions with new custom fields. Such extended transactions are called EIP712 transactions since EIP712 is used to sign them. More details on EIP712 transactions can be found in the ZKstack transaction lifecycle.

Overrides in transactions

ethers.js has a notion of overrides. For any on-chain transaction, ethers.js finds the optimal gasPrice, gasLimit, nonce, and other important fields under the hood. But sometimes, you may need to explicitly provide these values (e.g., setting a smaller gasPrice or signing a transaction with a future nonce).

In such cases, you can use an Overrides object as the last parameter. This object lets you set fields like gasPrice, gasLimit, nonce, etc.

To make the SDK as flexible as possible, zksync-ethers uses a customData object within the overrides to supply ZKsync-specific fields. Here's how you can do it:

const tx = {
  to: '0x...',
  value: ethers.utils.parseEther('0.01'),
  overrides: {
    gasPrice: ethers.utils.parseUnits('10', 'gwei'),
    gasLimit: 21000,
    nonce: 0,
    customData: {
      gasPerPubdata: BigNumberish,
      factoryDeps: BytesLike[],
      customSignature: BytesLike,
      paymasterParams: {
        paymaster: Address,
        paymasterInput: BytesLike
      }
    }
  }
};

CustomData fields

  • gasPerPubdata: Specifies L2 gas per published byte.
  • factoryDeps: Array of contract bytecodes for deployment.
  • customSignature: Custom signature for the transaction.
  • paymasterParams: Parameters for using a paymaster.
Everything inside customData in overrides is related to ZKsync (L2 gas, etc.).

Example overrides

To deploy a contract with specific bytecode and a gas limit for published data:

{
    customData: {
        gasPerPubdata: "100",
        factoryDeps: ["0xcde...12"],
    }
}

To use a custom signature and paymaster:

{
    customData: {
        customSignature: "0x123456",
        paymasterParams: {
            paymaster: "0x8e1DC7E4Bb15927E76a854a92Bf8053761501fdC",
            paymasterInput: "0x8c5a3445"
        }
    }
}

Encoding paymaster params

While the paymaster feature by itself does not impose any limitations on values of the paymasterInput, the Matter Labs team endorses certain types of paymaster flows that are processable by EOAs.

The ZKsync SDK provides a utility method that can be used to get the correctly formed paymasterParams object:

const paymasterParams = utils.getPaymasterParams(testnetPaymaster, {
  type: "ApprovalBased",
  token,
  minimalAllowance: fee,
  innerInput: new Uint8Array(),
});

Using a paymaster with a contract method

Here is how you can call the setGreeting method of a Contract object greeter and pay fees with a testnet paymaster:

const greeting = "a new greeting";
const tx = await greeter.populateTransaction.setGreeting(greeting);
const gasPrice = await sender.provider.getGasPrice();
const gasLimit = await greeter.estimateGas.setGreeting(greeting);
const fee = gasPrice.mul(gasLimit);

const paymasterParams = utils.getPaymasterParams(testnetPaymaster, {
  type: "ApprovalBased",
  token,
  minimalAllowance: fee,
  innerInput: new Uint8Array(),
});

const sentTx = await sender.sendTransaction({
  ...tx,
  maxFeePerGas: gasPrice,
  maxPriorityFeePerGas: 0n,
  gasLimit,
  customData: {
    gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
    paymasterParams,
  },
});
This document focuses solely on how to pass these arguments to the SDK.

Made with ❤️ by the ZKsync Community