Building Smart Accounts

Discover the process of building custom smart accounts.

To build custom accounts on our platform, developers must implement specific interfaces and follow our recommended best practices for account deployment and management.

Interface Implementation

Every custom account should implement the IAccount interface. You can find an example of a typical account implementation, resembling standard Externally Owned Accounts (EOA) on Ethereum, in the DefaultAccount.sol on GitHub.

This implementation returns an empty value when called by an external address, which may not be the desired behavior for your custom account.

EIP1271 Integration

For smart wallets, we highly encourage the implementation of the EIP1271 signature-validation scheme. This standard is endorsed by the ZKsync team and is integral to our signature-verification library.

Deployment Process

Deploying account logic follows a process similar to deploying a standard smart contract. However, to distinguish smart contracts that are not intended to be treated as accounts, use the createAccount/create2Account methods of the deployer system contract instead of create/create2.

Example Using zksync-ethers SDK (v5)

import { ContractFactory } from "zksync-ethers";

const contractFactory = new ContractFactory(abi, bytecode, initiator, "createAccount");
const aa = await contractFactory.deploy(...args);
await aa.deployed();

Verification Step Limitations

Currently, the verification rules for custom accounts are not fully enforced, which might change in the future:
  • Accounts must only interact with slots that belong to them.
  • Context variables (e.g., block.number) are prohibited in account logic.
  • Accounts must increment the nonce by 1 to maintain hash collision resistance.

These limitations are not yet enforceable at the circuit/VM level and do not apply to L1->L2 transactions.

Nonce Management

Both transaction and deployment nonces are consolidated within the NonceHolder system contract for optimization. Use the incrementMinNonceIfEquals function to safely increment your account's nonce.

Sending Transactions

Currently, only EIP712 formatted transactions are supported for sending from custom accounts. Transactions must specify the from field as the account's address and include a customSignature in the customData.

Example Transaction Submission

import { utils } from "zksync-ethers";

// Here, `tx` is a `TransactionRequest` object from `zksync-ethers` SDK.
// `zksyncProvider` is the `Provider` object from `zksync-ethers` SDK connected to the ZKSync network.
tx.from = aaAddress;
tx.customData = {
  customSignature: aaSignature,
const serializedTx = utils.serialize({ ...tx });

const sentTx = await zksyncProvider.sendTransaction(serializedTx);

Made with ❤️ by the ZKsync Community