V6

ZKsync Era Features

Explore ZKsync Era features and custom transactions

ZKsync Era offers several unique features compared to Ethereum, which require some 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 fields in transactions.
Everything inside customData in overrides is related to ZKsync(L2 gas, etc.).

EIP712 transactions

To accommodate these differences, ZKsync extends standard Ethereum transactions with new fields, creating what are known as EIP712 transactions. More details on EIP712 transactions can be found in the ZKstack transaction lifecycle.

Overrides in transactions

ethers.js can automatically find the best gasPrice, gasLimit, nonce, and other important fields for transactions. But sometimes, you need to set these values yourself. For example, you might want to set a lower gasPrice or use a specific nonce.

In such cases, you can use an Overrides object to provide these values. This object lets you set fields like gasPrice, gasLimit, nonce, etc.

For ZKsync-specific fields, you need to use the customData object within the overrides. 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.
This document focuses solely on how to pass these arguments to the SDK.

Example override

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

The ZKsync-ethers SDK includes a utility method for forming paymasterParams correctly, which is especially useful for built-in paymaster flows.

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,
  },
});

Made with ❤️ by the ZKsync Community