# 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.

## 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
| 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 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
```bash
cargo install casper-devnet --locked
```
## Docker usage
Pull the image:
```bash
docker pull ghcr.io/veles-labs/casper-devnet
```
Run a devnet with the default data location (persist assets and network state with a volume):
```bash
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:
```bash
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):
```bash
curl -v -XPOST --data 'stop --at block:250' http://127.0.0.1:32000/diagnostics/node-1/
```
Dump network info:
```bash
curl -v -XPOST --data 'net-info' http://127.0.0.1:32000/diagnostics/node-1/
```
Dump queues:
```bash
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:
```bash
wscat -c ws://127.0.0.1:32000/diagnostics/node-1/
```
## Usage
Add a local assets bundle:
```bash
casper-devnet assets add /path/to/assets-bundle.tar.gz
```
Add a custom override asset (symlink-backed local paths):
```bash
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:
```bash
casper-devnet assets list
```
Print absolute path to a custom asset directory (shell-substitution friendly):
```bash
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:
```bash
casper-devnet networks list
```
Remove a managed network directory from disk:
```bash
casper-devnet networks rm casper-dev
casper-devnet networks rm casper-dev --yes
```
Print the staged per-node config directories for a protocol version:
```bash
casper-devnet network casper-dev path 2.2.0
```
Print the network root:
```bash
casper-devnet network casper-dev path
```
Download assets from the latest release:
```bash
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/](https://github.com/veles-labs/devnet-launcher-assets/releases/).
Force re-download:
```bash
casper-devnet assets pull --force
```
Override the target triple:
```bash
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](https://github.com/veles-labs/devnet-launcher-assets/).
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:
```bash
casper-devnet assets list
```
Start a devnet:
```bash
casper-devnet start
```
Start from a specific installed bundle, or from a custom asset:
```bash
casper-devnet start --asset 2.1.3
casper-devnet start --custom-asset dev
```
Override the chainspec protocol version while using the selected asset files:
```bash
casper-devnet start --asset 2.1.3 --protocol-version 2.2.0
```
Patch chainspec values before fresh genesis setup:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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 only the currently active protocol version. If a future protocol version was
already staged, run `network <name> stage-protocol` again after adding nodes so the new nodes
receive that staged version too.
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.
In live mode, consensus keys are restored from the network seed before staging so
`migrate-data` can run successfully at the upgrade boundary.
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):
```bash
casper-devnet mcp
```
Run MCP in HTTP-only mode:
```bash
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):
```bash
casper-devnet network casper-dev is-ready
```
Create assets without starting processes:
```bash
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.
Rebuild assets:
```bash
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`):
```toml
[mcp_servers.casper-devnet]
command = "casper-devnet"
args = ["mcp", "--transport", "stdio"]
```
Or add it via Codex CLI:
```bash
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/](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.