Running with L1
Simplest (no contract changes etc)
If you’re not doing any contract changes, and simply want to hook up to L1, start anvil with pre-created state.
This repo includes a pre-setup L1 state zkos-l1-state.json that can be loaded into anvil. The state was generated by zkstack init and essentially consists of all L1 contracts deployed and initialized with L2 genesis.
The state comes with some L1 priority transactions that were generated by the old genesis logic and are hence failing in the new implementation.
It also comes with a deposit transaction that makes 0x36615cf349d7f6344891b1e7ca7c72883f5dc049 into a rich account (>10k ETH) (to regenerate it, see “Regenerate zkos-l1-state.json` below)
Before you run an L1 node, make sure you have a 1.x version of anvil installed (see foundry guide). Then:
anvil --load-state zkos-l1-state.json --port 8545
...
Listening on 127.0.0.1:8545
...
Advanced (contract changes, multi setup etc)
If you want to have more custom setup (for example you did some changes in L1 contracts, or want to run multiple sequencers hooked up to the same L1).
The high level steps are:
- Start L1 (anvil)
- setup ecosystem and configs using zkstack cli from zksync-era
- update necessary config
- start sequencers
Start L1
Start local L1 – by running anvil.
Setup ecosystem and chain configs
Use zksync-os-integration branch fromzksync-era.
IMPORTANT: the contracts deployed will come from the zksync-era/contracts directory. So if you want to test any changes to contracts, you have to put them there.
Make sure that your zkstack was compiled from ‘main’ branch of era, and is relatively fresh (after September 10).
Run this from the directory above zksync-era.
mkdir zkstack-playground && cd zkstack-playground
zkstack ecosystem create --ecosystem-name local-v1 --l1-network localhost --chain-name era1 --chain-id 270 --prover-mode no-proofs --wallet-creation random --link-to-code ../../zksync-era --l1-batch-commit-data-generator-mode rollup --start-containers false --base-token-address 0x0000000000000000000000000000000000000001 --base-token-price-nominator 1 --base-token-price-denominator 1 --evm-emulator false
For validium, use --l1-batch-commit-data-generator-mode validium instead.
This will create a ‘local-v1’ ecosystem directory, with one chain ‘era1’.
Fund L1 accounts
Now we’re ready to compile contracts and deploy them to L1.
Before the step below, you might want to fund some of the wallet accounts above. If you’re running on local L1, you can use the script below. Do not forget to use different PRIVKEY in case you have initialized the anvil with a different mnemonic. If you’re running on sepolia, zkstack will tell you which accounts to fund.
RPC_URL=http://localhost:8545
PRIVKEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
find . -type f -name 'wallets.yaml' | while read -r file; do
echo "Processing $file …"
# extract all addresses (strips leading spaces and the "address:" prefix)
grep -E '^[[:space:]]*address:' "$file" \
| sed -E 's/^[[:space:]]*address:[[:space:]]*//' \
| while read -r addr; do
if [[ $addr =~ ^0x[0-9a-fA-F]{40}$ ]]; then
echo "→ Sending 10 ETH to $addr"
cast send "$addr" \
--value 10ether \
--private-key "$PRIVKEY" \
--rpc-url "$RPC_URL"
else
echo "⚠️ Skipping invalid address: '$addr'" >&2
fi
done
done
Deploy L1 contracts
cd local_v1
zkstack ecosystem init --deploy-paymaster=false --deploy-erc20=false --observability=false \
--deploy-ecosystem --l1-rpc-url=http://localhost:8545 --chain era1 --zksync-os
Start sequencer
After this, you can finally run the sequencer:
general_zkstack_cli_config_dir=../zkstack-playground/local_v1/chains/era1 cargo run --release
the general_zkstack_cli_config_dir config option will read the YAML files and set the proper addresses and private keys.
Alternatively, you need to set:
l1_sender_operator_commit_pkto the operator private key ofwallets.yamlofzkstacktool output,l1_sender_operator_prove_pkandl1_sender_operator_execute_pkto respective wallets fromwallets.yaml,l1_sender_bridgehub_addresstobridgehub_proxy_addrincontracts.yamlofzkstacktool output- (if running validium)
l1_sender_da_input_modetovalidium
Restarting
If you restart anvil, you have to repeat a subset of steps from above, to re-create the bridgehub contracts:
- fund the accounts (shell script)
- re-run ecosystem init
- you might also want to restart the sequencer - it will figure out the state on L1, and commit missing batches.
Regenerate L1 state
Note: There is an experimental tool that can run these commands for you. If it turns out to be useful, we might make it more permanent.
L1 state is checked in into this repo under zkos-l1-state.json. To regenerate it from scratch, run the following commands:
anvil -m "stuff slice staff easily soup parent arm payment cotton trade scatter struggle" --state zkos-l1-state.json
Note that we pass this mnemonic to have 0x36615cf349d7f6344891b1e7ca7c72883f5dc049 rich wallet - legacy from era.
Then deploy the contracts using legacy tooling (see above).
After that, add a deposit transaction to the state - integration and load tests expect that 0x36615cf349d7f6344891b1e7ca7c72883f5dc049 has L2 funds. For this, use generate-deposit tool in this repo.
Make sure to provide correct bridgehub_addres (you can find it in configs/contracts.yaml):
> cargo run --bin zksync_os_generate_deposit -- --bridgehub <BRIDGEHUB_ADDRESS>
L1 balance: 9879999865731420184000
Successfully submitted L1->L2 deposit tx with hash '0xb8544a2a9bc55713f1f94acf3711c23d07e02917f44885b05e20b13af1402283'
Process finished with exit code 0
Now stop anvil (ctrl+c) - the state will be saved to the file. Rerun it with --load-state zkos-l1-state.json (--load-state - not --state, otherwise it will be overwritten). Commit the new file in git.
Update values in L1SenderConfig:
bridgehub_address->bridgehub_proxy_addrincontracts.yamlofzkstacktool outputoperator_commit_pk->operator_private_keyinwallets.yamloperator_prove_pk,operator_execute_pk->prove_operatorandexecute_operaratorkeys from wallets.yaml
Running multiple chains
Create a new chain (era2)
zkstack ecosystem create --ecosystem-name local-v1 --l1-network localhost --chain-name era2 --chain-id 271 --prover-mode no-proofs --wallet-creation random --link-to-code ../../zksync-era --l1-batch-commit-data-generator-mode rollup --start-containers false --base-token-address 0x0000000000000000000000000000000000000001 --base-token-price-nominator 1 --base-token-price-denominator 1 --evm-emulator false
Make sure to fund the accounts again (see the script in the docs above).
Init new chain (deploying contacts etc):
zkstack chain init --deploy-paymaster=false \
--l1-rpc-url=http://localhost:8545 --chain era2 \
--server-db-url=postgres://invalid --server-db-name=invalid
And start the sequencer.
general_zkstack_cli_config_dir=../zkstack-playground/local_v1/chains/era2 cargo run --release