This tutorial demonstrates how to send an L1 transaction that triggers a state change on an L2 ZKsync contract using the Bridgehub. We'll deploy an
AccessKey contract on a local anvil node (L1) and a *Vault
contract on a local anvil-zksync node (L2). The L1 contract acts as an access gate, unlocking the vault on L2.
- How to deploy smart contracts on both L1 and L2 locally using
anvil-zksync - How to send L1 → L2 messages
-
Install Foundry-ZKsync
Install
foundry-zksyncwhich ships withanvil-zksynchere. -
Install dependencies
Navigate to
l1-access:forge soldeer install
Navigate to
l2-vault:forge soldeer install
-
Configure
.envPRIVATE_KEY=0x... ACCESS_KEY_ADDRESS=0x... # deployed L1 AccessKey contract VAULT_ADDRESS=0x... # deployed L2 Vault contract BRIDGE_HUB_ADDRESS=0x... # ZKsync Bridgehub on L1 L2_CHAIN_ID=260 # e.g. anvil-zksync chainID (Default 260)
Run:
source .envTo fetch the BridgeHub address:
curl --request POST \ --url http://localhost:8011 \ --header 'Content-Type: application/json' \ --data '{ "jsonrpc": "2.0", "id": 1, "method": "zks_getBridgehubContract", "params": [] }'
forge script script/DeployAccessKey.s.sol:DeployAccessKey \
--rpc-url anvil-zksync-l1 \
--broadcast \
--private-key $PRIVATE_KEYMake sure to alias the AccessKey address when deploying to L2:
forge script script/DeployVault.s.sol:DeployVault \
--rpc-url anvil-zksync-l2 \
--broadcast \
--private-key $PRIVATE_KEYThis script sends the L1→L2 message via Bridgehub:
forge script script/UnlockVaultFromL1.s.sol:UnlockVaultFromL1 \
--rpc-url anvil-zksync-l1 \
--broadcast \
--private-key $PRIVATE_KEYIt:
- Encodes the
unlock()call to L2 - Estimates base cost using
l2TransactionBaseCost - Sends the message via
AccessKey.unlockVaultOnL2()using Bridgehub