casper-devnet 0.10.1

Launcher for local Casper Network development networks.
Documentation

Casper Devnet Launcher

Casper Devnet Launcher is a Rust tool for running a local Casper network, based on and heavily inspired by the NCTL workflow, to make local devnets quick and easy for smart contract developers. It embeds the essential node-launcher behavior in-process, so you only need the casper-node and (optionally) casper-sidecar binaries.

Casper Devnet Launcher demo

Why this exists

NCTL is fantastic for core protocol development and for building assets from source trees, but it comes with a large shell script surface, external process supervision, and multi-step UX. This tool targets application and contract developers who want a repeatable, portable devnet for development, CI, and tests.

Comparison with NCTL

Area NCTL Casper Devnet Launcher (this repo)
Primary audience Core protocol development Smart contract/app developers, CI/tests
Process control External supervisor (supervisord) In-process process control
Setup workflow Multiple commands Single command: casper-devnet start
Implementation Large shell script Rust binary (portable)
Node launcher External casper-node-launcher Embedded launcher logic
Requirements Node + launcher + sidecar + scripts Assets bundle (node + sidecar + templates)
Keys/accounts Random keys, friction to name/locate Deterministic accounts and one-shot inherited pipe consensus keys from a seed (BIP32 paths)
macOS devnet start Often requires extra local compilation Download pre-built cross-platform bundles
Network feedback Extra commands to watch blocks/txs Persistent SSE connection with live output

Installation

cargo install casper-devnet --locked

Docker usage

Pull the image:

docker pull ghcr.io/veles-labs/casper-devnet

Run a devnet with the default data location (persist assets and network state with a volume):

docker run --rm -it \
  -p 11101:11101 -p 14101:14101 -p 18101:18101 -p 22101:22101 -p 28101:28101 -p 32000:32000 \
  -v "$(pwd)/casper-devnet-data:/opt/casper-devnet-data" \
  ghcr.io/veles-labs/casper-devnet

Use a custom data directory by overriding XDG_DATA_HOME and mounting it:

docker run --rm -it \
  -e XDG_DATA_HOME=/data \
  -v "$(pwd)/casper-devnet-data:/data" \
  -p 11101:11101 -p 14101:14101 -p 18101:18101 -p 22101:22101 -p 28101:28101 -p 32000:32000 \
  ghcr.io/veles-labs/casper-devnet

The exposed ports map to node-1 services: RPC (11101), REST (14101), SSE (18101), network gossip (22101), binary protocol (28101), and diagnostics websocket proxy (ws://127.0.0.1:32000/diagnostics/node-1/). The diagnostics proxy also accepts HTTP POST requests to /diagnostics/node-1/ for non-websocket clients and streams NDJSON responses.

Diagnostics HTTP Proxy

The diagnostics proxy is useful in environments where you cannot or do not want to keep a websocket connection open. It accepts plain HTTP POST requests, forwards them to the node's diagnostics Unix socket, and returns line-delimited JSON responses. This is handy for automation or for setting failure points and collecting detailed runtime state.

Set a failure point (stop at a specific block height):

curl -v -XPOST --data 'stop --at block:250' http://127.0.0.1:32000/diagnostics/node-1/

Dump network info:

curl -v -XPOST --data 'net-info' http://127.0.0.1:32000/diagnostics/node-1/

Dump queues:

curl -v -XPOST --data 'dump-queues' http://127.0.0.1:32000/diagnostics/node-1/

For interactive workflows, use websockets so you can send commands and immediately see responses without re-establishing connections:

wscat -c ws://127.0.0.1:32000/diagnostics/node-1/

Usage

Add a local assets bundle:

casper-devnet assets add /path/to/assets-bundle.tar.gz

Add a custom override asset (symlink-backed local paths):

casper-devnet assets add dev \
  --casper-node /path/to/casper-node \
  --casper-sidecar /path/to/casper-sidecar \
  --chainspec /path/to/chainspec.toml \
  --node-config /path/to/node-config.toml \
  --sidecar-config /path/to/sidecar-config.toml

Custom asset names are write-once: reusing an existing name returns an error instead of replacing the asset directory.

List installed protocol bundles and custom assets:

casper-devnet assets list

Print absolute path to a custom asset directory (shell-substitution friendly):

casper-devnet assets path dev
vim "$(casper-devnet assets path dev)/chainspec.toml"

Custom assets install only symlink-backed asset files. Hooks are network-scoped and live under the managed network directory.

List managed network directories:

casper-devnet networks list

Remove a managed network directory from disk:

casper-devnet networks rm casper-dev
casper-devnet networks rm casper-dev --yes

Print the staged per-node config directories for a protocol version:

casper-devnet network casper-dev path 2.2.0

Print the network root:

casper-devnet network casper-dev path

Download assets from the latest release:

casper-devnet assets pull

Supported host architectures:

  • aarch64-apple-darwin
  • aarch64-unknown-linux-gnu
  • x86_64-apple-darwin
  • x86_64-unknown-linux-gnu

See also https://github.com/veles-labs/devnet-launcher-assets/releases/.

Force re-download:

casper-devnet assets pull --force

Override the target triple:

casper-devnet assets pull --target x86_64-unknown-linux-gnu

Security note

casper-devnet assets pull downloads pre-built binaries from https://github.com/veles-labs/devnet-launcher-assets/releases. If you are not comfortable running pre-built binaries, download the assets repo and rebuild the binaries locally using the provided scripts before installing them with assets add.

List available protocol versions:

casper-devnet assets list

Start a devnet:

casper-devnet start

Start from a specific installed bundle, or from a custom asset:

casper-devnet start --asset 2.1.3
casper-devnet start --custom-asset dev

Override the chainspec protocol version while using the selected asset files:

casper-devnet start --asset 2.1.3 --protocol-version 2.2.0

Patch chainspec values before fresh genesis setup:

casper-devnet start --force-setup \
  --chainspec-override 'core.minimum_era_height=1' \
  --chainspec-override 'core.test_values=[1, 10]'

--chainspec-override uses key.path=<toml-value> syntax and may be repeated. Quote values that contain shell-sensitive characters or spaces, especially arrays and strings. Overrides are applied before launcher defaults, so --protocol-version, --delay, --network-name, and --node-count still control their generated chainspec fields. Overrides only apply to a fresh setup; use --force-setup when the network already exists.

Stage a protocol upgrade from a versioned or custom asset:

casper-devnet network casper-dev stage-protocol --asset 2.1.3 --protocol-version 2.2.0 --activation-point 123
casper-devnet network casper-dev stage-protocol --custom-asset dev --protocol-version 2.2.0 --activation-point 123
casper-devnet network casper-dev stage-protocol --custom-asset dev \
  --protocol-version 2.2.0 \
  --activation-point 123 \
  --chainspec-override 'core.minimum_era_height=1'

Derive deterministic account material from a seed and BIP32 path:

casper-devnet derive "m/44'/506'/0'/0/0" --secret-key
casper-devnet derive "m/44'/506'/0'/0/100" --public-key
casper-devnet derive "m/44'/506'/0'/0/100" --account-hash -o /tmp/derived
casper-devnet derive "m/44'/506'/0'/0/100" --account-hash -o -

Print a random live endpoint for a running node:

casper-devnet network casper-dev port --rpc
casper-devnet network casper-dev port --sse
casper-devnet network casper-dev port --rest
casper-devnet network casper-dev port --binary
casper-devnet network casper-dev port --diagnostics

When the network is actively managed by casper-devnet, this command prefers the live control socket to discover currently running nodes before choosing an endpoint. If that live query is unavailable or unresponsive, it falls back to state.json instead of hanging indefinitely.

Add managed non-genesis nodes to a running network:

casper-devnet network casper-dev add-nodes --count 2

network <name> add-nodes is live-only: it requires the foreground start or MCP-managed process to be running so it can prepare assets, spawn the new node and sidecar processes, track them in state.json, and stop them during normal shutdown. During expansion, the manager reads a recent trusted hash from an existing node's REST /status, writes it to [node].trusted_hash, and uses the active config's joining sync mode ([node].sync_handling = "ttl", or sync_to_genesis = false for legacy configs). Added nodes inherit the currently active protocol version and any higher protocol versions already staged on existing nodes, so a node added before an activation era can follow the pending upgrade. The foreground start manager logs the full endpoint summary for nodes added through the control plane and prints node reactor state changes observed by polling each node's REST /status every 500ms.

If a managed process is running (from start or MCP), staging runs in live mode and restarts sidecars. Otherwise, staging runs in offline mode and only writes versioned nodes/node-*/bin/<version> and nodes/node-*/config/<version> assets. Live staging control uses a per-network Unix socket at /tmp/<network-name>.socket for runtime stage requests. Managed node processes serve consensus secret keys through inherited pipe file descriptors, deriving the PEM from the network seed and writing it once after each child process starts. Managed on-disk configs still reference keys/secret_key.pem, but the embedded launcher starts validators and migrate-data with temporary configs whose consensus key paths point at /proc/self/fd/<fd> on Linux or /dev/fd/<fd> on macOS. The generated PEM is not served again for that config read, and fresh pipes are created before later migrate-data runs or validator restarts. Node and sidecar log aliases (for example node-1.stdout) are atomically repointed to versioned log files during protocol transitions; use tail -F to follow across alias swaps. If networks/<network>/hooks/pre-stage-protocol exists, it runs after the target version's per-node bin/<version> and config/<version> directories have been staged, and before post-stage metadata is queued, with argv <network_name> <protocol_version> <activation_point>. Use casper-devnet network <network> path <protocol_version> inside the hook to locate each staged per-node config directory. If the hook fails, the newly staged version directories are removed and post-stage-protocol is not queued. If networks/<network>/hooks/post-stage-protocol exists, it runs once later at the real upgrade boundary, after the launcher starts the target validator version, with argv <network_name> <protocol_version>. If networks/<network>/hooks/pre-genesis exists, it runs after assets have been prepared for a fresh network but before the network is started, with argv <network_name> <protocol_version>. If networks/<network>/hooks/post-genesis exists, it runs once after the fresh network produces its first block, with argv <network_name> <protocol_version>. If networks/<network>/hooks/block-added exists, it runs on each observed new block with argv <network_name> <protocol_version> and the block event JSON payload on stdin. Each hook runs in its own working directory under networks/<network>/hooks/work/<hook-name>/, so hooks can leave files behind for later hooks to inspect. Hook stdout/stderr are streamed line by line through casper-devnet stderr as <hook_name> stdout: ... and <hook_name> stderr: .... Non-zero exits are still reported, but successful exit code 0 is quiet. The raw hook streams are also written under networks/<network>/hooks/logs/. The generated sample hooks live under networks/<network>/hooks/*.sample. The samples show how to call casper-devnet network <network> port --rpc, issue an info_get_status JSON-RPC request, consume block-added JSON from stdin, and use casper-devnet network <network> path [<protocol_version>] to locate the network root or staged per-node config directories.

Run MCP control plane server (STDIO + HTTP):

casper-devnet mcp

Run MCP in HTTP-only mode:

casper-devnet mcp --transport http --http-bind 127.0.0.1:32100 --http-path /mcp

Check whether a devnet has produced blocks (useful for CI):

casper-devnet network casper-dev is-ready

Create assets without starting processes:

casper-devnet start --setup-only

Use --setup-only when you want to tweak chainspecs or node configs before launching. Use --chainspec-override with --setup-only to apply repeatable TOML value patches during fresh asset setup. --setup-only writes configs that reference keys/secret_key.pem, but it does not create keys/ directories or write regular consensus secret key PEM files. Managed start/MCP runs use one-shot inherited pipe key delivery at runtime.

Rebuild assets:

casper-devnet start --force-setup

MCP workflow

casper-devnet mcp does not auto-start a network. Use MCP tools in this order:

  1. spawn_network (defaults to force_setup=true for fresh setup; set force_setup=false to resume existing assets).
  2. wait_network_ready (waits for running processes, healthy /status, reactor_state=Validate, and first observed block).
  3. Call network tools (RPC/status/block/log/SSE/transactions).

MCP server defaults:

  • transport=both
  • http_bind=127.0.0.1:32100
  • http_path=/mcp

MCP tools require network_name; node-scoped tools also require node_id. Managed networks are stopped automatically when the MCP server exits. Use managed_processes to inspect managed node/sidecar processes, with optional process-name filtering and running_only control. Use stage_protocol to stage versioned-asset or custom-asset upgrades for managed networks (live_mode=true) or discovered stopped networks (live_mode=false). rpc_query_global_state auto-resolves the latest block hash when both block_id and state_root_hash are omitted. For transaction construction, use MCP tools (make_transaction_package_call, make_transaction_contract_call, make_transaction_session_wasm) with send_transaction_signed instead of invoking external casper-client binaries. session_args supports full CLType strings (including nested types such as Option<List<U512>>, Map<String,U64>, tuples, and ByteArray[32]). Scalars can be passed as string/number/bool, null maps to None for Option<T>, and composite values should be provided as hex bytes (0x...). Pass this field as JSON (array/object), not an escaped JSON string. Legacy session_args_json is still accepted for compatibility. send_transaction_signed.transaction should be a typed JSON object. Field name transaction_json is not accepted, and encoded JSON strings are not supported. Use MCP transaction query tools (get_transaction, wait_transaction) instead of shelling out to curl for info_get_transaction calls. Valid session_args examples:

  • [{"name":"value","type":"I32","value":"1"}]
  • [{"name":"items","type":"List<U64>","value":"0x03000000010000000000000002000000000000000300000000000000"}] Unsupported formats:
  • {"value":1} (object shorthand)
  • ["value:i32=1"] (casper-client CLI arg string format)

Codex CLI stdio MCP example (~/.codex/config.toml):

[mcp_servers.casper-devnet]
command = "casper-devnet"
args = ["mcp", "--transport", "stdio"]

Or add it via Codex CLI:

codex mcp add casper-devnet -- casper-devnet mcp --transport stdio

If casper-devnet is not on PATH, set command to an absolute binary path. Claude CLI config example: PRs welcome.

Common flags

  • --asset <version>: Versioned asset bundle to use from the assets store (accepts 2.1.3 or v2.1.3; defaults to newest bundle)
  • --custom-asset <name>: Custom asset under assets/custom/<name> to use instead of a versioned bundle
  • --protocol-version <version>: Override the chainspec protocol version; when omitted, start uses the selected asset's chainspec value
  • --chainspec-override <key.path=value>: Patch a generated chainspec value before genesis setup; repeatable; value must be valid TOML, for example 'core.test_values=[1, 10]'; requires a fresh network or --force-setup
  • --network-name <name>: Network name for configs/paths (default: casper-dev)
  • --net-path <path>: Override the network runtime root (default: platform data dir .../networks)
  • --node-count <n>: Number of nodes (aliases: --nodes, --validators; default: 4)
  • --users <n>: Number of user accounts (default: node count)
  • --delay <seconds>: Genesis activation delay (default: 3). Keep it short for local devnets; increase if you need more time to attach tooling before genesis.
  • --log-level <level>: Child process log level (default: info)
  • --node-log-format <format>: Node logging format in config (default: json)
  • --setup-only: Build assets and exit
  • --force-setup: Rebuild assets even if they exist, while preserving networks/<network>/hooks/
  • --seed <string>: Seed for deterministic devnet keys (default: default)

casper-devnet mcp flags:

  • --transport <stdio|http|both>: MCP transport mode (default: both)
  • --http-bind <addr:port>: HTTP bind address for streamable MCP (default: 127.0.0.1:32100)
  • --http-path <path>: HTTP mount path for MCP endpoint (default: /mcp)
  • --net-path <path>: Override network runtime root (same behavior as start)

casper-devnet network <network> stage-protocol flags:

  • [asset]: Optional positional shorthand for --asset <version>
  • --asset <version>: Versioned asset bundle to stage from
  • --custom-asset <name>: Custom asset under assets/custom/<name> to stage from
  • --protocol-version <version>: Protocol version to stage (required)
  • --activation-point <era-id>: Future era id for activation (required)
  • --chainspec-override <key.path=value>: Patch the staged chainspec before pre-stage hooks run; repeatable; value must be valid TOML, for example 'core.test_values=[1, 10]'
  • --net-path <path>: Override network runtime root (same behavior as start)

Exactly one of [asset], --asset, or --custom-asset is required for staging.

casper-devnet derive flags:

  • <path>: BIP32 derivation path to resolve
  • --secret-key: Print or write the derived secret key PEM
  • --public-key: Print or write the derived public key hex
  • --account-hash: Print or write the derived account hash
  • --seed <string>: Deterministic seed for derivation (default: default)
  • -o, --output <path>: Output directory, or - for stdout

Exactly one of --secret-key, --public-key, or --account-hash must be provided.

casper-devnet network <network> path flags:

  • [protocol_version]: Optional protocol version to inspect
  • --net-path <path>: Override network runtime root (same behavior as start)

When no protocol version is provided, this prints the network root directory. When a protocol version is provided, it prints one staged config directory per known node, one path per line.

casper-devnet network <network> add-nodes flags:

  • --count <n>: Number of managed non-genesis nodes to add to the live network
  • --net-path <path>: Override network runtime root (same behavior as start)

casper-devnet network <network> port flags:

  • --rpc: Print one random running node RPC URL
  • --sse: Print one random running node SSE URL
  • --rest: Print one random running node REST URL
  • --binary: Print one random running node binary-port address
  • --diagnostics: Print one random running node diagnostics socket path
  • --net-path <path>: Override network runtime root (same behavior as start)

Exactly one of --rpc, --sse, --rest, --binary, or --diagnostics must be provided.

casper-devnet network <network> status flags:

  • --node-id <id>: Node id whose REST /status endpoint should be queried
  • --net-path <path>: Override network runtime root (same behavior as start)

casper-devnet network <network> is-ready flags:

  • --net-path <path>: Override network runtime root (same behavior as start)

Assets bundle layout

The bundle is extracted into the platform data directory and should include a versioned root with the following shape:

v2.1.1/bin/casper-node
v2.1.1/bin/casper-sidecar
v2.1.1/chainspec.toml
v2.1.1/sidecar-config.toml
v2.1.1/node-config.toml

Custom override assets are stored separately under assets/custom/<name>/ as symlinks to local casper-node, casper-sidecar, chainspec.toml, node-config.toml, and sidecar-config.toml. Custom assets do not install or execute hooks.

Network hooks live under each managed network directory. Samples are generated as:

networks/<network>/hooks/pre-genesis.sample
networks/<network>/hooks/post-genesis.sample
networks/<network>/hooks/block-added.sample
networks/<network>/hooks/pre-stage-protocol.sample
networks/<network>/hooks/post-stage-protocol.sample

Only exact active hook filenames are executed:

networks/<network>/hooks/pre-genesis
networks/<network>/hooks/post-genesis
networks/<network>/hooks/block-added
networks/<network>/hooks/pre-stage-protocol
networks/<network>/hooks/post-stage-protocol

The .sample files are boilerplate only and are never executed directly.

For manual rebuilds and bundle scripts, see https://github.com/veles-labs/devnet-launcher-assets/.

Notes

  • The launcher runs the node directly and manages processes internally; no external supervisor is required.
  • The embedded launcher state is handled within the process; only the node/sidecar binaries are required.
  • Assets are stored under the platform data directory (e.g., ~/.local/share/xyz.veleslabs.casper-devnet on Linux or ~/Library/Application Support/xyz.veleslabs.casper-devnet on macOS), with assets/ for bundles and networks/ for runtime assets.