Skip to main content

Overview

For chains that are part of the public superchain-registry, the standard kona-client absolute prestate published in standard-prestates.toml already embeds your chain configuration. No custom build is needed. This tutorial is for the rarer case: a partner chain whose RollupConfig is not yet in the public registry but which still needs to run permissionless fault proofs (cannon-kona game type). In that case you must build a custom kona-client that embeds your chain’s configuration, generate the absolute prestate hash from that build, and host the resulting binary at a URL op-challenger can fetch.
Most chains are in the Superchain Registry and should use the standard prestate. Only follow this tutorial if your chain is not yet in the registry. Once the chain is added to the public registry, future releases will cover it via the standard prestate and you can stop maintaining a custom build.

Prerequisites

Before starting, ensure you have:
  • Docker running
  • just installed
  • Your chain’s rollup.json, L2 genesis, and deployment artifacts (typically produced by op-deployer)

How kona-client picks up custom chain configurations

kona-client reads its embedded chain registry from the kona-registry crate. At build time, the crate’s build.rs can merge additional chains on top of the canonical Superchain Registry snapshot. The merge is gated by two environment variables:
KONA_CUSTOM_CONFIGS=true
KONA_CUSTOM_CONFIGS_DIR=/absolute/path/to/your/configs
When set, the build script reads two files from KONA_CUSTOM_CONFIGS_DIR:
  • chainList.json — light-weight Chain entries for your custom chains
  • configs.json — full ChainConfig + RollupConfig entries grouped under a Superchain bucket
The full schema and an end-to-end example are documented in the kona-registry README. The compiled kona-client binary embeds the merged registry via include_str!(), so the resulting absolute prestate hash differs from the standard one. Any honest challenger participating in your chain’s dispute games will need to run this same binary.

Generating the custom prestate

1

Check out a kona-node release tag

Use the same kona-node/v<VERSION> tag that your chain’s nodes are running. The native KONA_CUSTOM_CONFIGS_DIR support documented below requires kona-node/v1.5.2 or later; on older tags, additional justfile patches are needed.
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
git checkout kona-node/v<VERSION>
2

Stage your chain configuration

Create a directory for your custom configs:
mkdir -p rust/kona/crates/protocol/registry/etc/custom-configs/<chain-name>
Add two files inside it — chainList.json (lightweight chain entry) and configs.json (full rollup config). Replace every <PLACEHOLDER> with your chain’s value. Notes:
  • <CHAIN_GROUP>: mainnet if your L1 is Ethereum mainnet, sepolia if your L1 is Sepolia. The bucket groups chains by L1 network, not by governance — your custom-chain status is expressed via governedByOptimism: false and superchainLevel: 0.
  • faultProofs.status: permissionless for chains running the cannon-kona game type publicly, permissioned for chains that only allow whitelisted challengers.
[
  {
    "name": "<YOUR_CHAIN_NAME>",
    "identifier": "<CHAIN_GROUP>/<YOUR_CHAIN_NAME>",
    "chainId": <YOUR_L2_CHAIN_ID>,
    "rpc": [],
    "explorers": [],
    "superchainLevel": 0,
    "governedByOptimism": false,
    "dataAvailabilityType": "eth-da",
    "parent": { "type": "L2", "chain": "<CHAIN_GROUP>" },
    "faultProofs": { "status": "<permissionless | permissioned>" }
  }
]
Source the values from your chain’s existing artifacts:
  • genesis.*, block_time, seq_window_size, max_sequencer_drift, batch_inbox_address, hardfork timestamps → your chain’s rollup.json
  • Addresses.* → your op-deployer state (opChainDeployments[<i>])
  • Roles.* → your op-deployer state’s per-chain roles block
Cross-check each L1 contract address onchain (cast call) before committing.
3

Generate the absolute prestate

From the root of the monorepo, point KONA_CUSTOM_CONFIGS_DIR at the directory you created in Step 2, then run the canonical build:
export KONA_CUSTOM_CONFIGS_DIR="$PWD/rust/kona/crates/protocol/registry/etc/custom-configs/<chain-name>"
just reproducible-prestate-kona
jq -r .pre rust/kona/prestate-artifacts-cannon/prestate-proof.json
The hash printed is your chain’s custom kona absolute prestate. The build also writes a hash-named gzipped binary at rust/kona/prestate-artifacts-cannon/0x<hash>.bin.gz — this is the file op-challenger needs to fetch at dispute time.
4

Verify your chain is embedded in the prestate

A custom-config build that silently fails to merge your chain produces the standard prestate hash — and an op-challenger pointed at it will never agree with your games. Always confirm your chain actually made it in:
# 1. The build log must list every custom chain that was merged:
#    cargo:warning=...: inserting new custom chain <bucket>: [chain-a,chain-b]
#    (or "merging custom chains <bucket>: [...]" if the bucket already exists)
#
# 2. The chain name must appear inside the prestate image itself:
gunzip -c rust/kona/prestate-artifacts-cannon/prestate.bin.gz | strings | grep "<chain-name>"
Run the grep once per chain in your chainList.json and confirm at least one match for every chain — a build that embeds chain A but drops chain B still produces a valid-looking hash. Zero matches means KONA_CUSTOM_CONFIGS=true was not set in the build shell, or KONA_CUSTOM_CONFIGS_DIR was a relative path.
5

Verify reproducibility

From a fresh checkout of the same tag with the same custom-configs files in place, repeat the build and confirm the hash is bit-identical. Anyone who wants to participate as an honest challenger on your chain must be able to reproduce this hash from the same inputs.

Deploying and configuring with the custom prestate

1

Host the prestate binary

Upload 0x<hash>.bin.gz to wherever you serve preimage files for op-challenger — typically a GCS bucket or HTTP endpoint. Files must be named by their absolute prestate hash so the challenger can resolve them on demand.
2

Register the custom prestate as game type 8

On the current (v2.4+) dispute-game contracts the absolute prestate is not a constructor argument — it lives in the DisputeGameFactory’s per-game-type gameArgs, appended to each game via clones-with-immutable-args. So you do not deploy a new implementation for a new prestate. Reuse the existing permissionless FaultDisputeGame implementation (a fresh op-deployer chain deploys one but leaves it unregistered) and register it for game type 8 with gameArgs that carry your prestate.gameArgs for a permissionless game is the packed encoding (124 bytes):
abi.encodePacked(absolutePrestate, vm, anchorStateRegistry, weth, l2ChainId)
Reuse vm / anchorStateRegistry / weth / l2ChainId from an existing registered game type (e.g. read gameArgs(1) and swap in your prestate), then register from the DisputeGameFactory owner:
// GAME_TYPE_CANNON_KONA == 8
DisputeGameFactory.setImplementation(8, faultDisputeGameImpl, gameArgs);
DisputeGameFactory.setInitBond(8, initBond);            // 0 is fine for a dev/test chain
Then make game type 8 the respected type from the Guardian role — respectedGameType lives on the AnchorStateRegistry, not the OptimismPortal (the portal proxies to it):
AnchorStateRegistry.setRespectedGameType(8);
See Migrating to permissionless fault proofs for the full role/transaction walkthrough. (On older, pre-v2.4 contracts where absolutePrestate was a constructor immutable, each prestate did require a fresh implementation deployment instead.)
3

Configure op-challenger

Add the kona-specific env vars to your existing challenger config:
OP_CHALLENGER_TRACE_TYPE=cannon-kona,permissioned
OP_CHALLENGER_CANNON_KONA_PRESTATES_URL=<HTTP_URL_PATH_TO_PRESTATES>
The challenger appends /0x<hash>.bin.gz to *_PRESTATES_URL to resolve the right binary per dispute. Your existing OP_CHALLENGER_ROLLUP_CONFIG, OP_CHALLENGER_L2_GENESIS, and OP_CHALLENGER_GAME_FACTORY_ADDRESS continue to apply unchanged.

Next Steps