A local development stack for running a full Taiko L2 devnet β including a local Ethereum L1, deployed protocol contracts, and the Catalyst preconfirmation layer. Supports both Nethermind and taiko-geth as the L2 execution client.
- What this sets up
- Prerequisites
- Quick start β interactive mode
- Choosing an L2 execution client
- Optional add-ons
- Non-interactive / scripted usage
- Available deploy flags
- Service endpoints
- Tearing down the stack
- Common workflows
- Troubleshooting
- Directory layout
Running deploy-taiko-full.sh performs three phases in order:
| Phase | What happens |
|---|---|
| 1 β L1 devnet | Spins up a local Ethereum proof-of-stake network inside Kurtosis |
| 2 β L1 contracts | Deploys the Taiko Pacaya and Shasta protocol contracts onto that L1 |
| 3 β L2 stack | Starts the Taiko L2 execution client, drivers, and Catalyst preconf nodes via Docker Compose |
Everything runs locally on your machine. No real funds or external infrastructure are needed.
Install each tool before running the scripts.
Docker runs all the containers that make up the stack.
-
Mac / Windows: install Docker Desktop
-
Linux: install Docker Engine and add your user to the
dockergroup:sudo usermod -aG docker $USER # Log out and back in for the group change to take effect
Verify it works:
docker run --rm hello-worldKurtosis manages the L1 devnet as a self-contained "enclave".
# Mac (Homebrew)
brew install kurtosis-tech/tap/kurtosis-cli
# Linux / Windows WSL2
echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" \
| sudo tee /etc/apt/sources.list.d/kurtosis.list
sudo apt update && sudo apt install kurtosis-cliVerify:
kurtosis versionKurtosis requires Docker to already be running.
A command-line JSON processor used by the scripts.
# Mac
brew install jq
# Ubuntu / Debian
sudo apt install jq
# Fedora / RHEL
sudo dnf install jqUsually pre-installed. Check with curl --version. If missing:
# Ubuntu / Debian
sudo apt install curlUsed to initialise submodules.
git --version # already installed on most systems[ ] docker β docker version
[ ] kurtosis β kurtosis version
[ ] jq β jq --version
[ ] curl β curl --version
[ ] git β git --version
Clone the repository and enter the directory:
git clone <repo-url>
cd simple-taiko-node-nethermindCopy the example environment file:
cp .env.example .envYou do not need to edit
.envright now. The deploy script fills in contract addresses and other values automatically.
Make the scripts executable (first time only):
chmod +x deploy-taiko-full.sh remove-taiko-full.shRun the deploy script:
./deploy-taiko-full.shThe script walks you through a series of prompts:
- L2 execution client β choose
nethermind(default),taiko-gethoralethia-reth - Deployment environment β choose
local(default) orremote - L1 Blockscout explorer β optional block explorer for the L1 devnet (yes / no)
- Output mode β choose
silence(default, shows a spinner) ordebug(full logs) - L2 Blockscout explorer β optional block explorer for the L2 (yes / no)
- L2 transaction spammer β optional background traffic generator (yes / no)
Each prompt shows [0] as the default β just press Enter to accept it.
The full deploy takes 5β15 minutes on first run, mostly waiting for Kurtosis to pull Docker images.
Two execution clients are supported:
| Client | Description |
|---|---|
nethermind |
Nethermind β a C# / .NET Ethereum client with Taiko-specific changes |
geth (taiko-geth) |
A Go Ethereum fork maintained by the Taiko team |
alethia-reth |
Alethia RETH fork maintained by the Taiko team |
You can select the client interactively, or pass it as a flag:
./deploy-taiko-full.sh --client nethermind
./deploy-taiko-full.sh --client geth
./deploy-taiko-full.sh --client alethia-rethBoth clients expose the same L2 RPC port (8547), so you can switch between them for testing. Just run ./remove-taiko-full.sh first to tear down the existing stack completely before redeploying with the other client.
When you select Nethermind, the script automatically builds a Nethermind chainspec from the authoritative Taiko genesis data:
- Fetches the canonical genesis alloc from
taikoxyz/taiko-geth(core/taiko_genesis/internal.json) and saves it tostatic/genesis.json. This file contains only the account balances β it is not a full genesis. - Starts taiko-geth temporarily to read the full genesis block (
eth_getBlockByNumber("0x0")), extracting authoritative header fields:gasLimit,extraData,mixHash,nonce,timestamp,parentHash, andbaseFeePerGas. - Assembles a complete genesis.json by combining the alloc from step 1 with the header from step 2 and a synthesized chain config (chainId, fork block numbers, Shasta timestamp).
- Converts the complete genesis to a Nethermind chainspec at
static/taiko-shasta-chainspec.jsonusing agen2spec.jqtransform fetched fromNethermindEth/core-scripts. - Derives the genesis hash by starting Nethermind briefly and reading it from its startup logs.
You do not need to maintain the chainspec file manually β it is always regenerated from the canonical source at deploy time.
To use a different genesis source (e.g. a local fork), set the TAIKO_GENESIS_URL environment variable before running the script:
export TAIKO_GENESIS_URL=https://raw.githubusercontent.com/your-fork/taiko-geth/branch/core/taiko_genesis/internal.json
./deploy-taiko-full.sh --client nethermindBoth add-ons are opt-in and can be enabled in the interactive prompts or via flags.
A full block explorer for your L2 chain β browse blocks, transactions, and contracts in a browser.
Enable it:
./deploy-taiko-full.sh --l2-blockscout trueOnce running, open your browser at:
http://localhost:3001
Blockscout starts several containers (
blockscout-postgres,blockscout-verifier,blockscout,blockscout-frontend) so the first startup takes an extra minute or two.
Automatically sends a continuous stream of transactions to your L2 so you can observe the chain processing real load without any manual effort.
Enable it:
./deploy-taiko-full.sh --l2-spammer trueThe spammer dashboard is at:
http://localhost:8083
Two scenario types run by default:
- ETH transfers β simple value transfers between accounts
- ERC-20 activity β deploys test tokens and performs transfers
The scenario configuration is at static/spamoor/scenario-configs.yml. You can edit the throughput value to increase or decrease transactions per second.
Pass all options as flags to skip every prompt. Useful in CI or when you want a repeatable one-liner:
# Full deploy, nethermind, debug output
./deploy-taiko-full.sh --client nethermind --mode debug -f
# Full deploy, geth, with Blockscout and spammer, no prompts
./deploy-taiko-full.sh --client geth --l2-blockscout true --l2-spammer true -f
# Restart the L2 stack only (L1 and contracts already deployed)
./deploy-taiko-full.sh --skip-l1-devnet --skip-contracts -f
# Deploy with an existing L1, redeploy contracts and stack
./deploy-taiko-full.sh --skip-l1-devnet -fImportant: Do not use
--skip-contractsafter a full teardown. If you removed the L1 devnet, the old contract addresses no longer exist β you must redeploy them.
| Flag | Values | Default | Description |
|---|---|---|---|
--client |
nethermind | geth | alethia-reth |
from .env, or interactive |
L2 execution client |
--environment |
local | remote |
interactive | Whether the L1 devnet is local or remote |
--skip-l1-devnet |
β | false | Skip L1 devnet deployment (reuse running devnet) |
--skip-contracts |
β | false | Skip contract deployment (reuse existing deployments/) |
--l1-blockscout |
true | false |
interactive | Enable Blockscout inside the L1 devnet |
--l2-blockscout |
true | false |
interactive | Enable L2 Blockscout explorer |
--l2-spammer |
true | false |
interactive | Enable L2 transaction spammer |
--mode |
silence | debug |
interactive | Output verbosity |
-f, --force |
β | false | Skip all confirmation prompts, use defaults |
-h, --help |
β | β | Print help and exit |
After a successful deploy, the deploy script prints the exact URLs for your environment. The table below shows the defaults for a local deploy:
| Service | URL |
|---|---|
| L1 RPC (HTTP) | http://localhost:32003 |
| L1 RPC (WebSocket) | ws://localhost:32004 |
| L1 Beacon API | http://localhost:33001 |
| L2 RPC (HTTP) | http://localhost:8547 |
| L2 RPC (WebSocket) | ws://localhost:8548 |
| L2 Blockscout (if enabled) | http://localhost:3001 |
| L2 Spammer UI (if enabled) | http://localhost:8083 |
Remote deploy: when you run with
--environment remote, all URLs in the summary use the machine's IP address instead oflocalhost, so external clients can reach them.
Note for Docker containers: the
.envfile stores the Docker-internal form of L1 URLs (host.docker.internal:32003). These are used by the L2 containers to reach the L1. Thelocalhostform is only for access from your host terminal.
You can point any Ethereum-compatible wallet or tool (e.g. MetaMask, cast, ethers.js) at http://localhost:8547 to interact with your L2.
./remove-taiko-full.shThe script prompts you to choose which components to remove:
- L1 devnet (Kurtosis enclave) β removes the entire L1 network
- L2 stack containers β stops and removes all Docker containers
- Docker volumes β deletes persistent data (chain state, database files)
- Deployment files β removes the
deployments/directory (contract addresses) .envfile β resets environment configuration
Tip: Remove everything (options 1β5) when you want a completely clean slate for a fresh deploy. If you only want to restart the L2 containers without losing the L1 or contract state, select options 2 and 3 only.
# Remove everything without prompts
./remove-taiko-full.sh --force
# Remove only containers and volumes, keep L1 and contract addresses
./remove-taiko-full.sh --remove-l1-devnet false --remove-deployments false --remove-env false --force
# Full debug teardown to see what is happening
./remove-taiko-full.sh --mode debug| Flag | Values | Description |
|---|---|---|
--remove-l1-devnet |
true | false |
Remove the Kurtosis L1 enclave |
--remove-stack |
true | false |
Stop and remove L2 Docker containers |
--remove-volumes |
true | false |
Delete Docker volumes (chain data) |
--remove-deployments |
true | false |
Delete deployments/ directory |
--remove-env |
true | false |
Delete .env file |
--mode |
silence | debug |
Output verbosity |
-f, --force |
β | Skip confirmation prompts, remove all by default |
./remove-taiko-full.sh --force
./deploy-taiko-full.sh --client nethermind -f./remove-taiko-full.sh --remove-l1-devnet false --remove-deployments false --remove-env false --force
./deploy-taiko-full.sh --skip-l1-devnet --skip-contracts -f./remove-taiko-full.sh --force
./deploy-taiko-full.sh --client nethermind -f./deploy-taiko-full.sh --client nethermind --l2-blockscout true --l2-spammer true --mode debug -fdocker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"# L2 execution client (nethermind)
docker logs taiko-nethermind-1 --tail 50 -f
# L2 execution client (geth)
docker logs taiko-geth-1 --tail 50 -f
# Catalyst driver
docker logs taiko-driver-1 --tail 50 -fpermission denied while trying to connect to the Docker daemon
Your user is not in the docker group. Fix:
sudo usermod -aG docker $USERLog out and back in, then try again.
An enclave with name 'surge-devnet' already exists
A previous devnet was not cleaned up. Remove it:
kurtosis enclave rm surge-devnet --force
# Then re-run deploy
./deploy-taiko-full.shgenesis header hash mismatch, node: 0x3c23..., Taiko contract: 0x7a2c...
The genesis hash Nethermind computed from the chainspec does not match the hash the deployer registered on L1.
The deploy script always regenerates the chainspec from the authoritative taiko-geth source, so this should not happen during a fresh deploy. If you see it:
- Run a full teardown and redeploy β this forces the chainspec to be rebuilt:
./remove-taiko-full.sh --force
./deploy-taiko-full.sh --client nethermind -f- If the taiko-geth source has changed and you need a specific version, set
TAIKO_GENESIS_URLto a fixed commit URL:
export TAIKO_GENESIS_URL=https://raw.githubusercontent.com/taikoxyz/taiko-geth/<commit>/core/taiko_genesis/internal.json
./deploy-taiko-full.sh --client nethermind -fContainers may still be initialising. Wait 30 seconds and try:
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8547If it keeps failing, check the logs:
docker logs taiko-nethermind-1 --tail 100
# or
docker logs taiko-geth-1 --tail 100Blockscout has several containers and takes a couple of minutes to become ready. Check all four are running:
docker ps | grep blockscoutYou should see: blockscout-postgres, blockscout-verifier, blockscout, blockscout-frontend.
[WARNING] L1 execution layer not responding at http://localhost:32003 (may still be starting)
This warning appears immediately after the L1 devnet starts and is usually harmless β the L1 nodes need a few seconds to come up after Kurtosis finishes. The deploy continues regardless.
If the L2 stack later fails to connect to L1, the devnet may have failed to start cleanly. Check:
kurtosis enclave ls
kurtosis enclave inspect surge-devnetIf the enclave shows errors, run a full teardown and try again:
./remove-taiko-full.sh --force
./deploy-taiko-full.sh -fError during transition monitoring: Error: socket hang up
The fork-switch service monitors the L1 for the Shasta fork activation and then submits the activate transaction to L2. On first start it may attempt to connect before the L2 execution client's RPC is ready, which causes it to crash.
This is handled automatically β fork-switch has restart: on-failure in the Docker Compose configuration, so it retries and succeeds once the L2 RPC is up. A brief error in the logs on the first attempt is expected and harmless.
To verify it recovered:
docker logs fork-switch --tail 20A successful run ends with something like Shasta activation submitted and the container exits with code 0.
If you see an error and are not sure why, re-run with --mode debug for full output:
./deploy-taiko-full.sh --mode debugsimple-taiko-node-nethermind/
βββ deploy-taiko-full.sh # Main deploy script (L1 devnet, contracts, L2 stack)
βββ remove-taiko-full.sh # Teardown script
βββ helpers.sh # Shared functions sourced by both scripts:
β # URL helpers (to_localhost, configure_environment_urls)
β # Kurtosis helpers, env-file helpers, fork timestamp
βββ docker-compose.yml # L2 stack for taiko-geth (includes fork-switch)
βββ docker-compose-nethermind.yml # L2 stack for Nethermind (includes fork-switch)
βββ .env.example # Environment variable template
βββ configs/ # Patched ethereum-package configuration files
β βββ network_params.yaml # L1 network parameters (slot time, etc.)
β βββ ...
βββ static/
β βββ taiko-shasta-chainspec.json # Auto-generated Nethermind chainspec (git-ignored)
β βββ genesis.json # Auto-fetched from taikoxyz/taiko-geth (git-ignored)
β βββ spamoor/
β βββ scenario-configs.yml # Transaction spammer scenario definitions
βββ deployments/ # Auto-generated contract address files (git-ignored)
βββ ethereum-package/ # Kurtosis ethereum-package submodule