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
- 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 = {
...tx.customData,
customSignature: aaSignature,
};
const serializedTx = utils.serialize({ ...tx });
const sentTx = await zksyncProvider.sendTransaction(serializedTx);