Deploy Surge
This guide walks you through spinning up a full Surge devnet on your local machine (or a VM): an L1 Ethereum devnet, all L1 protocol contracts, and the L2 execution stack.
Total time: ~15 minutes for mock prover. Real prover setup takes longer — mainly waiting for ZisK keys to install.
Before you start
Pick your prover mode
The biggest decision upfront is which prover to use. You can change this later, but it's easier to decide now.
- Mock prover (recommended for local dev)
- Real prover (ZisK)
The mock prover skips real ZK proof generation. A dummy contract accepts any signed proof, so blocks propose and finalize immediately without GPU hardware.
Use this if:
- You're just exploring or developing locally
- You don't have an NVIDIA GPU with 24 GB+ VRAM
- You want the fastest possible setup
No extra setup required — the script handles everything.
The real prover generates actual ZK proofs using the ZisK GPU backend. Proof time is ~10–17 seconds per block.
Use this if:
- You're running a production or staging deployment
- You have a dedicated GPU machine (RTX 3090 24 GB minimum; RTX 5090 / L40 / multi-GPU recommended)
You'll need to set up Raiko on the GPU machine before running the deploy script, then point the script at it with RAIKO_HOST_ZKVM=http://<prover-ip>:<prover-port>.
System requirements
Full local devnet (L1 + L2)
| Minimum | Recommended | |
|---|---|---|
| CPU | 8 cores | 16 cores |
| RAM | 16 GB | 32 GB |
| Disk | 80 GB SSD | 200 GB SSD |
| OS | Ubuntu 22.04+ / macOS 13+ | Ubuntu 24.04 |
L2 stack only (connecting to existing L1)
| Minimum | Recommended | |
|---|---|---|
| CPU | 4 cores | 8 cores |
| RAM | 8 GB | 16 GB |
| Disk | 50 GB SSD | 100 GB SSD |
| OS | Ubuntu 22.04+ / macOS 13+ | Ubuntu 24.04 |
Prover (separate machine, real prover only)
See Prover Setup — minimum 1x RTX 3090 (24 GB VRAM); multi-GPU L40 / RTX 5090 recommended for real-time-feel proof times.
Prerequisites
Install these tools before running the deploy script.
Docker Desktop
Download and install from docker.com. Make sure Docker is running before you continue.
On Linux, install Docker Engine + Compose plugin instead:
# Quick install on Ubuntu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # log out and back in after this
Foundry (includes cast)
curl -L https://foundry.paradigm.xyz | bash
foundryup
Kurtosis CLI
# macOS
brew install kurtosis-tech/tap/kurtosis-cli
# Linux / other — see https://docs.kurtosis.com/install
Standard tools
jq, curl, and bc are needed. They're usually already installed, but if not:
# Ubuntu/Debian
sudo apt install -y jq curl bc
# macOS
brew install jq
Step 1 — Clone the repo
git clone https://github.com/NethermindEth/simple-surge-node.git
cd simple-surge-node
git submodule update --init --recursive
cp .env.devnet .env
The .env.devnet file has pre-funded test keys — you don't need to set up any wallets.
Step 2 — Deploy
- Mock prover
- Real prover
- Interactive (step by step)
Run the script with all flags set so it doesn't stop to ask questions:
./deploy-surge-full.sh \
--environment devnet \
--deploy-devnet true \
--deployment local \
--stack-option 2 \
--mock-prover \
--mode silence \
--force
This starts a fresh L1 devnet, deploys all L1 contracts with a mock proof verifier, generates the L2 genesis, and brings up the full L2 stack (Nethermind + Driver + Catalyst).
Make sure Raiko is running and reachable at your prover machine's IP before running this (by default, prover port is set to be 8080). Check it's ready:
curl http://<prover-ip>:<prover-port>/guest_data
You should get back a JSON response with a zisk.batch_vkey field. If you get a connection error, Raiko isn't up yet — see Prover Setup.
RAIKO_HOST_ZKVM in .env, not via inline exportThe deploy script sources .env on every run, which overwrites any inline RAIKO_HOST_ZKVM=... you prepend to the command. Edit .env directly:
# In .env — use a URL reachable from inside the Catalyst container
RAIKO_HOST_ZKVM=http://host.docker.internal:8080 # prover on same machine
# or for a separate prover host:
# RAIKO_HOST_ZKVM=http://<prover-lan-ip>:8080
Catalyst runs inside a Docker container, so localhost on the host machine is not the same address Catalyst sees. host.docker.internal works because the compose file maps it to the host gateway. Using a plain localhost:8080 value will cause every proof request to fail and the L2 chain to reorg repeatedly.
--force defaults to real proverWith --force and no --mock-prover flag, the script auto-answers all three prover prompts (mock-vs-real → real, deploy provers → yes, run ZISK → yes). If you want mock prover instead, add --mock-prover explicitly.
Once Raiko is ready and .env is set:
./deploy-surge-full.sh \
--environment devnet \
--deploy-devnet true \
--deployment local \
--stack-option 2 \
--mode silence \
--force
After the L2 stack is up but before any L2 transaction (DEX deployment is the first one), the script verifies Raiko is reachable. If the check fails, the script aborts immediately — no DEX deploy, no L2 txs, no Catalyst proof requests against an unready prover.
- Mock prover — Raiko is brought up alongside the L2 stack via
--profile proverand exposed onlocalhost:8082. The check is a singleGET /healthping; the mock image returns{}for/guest_data(no real ZisK setup) so the deeper checks would loop forever and add no value — mock proofs are instant anyway. - Real prover — Raiko runs on a separate machine. The check uses
$RAIKO_HOST_ZKVM(transforminghost.docker.internal→localhostfor host-side reachability). It runs three sequential probes against the prover URL:GET /health(basic liveness, must return 200) — 90 second budget: if Raiko isn't returning /health that fast, it isn't running at all on the prover VM. The script exits here with a checklist (container running? port 8080 open? configs scp'd + raiko-zk restarted with--force-recreate?).GET /guest_data(must return azisk.batch_vkey— first call takes 4–5 min on cold start) — full 30-minute budget. If Raiko answers/guest_databut never produces a vkey, the error explicitly calls out the most common cause: Raiko on the prover VM is still running against the default chain spec because the simple-surge-node-generated configs were never synced.POST /v3/proof/batch/realtimewithsources: [](status-poll mode — verifies the proof endpoint's request handler is alive without triggering proof generation).
The check is skipped only for --stack-option 0 (no L2 stack at all).
This does not fully warm the ZisK proofman pipeline on real prover. The first real proof request from Catalyst still triggers proofman init (~16 min on a single L40, ~1 min on multi-GPU).
If you're running Raiko and the L2 stack on the same VM, add host.docker.internal to your host's /etc/hosts so the script's host-side curl to Raiko also resolves:
echo "127.0.0.1 host.docker.internal" | sudo tee -a /etc/hosts
If Raiko runs on a separate machine from the L2 stack, three things differ from the docs' default flow:
-
RAIKO_HOST_ZKVMmust be the prover's reachable IP, nothost.docker.internal. SetRAIKO_HOST_ZKVM=http://<prover-public-or-lan-ip>:8080insimple-surge-node/.env. -
Open inbound TCP/8080 on the prover VM so the L2 host's containers can reach it. Verify from the L2 host:
curl http://<prover-ip>:8080/guest_datashould return the vkey JSON. -
The chain spec doesn't auto-sync.
deploy-zisk-prover.sh --surge-dir ../simple-surge-nodeassumes both repos are on the same machine. For two-VM, afterdeploy-surge-full.shgenerates the chain spec, copy it to the prover and restart Raiko:# From the L2 host:
scp simple-surge-node/configs/chain_spec_list.json \
simple-surge-node/configs/config.json \
<prover-host>:/path/to/raiko/host/config/devnet/
# Then on the prover host:
docker compose -f docker/docker-compose-zk.yml restartCatalyst will start sending proof requests as soon as the L2 stack is up — restart Raiko before
deploy-surge-full.shreaches L2 stack startup, otherwise the first proof request hits a Raiko still configured against the default chain spec and either errors or returns a vkey for the wrong chain.
The deploy script's DEX setup (Setting L2 vault on L1 vault...) sends L2 transactions and waits for them to confirm. If the L2 chain hasn't progressed past block 1 (e.g., because Catalyst can't propose, or proofs aren't returning, or operator isn't registered), these txs time out and DEX verification fails with transaction was not confirmed within the timeout.
Before troubleshooting DEX errors, check:
curl -s -X POST -H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8547
If the result is 0x0 or 0x1 and not advancing, fix the L2 production path first. The DEX errors are downstream symptoms.
If you prefer to answer each prompt manually:
./deploy-surge-full.sh
| Step | Prompt | What to pick |
|---|---|---|
| 1 | Environment | devnet |
| 2 | Deployment type | local (same machine) or remote (VM) |
| 3 | Deploy new L1 devnet? | true for a fresh devnet |
| 4 | Execution mode | silence (progress bars) or debug (full logs) |
| 5 | Mock or real prover | 0 mock / 1 real |
| 6 | L2 stack option | 2 — Driver + Catalyst (or 0 to skip; see below) |
L2 stack options
| Option | Components |
|---|---|
0 | None. Skip the L2 stack, just verify the external L2 RPC is reachable. Dev-only — use when you're running your own L2 node and just want to deploy protocol + DEX contracts against it. |
1 | Driver only |
2 | Driver + Catalyst (default) |
3 | Driver + Catalyst + Spammer |
The script saves a lock file after each phase. If something fails or you press Ctrl+C, just re-run the same command — completed phases are skipped.
--force does--force skips every interactive prompt and uses sensible defaults: real prover (unless --mock-prover is set), deploy provers, register ZISK vkey, and suppresses the "Start a new deployment?" confirmation when locks exist.
What gets deployed
L1 devnet (Kurtosis / ethereum-package)
└── Execution client + beacon chain
L1 protocol contracts
├── RealTimeInbox
├── SurgeVerifier
├── Bridge + SignalService
├── ERC20/721/1155 vaults
├── Multicall + UserOpsSubmitter
└── ProofVerifierDummy ← mock prover only
L2 genesis + chainspec
L2 stack (Docker Compose)
├── l2-nethermind-execution-client
├── l2-taiko-consensus-client (Driver)
├── web3signer-l1, web3signer-l2
└── l2-catalyst-node (Catalyst)
Step 3 — Verify it's working
After the script finishes, it prints a summary table with all endpoints. You can also check manually:
# Are all L2 containers running?
docker compose ps
# Does the L2 RPC respond?
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8547
Default service endpoints:
| Service | URL |
|---|---|
| L1 RPC | http://localhost:32003 |
| L1 WebSocket | ws://localhost:32004 |
| L1 Beacon | http://localhost:33001 |
| L2 RPC | http://localhost:8547 |
| L2 WebSocket | ws://localhost:8548 |
| L2 Blockscout | http://localhost:3001 |
| DEX UI | http://localhost:5173 |
On a remote VM, replace localhost with the machine's public IP.
Once Catalyst starts proposing blocks, you'll see new L2 blocks appear. With the mock prover they finalize immediately. With the real prover, finalization follows proof generation (~10–17 seconds).
Troubleshooting
L1 health check fails right after devnet start
Kurtosis is still assigning ports. Wait 15–30 seconds and re-run.
cast not found
curl -L https://foundry.paradigm.xyz | bash && foundryup
Kurtosis enclave already exists
The script will offer to remove it. Force-remove manually:
kurtosis enclave rm surge-devnet --force
Blocks propose but never finalize (mock prover)
ProofVerifierDummy requires proofs signed by MOCK_PROOF_SIGNER. Confirm --mock-prover was passed (or MOCK_PROOF_MODE=true was set) during L1 deployment and that the key in .env matches.
Real prover: ZISK guest data is missing
Raiko must be running before you deploy. Check:
curl http://<prover-ip>:<prover-port>/guest_data
Collect diagnostic logs
If something goes wrong and you want to file a bug report or share logs:
# Full snapshot: L1 enclave + all L2 containers
./collect-devnet-logs.sh
# Just the last 30 minutes
./collect-devnet-logs.sh --since 30m
# L1 only (kurtosis enclave dump)
./collect-devnet-logs.sh --l1-only
# L2 only (docker logs per container)
./collect-devnet-logs.sh --l2-only
Logs are saved to ./logs/snapshot-YYYYMMDD-HHMMSS/.
Restart or redeploy
Restart L2 containers only (no redeploy)
Stop and restart just the L2 stack, keeping the L1 devnet and all deployment artifacts intact:
# Stop L2 containers
./remove-surge-full.sh \
--remove-l1-devnet false \
--remove-l2-stack true \
--remove-data false \
--remove-configs false \
--force
# Start L2 stack again against the existing deployment
./deploy-surge-full.sh \
--environment devnet \
--deploy-devnet false \
--deployment local \
--stack-option 2 \
--force
Redeploy L2 from scratch (keep L1 devnet)
Wipe everything except the Kurtosis enclave and redeploy L1 contracts + L2 from scratch:
# Wipe L2 containers, data, and all deployment files
./remove-surge-full.sh \
--remove-l1-devnet false \
--remove-l2-stack true \
--remove-data true \
--remove-configs true \
--force
# Redeploy against the existing L1 enclave
./deploy-surge-full.sh \
--environment devnet \
--deploy-devnet false \
--deployment local \
--stack-option 2 \
--force
Cleanup
# Remove everything except .env
./remove-surge-full.sh --force
# Remove everything including .env
./remove-surge-full.sh --force --remove-env true
Next steps
- Set up the ZisK prover if you want real ZK proofs
- Deploy a dApp on your Surge network
Our deployment (for reference)
| Component | Hardware | Notes |
|---|---|---|
| L1 | Gnosis mainnet | QuickNode RPC |
| L2 stack | Standard VM | Nethermind, Driver, Catalyst |
| Prover | NVIDIA RTX 5090 (×8) | ~10–11s proofs |
| Secondary prover | L40s cluster | ~13–14s proofs |