:: In active development. Codebase tracks the dev branch. Explore at your own risk. Official release coming soon.
Skip to content

A Quick Note

Scaffold a vesl nockapp, install fourteen verifiable primitives, compose them into one Hoon kernel, watch a Rust hull settle a Merkle-rooted note. Three commands. Five minutes.

vesl-nockup is the recommended starting point — a self-contained distribution that pairs with nockup, the project scaffolder shipped from the nockchain monorepo. The rest of this guide assumes you're using both.

Get Started

Three commands from an empty directory to a kernel that registers a Merkle root and settles a note against it:

bash
nockup project init                                    # fetches vesl template + zkvesl/vesl-graft
nockup graft inject --apply hoon/app/app.hoon          # composes grafts into the kernel
cargo +nightly run --release                           # builds out.jam, runs the kernel

If hoonc, nockchain, or nockup are unfamiliar, see vesl at a glance or the Glossary first.

Why --release?

The nockvm runtime ships debug_assert!s in its stack-frame check (is_in_frame) that fire under debug-build assumptions and are compiled out in release. Booting a 14-graft kernel from a debug build panics on the first poke. --release is the supported development mode for vesl-nockup until the upstream assertion is loosened.

Prerequisites

  1. Clone the nockchain monorepo — source for nockup, and for the nockchain binary later if you run against fakenet/dumbnet (the three-command flow below doesn't need the chain binary on PATH):

    bash
    git clone https://github.com/nockchain/nockchain.git
  2. Install nockup — the project scaffolder. nockup install downloads hoon, hoonc, and nockup itself into ~/.nockup/bin/ and prepends that to your PATH. Either script install or build from the cloned monorepo:

    bash
    # Script install
    curl -fsSL https://raw.githubusercontent.com/nockchain/nockchain/refs/heads/master/crates/nockup/install.sh | bash
    
    # Or build from source
    cd nockchain && cargo install --path crates/nockup --locked
  3. Rust nightlyrustup toolchain install nightly. Then cargo +nightly resolves to it.

  4. nockup-graft — vesl-flavored graft composer; ships from vesl-nockup as a sidecar binary alongside graft-inject:

    bash
    cargo install --git https://github.com/zkvesl/vesl-nockup --bin nockup-graft

    Once installed, nockup graft <subcmd> resolves the binary via nockup's plugin discovery and dispatches to it.

Verify the toolchain:

bash
hoonc --version && nockup --help >/dev/null && cargo +nightly --version && nockup-graft --version

Shell completions (optional)

Both nockup-graft and vesl-test emit completion scripts for bash, zsh, fish, elvish, and powershell via a completions <shell> subcommand. One-line install per shell:

bash
# bash — drop into the user's bash-completion dir; re-source your bashrc.
nockup-graft completions bash > ~/.local/share/bash-completion/completions/nockup-graft
vesl-test    completions bash > ~/.local/share/bash-completion/completions/vesl-test

# zsh — into the first fpath entry; rebuild compinit (`exec zsh` or `compinit`).
nockup-graft completions zsh  > "${fpath[1]}/_nockup-graft"
vesl-test    completions zsh  > "${fpath[1]}/_vesl-test"

# fish — picked up automatically on next shell start.
nockup-graft completions fish > ~/.config/fish/completions/nockup-graft.fish
vesl-test    completions fish > ~/.config/fish/completions/vesl-test.fish

After install, tab-complete subcommands (nockup-graft inj<TAB>inject) and flag names (nockup-graft inject --lib-<TAB>--lib-dir). The scripts are clap-generated, so they stay in sync with the binary on every rebuild — re-run the install line after a cargo install --force to pick up new subcommands.

1. Scaffold from the vesl Template

Write a nockapp.toml declaring the package and template source, then let nockup create the project:

bash
cat > nockapp.toml <<'TOML'
[package]
name = "my-app"
version = "0.1.0"
description = "grafted NockApp"
template = "vesl"
template_git = "https://github.com/zkvesl/vesl-nockup"
template_path = "templates"

[dependencies]
"zkvesl/vesl-graft" = "latest"
TOML

nockup project init
cd my-app

nockup project init does three things:

  1. Creates the my-app/ project directory.
  2. Pulls in the vesl graft library.
  3. Runs the package's post-install patches — Cargo.toml rewrites that align Rust deps with the pinned nockchain revision, plus any scaffold-file additions the package declares. You'll see a confirmation prompt that lists every patch operation; press y to apply. Patches are declarative — no code runs.

Running this in a script

The confirmation prompt in step 3 blocks unattended runs. To pre-approve, pipe consent:

bash
yes y | nockup project init

2. Wire the Kernel

bash
nockup graft inject hoon/app/app.hoon            # preview
nockup graft inject --apply hoon/app/app.hoon    # write

The template's app.hoon ships with ten :: nockup:* markers at fixed structural points. nockup graft inject discovers every <name>-graft.toml under hoon/lib/, composes their per-marker blocks, and (with --apply) writes the result. About 80 lines per graft.

Preview is the default. Nothing lands on disk until you pass --apply — this keeps a compromised hoon/lib/ from silently composing hostile Hoon into your kernel source. See Inject for marker semantics, lint families, and the per-graft sha256 banner.

Inject also refuses to compose when the project has no nockapp.toml — the file you wrote in §1 is the trust anchor, per Manifest Schema → Trust Model. Without it, inject would splice arbitrary Hoon from any directory into your kernel; running from inside the scaffolded project keeps this check satisfied.

3. Build and Run

bash
./compile.sh
cargo +nightly run --release

compile.sh ships in the scaffold: it runs hoonc, then fails loud if hoonc exited 0 without writing out.jam — a structural type error can cause exactly that. See Build & Run for vesl-test verify-jam, the staleness check.

First Cargo build fetches and compiles the full nockchain stack — expect 2–5 minutes.

4. Verify Effects

The last two lines should be:

  effect: %settle-registered
  effect: %settle-noted

Each effect: line is a tagged event the kernel emitted back to the hull:

  • %settle-registered — the kernel accepted a Merkle root under a namespace (called a hull). The root is a fingerprint of a set of items; once registered, only items that hash into it can later be proven to belong.
  • %settle-noted — the hull then submitted one item from that set along with a Merkle proof, and the kernel verified the proof matches the registered root. The item is now recorded as settled, and the same item can't be settled twice.

This commit-then-prove cycle is the canonical vesl pattern. The same shape powers asset registries, licensing flows, and audit logs — anywhere a kernel needs cryptographic evidence that an item belongs to a published set. The template's src/main.rs is a clap dispatch — the Demo arm walks the lifecycle once; the Serve arm boots the same kernel and mounts the vesl-hull HTTP API. See Build / Hull → Scaffold CLI: Demo and Serve for the Serve flags and endpoint catalog. You'll extend the Demo arm to your domain in Build / Hull.

5. Exercise the Lifecycle

You've watched the binary emit two effects from a startup poke. The next step is to confirm those effects landed in kernel state and to query that state from outside the lifecycle.

Observe both effects

Re-run the binary and capture the effect list. The two head tags emitted are:

EffectFull payloadMeaning
%settle-registered[hull=@ root=@]A hull was registered against the configured root; the hull is now known to settle-graft.
%settle-notednote=[id=@ hull=@ root=@ state=[%settled ~]]A note was filed against the registered hull; settle-graft updated its per-hull index.

For the full payload shape of every shipped effect, see Effect Catalog → settle-graft.

Peek the registered hull

To confirm the hull registered, ask the running kernel directly. The vesl-test CLI (installed once via cargo install --path test/vesl-test --locked from your vesl-nockup checkout) sends one-shot peeks against a compiled out.jam:

bash
# hull-keyed peek: did settle-graft register hull 1?
vesl-test inspect peek out.jam --path-tag settle-registered --hull 1

Three outcomes:

  • unrecognized — bare ~. The settle-graft tag isn't composed, or the path is malformed.
  • present-but-empty[~ ~]. Path recognized; no value at that hull.
  • present[~ [~ value]]. Hull is registered.

See Testing → The CLI → inspect peek for the full subcommand surface, and Peek Catalog → settle-graft for every shipped peek path.

Handcraft a second poke

To go beyond the startup pair — send your own %settle-note cause from a test and watch a fresh %settle-noted effect — see Testing → Domain Pokes. That page shows the build_settle_note_poke builder, the harness API, and the matching peek pattern. The same shape extends to every other shipped graft.

Where to Go Next

  • Build a Real App — compose five grafts (rbac + registry + log + settle + batch) into a license registry, end to end.
  • NockApp Anatomy — what the hull, grafts, and your domain are doing under the hood.
  • Customizing — multi-leaf gates, signed gates, STARK gates, custom domain pokes.
  • State graftskv-graft, counter-graft, queue-graft, rbac-graft, registry-graft.
  • mint_lifecycle.rs — Rust-native end-to-end test that mirrors the lifecycle above.

Troubleshooting

  • Template 'vesl' not found at <path> — nockup fetched the template repo via template_git but couldn't find <template_path>/<template>/ inside it. Verify the template_git, template_path, and template fields all line up. If you're working against a branch where the template doesn't exist yet, pin template_commit = "<sha>" to a known-good commit. Omitting template_git falls back to the channel cache populated by nockup channel update.

  • unknown command: graftnockup-graft isn't on your $PATH. Re-run cargo install --git ... --bin nockup-graft and verify with which nockup-graft.