var img = document.createElement('img'); img.src = "https://nethermind.matomo.cloud//piwik.php?idsite=6&rec=1&url=https://www.surge.wtf" + location.pathname; img.style = "border:0"; img.alt = "tracker"; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(img,s);
Skip to main content

Deploy on an Existing L1

This guide covers deploying Surge on a public L1 network — Sepolia, Holesky (Hoodi), Gnosis Chain, or Ethereum mainnet — instead of a local Kurtosis devnet.

The steps are almost identical to the local devnet guide, with two key differences:

  1. You skip the "deploy new L1 devnet" step — you already have one.
  2. You need funded wallets on that L1 for gas.

Supported L1 networks​

NetworkChain IDTypeNotes
Ethereum Sepolia11155111TestnetMost common for testing — free ETH from faucets
Ethereum Hoodi560048TestnetLarger validator set, good for realistic testing
Gnosis Chain100MainnetWhat Surge itself uses in production
Ethereum Mainnet1MainnetReal money — make sure you know what you're doing

Prerequisites​

Everything from the local devnet guide, plus:

Funded wallets​

You need three wallets with ETH (or xDAI on Gnosis) for gas. Each plays a specific role:

VariableRoleHow much do you need?
PRIVATE_KEYDeploys all L1 contracts~0.1–0.5 ETH (most gas)
OPERATOR_PRIVATE_KEYRuns Driver + Catalyst~0.05 ETH (ongoing L1 txs)
SUBMITTER_PRIVATE_KEYSubmits batches to L1~0.05 ETH (ongoing L1 txs)

You can use the same wallet for all three on a testnet. On mainnet, keep them separate.

Testnet faucets:

L1 RPC endpoints​

You need three endpoints from your L1 provider:

EndpointWhat it's for
HTTP RPCContract deployment + reads
WebSocket RPCEvent subscriptions
Beacon APIL1 slot/epoch data

Step 1 — Clone and configure​

git clone https://github.com/NethermindEth/simple-surge-node.git
cd simple-surge-node
git submodule update --init --recursive
cp .env.devnet .env

Now edit .env with your L1 details:

# L1 network
L1_CHAIN_ID=11155111
L1_ENDPOINT_HTTP=https://some-rpc-providers/<YOUR_KEY>
L1_ENDPOINT_WS=wss://some-rpc-providers/<YOUR_KEY>
L1_BEACON_HTTP=https://some-rpc-providers/<YOUR_KEY>

# Your funded wallets (replace with real private keys)
PRIVATE_KEY=
OPERATOR_PRIVATE_KEY=
SUBMITTER_PRIVATE_KEY=
PUBLIC_KEY=
OPERATOR_PUBLIC_KEY=
SUBMITTER_PUBLIC_KEY=

# L2 chain ID — pick any unused chain ID as long as it doesn't crash with your L1 chain ID
L2_CHAIN_ID=763374

Step 2 — Deploy​

Deploying on a public L1 always uses the real ZK prover — mock proofs are for local testing only and aren't appropriate for a network anyone else will interact with.

Make sure Raiko is running and reachable before continuing (by default, prover port is 8080). Check:

curl http://<prover-ip>:8080/guest_data

You should get a JSON response with a zisk.batch_vkey field. If you get a connection error, see Prover Setup first.

Add the prover endpoint to .env:

RAIKO_HOST_ZKVM=http://<prover-ip>:8080
note

Don't pass RAIKO_HOST_ZKVM inline — deploy-surge-full.sh sources .env on every run and inline exports get overridden.

Once Raiko is ready and .env is set:

./deploy-surge-full.sh \
--environment devnet \
--deploy-devnet false \
--deployment local \
--stack-option 2 \
--force

The script deploys all L1 protocol contracts to your chosen chain, generates the L2 genesis, and starts the L2 stack. The Cross-Chain DEX deploys a fresh SwapToken by default — set SWAP_TOKEN in .env to an existing ERC20 address (e.g. USDC) to use a real token instead.


Step 3 — Verify​

Check your L2 RPC is responding:

curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8547

You can also confirm the contracts deployed by checking your deployer address's transaction history on the L1 block explorer (Etherscan for Sepolia/Mainnet, Gnosisscan for Gnosis).


Remote VM​

If you're running the L2 stack on a remote machine (common for public L1 deployments), use --deployment remote. The script will use the machine's public IP for endpoint URLs instead of localhost. With RAIKO_HOST_ZKVM already set in .env:

./deploy-surge-full.sh \
--environment devnet \
--deploy-devnet false \
--deployment remote \
--stack-option 2 \
--force

Make sure these ports are open in your firewall:

PortService
8547L2 RPC (HTTP)
8548L2 RPC (WebSocket)
5173DEX UI
30313L2 P2P discovery

Troubleshooting​

"insufficient funds" during deployment​

Your deployer wallet doesn't have enough ETH. The script runs ~15–20 transactions — top up and re-run. Completed steps are skipped automatically.

L1 RPC rate limits​

Public RPC endpoints (like the free Alchemy tier) can hit rate limits during deployment. If you see 429 errors in debug mode, switch to a paid plan or a different provider.

Beacon API errors on Gnosis​

Gnosis doesn't have a beacon chain in the same sense as Ethereum. Set L1_BEACON_HTTP to any reachable URL — it's not used for Gnosis deployments but the field must be set.

Wrong chain ID in genesis​

If you change L2_CHAIN_ID after genesis was generated, delete deployment/surge_genesis.json and re-run. A stale genesis with the wrong chain ID will cause the L2 node to reject all blocks.


Cleanup​

# Remove L2 stack and deployment artifacts (L1 contracts stay deployed)
./remove-surge-full.sh \
--remove-l1-devnet false \
--remove-l2-stack true \
--remove-data true \
--remove-configs true \
--force

Note: Contracts deployed to a public L1 can't be "removed" — they stay on-chain. This command only cleans up your local Docker containers and deployment files.