logos-scaffold is a Rust CLI for bootstrapping LEZ (Logos Execution Zone) program_deployment projects in standalone mode.
The CLI is currently Unix-only. Localnet and process/port detection rely on Unix tools (lsof, ps, kill).
- Single external dependency: LEZ
- Standalone sequencer flow only
- No
logos-blockchaindependency - No full-stack/circuits management
git,rustc,cargocurlfor fetching thelogos-blockchain-circuitsrelease on firstsetup- Unix process helpers:
lsof,ps,kill - Container runtime for guest builds: Docker or Podman
cargo install --path .This installs two binaries on your PATH: logos-scaffold and the shorter
alias lgs. They are functionally identical; use either.
lgs completions <shell> prints a completion script to stdout. The script
completes both lgs and logos-scaffold.
Per-shell install instructions live in the CLI help itself:
lgs completions bash --help
lgs completions zsh --helpCanonical dogfooding scenarios live in DOGFOODING.md. Keep that runbook updated whenever first-class commands, templates, or supported workflows change.
All commands below also work under the lgs alias (e.g. lgs setup).
logos-scaffold create <name> [--vendor-deps] [--lez-path PATH]
logos-scaffold new <name> [--vendor-deps] [--lez-path PATH]
logos-scaffold init [--dry-run] [--no-backup]
logos-scaffold setup
logos-scaffold build [project-path]
logos-scaffold deploy [program-name]
logos-scaffold localnet start [--timeout-sec N]
logos-scaffold localnet stop
logos-scaffold localnet status [--json]
logos-scaffold localnet logs [--tail N]
logos-scaffold localnet reset (--yes | --dry-run) [--reset-wallet] [--verify-timeout-sec N]
logos-scaffold build idl [project-path]
logos-scaffold build client [project-path]
logos-scaffold wallet list [--long]
logos-scaffold wallet topup [<address> | --address <address-ref>] [--dry-run]
logos-scaffold wallet default set <address-ref>
logos-scaffold wallet default set --address <address-ref>
logos-scaffold wallet -- <wallet-command...>
logos-scaffold run [--post-deploy <cmd>...] [--no-post-deploy] [--localnet-timeout N]
logos-scaffold spel -- <spel-command...>
logos-scaffold basecamp setup
logos-scaffold basecamp modules [--path PATH]... [--flake REF]... [--show]
logos-scaffold basecamp install [--print-output]
logos-scaffold basecamp launch <profile> (--yes | --no-clean | --dry-run)
logos-scaffold basecamp build-portable
logos-scaffold basecamp doctor [--json]
logos-scaffold doctor [--json]
logos-scaffold report [--out PATH] [--tail N]
logos-scaffold completions <bash|zsh>
logos-scaffold helpEach subcommand documents copy-paste examples under --help. Global -q / --quiet (or LOGOS_SCAFFOLD_QUIET=1) suppresses echoed external commands.
createandneware aliases.initwritesscaffold.toml(schema v0.2.0) with defaults into the current directory so an existing project can use the scaffold workflow. It creates.scaffold/{state,logs}and appends.scaffoldto.gitignore. Whenscaffold.tomlalready exists at an older schema,initmigrates it in place viatoml_editso comments, key ordering, and unrelated sections survive the rewrite — old[basecamp].pin/.source/.lgpm_flakemove to[repos.basecamp]/[repos.lgpm]; old[basecamp.modules.*]move to top-level[modules.*]; legacyurlfields on[repos.{lez,spel}]are dropped. Migrations write ascaffold.toml.baknext to the original by default (skip with--no-backup); preview either form with--dry-run. Already-current configs succeed, leavescaffold.tomlunchanged, and refresh the shipped AI skills. Runsetupnext after a fresh init or migration.setupsyncs LEZ andspelto their pinned commits (read from[repos.lez]/[repos.spel]), builds the standalonesequencer_service,wallet, andspelbinaries locally, and seeds a deterministic default wallet from preconfigured public accounts when none is set. All binaries are project-local and are not installed to PATH — uselogos-scaffold wallet .../logos-scaffold spel -- ...to interact with them. By default[repos.lez].path/[repos.spel].pathare empty inscaffold.toml; the on-disk location is resolved at runtime from<cache_root>/repos/<name>/<pin>, so the file is portable across machines and CI.--vendor-depsprojects keep relative.scaffold/repos/{lez,spel}literals; an explicit absolutepathset inscaffold.tomlis honored as-is.build [project-path]runssetupand thencargo build --workspace.deploy [program-name]deploys one or all guest programs discovered inmethods/guest/src/bin/*.rsusing prebuilt.binartifacts. After each successful submission it printsprogram_id: <hex>(the risc0 image ID, computed locally from the submitted ELF) and includes it in--program-path … --jsonoutput. Use--jsonfor machine-readable output (recommended for automation).build idl [project-path]regenerates the IDL from the project source using the vendoredspelbinary.build client [project-path]regenerates client bindings from the current IDL using the vendoredspelbinary.localnet startwaits until localnet is actually ready (pid alive+127.0.0.1:3040reachable), otherwise fails with diagnostics.localnet statusdistinguishes managed process, stale state, and foreign listeners.localnet resetstops the sequencer, clears sequencer chain state, restarts, and verifies blocks. Destructive:--yesis required unless--dry-runis passed (--dry-runprints the plan without changing anything).--reset-walletalso deletes the project wallet home and default-address state (irrecoverable).wallet listshows known wallet accounts (wallet account list).wallet topupchecks account state first (wallet account get --account-id ...), runswallet auth-transfer init --account-id ...only when the destination is uninitialized, then performs Piñata faucet claim (wallet pinata claim --to ...). If address is omitted, scaffold uses project default wallet from.scaffold/state/wallet.state.wallet default setstores a project-scoped default wallet address in.scaffold/state/wallet.state.wallet -- ...forwards raw wallet CLI arguments to the project-local wallet binary while preserving project wallet environment.runcombines build, IDL build, localnet start, wallet topup, and deploy into a single command. If a[run]section withpost_deployis present inscaffold.toml, each hook is executed after deploy viash -c(cwd = project root) withSEQUENCER_URL,NSSA_WALLET_HOME_DIR,SCAFFOLD_PROJECT_ROOT, andSCAFFOLD_IDL_DIRenv vars; when the project has exactly one deployable program,SCAFFOLD_PROGRAM_IDandSCAFFOLD_GUEST_BINare also set. If a localnet is already running it is reused; otherwise it is started.--post-deploy <cmd>(repeatable) overrides the configured hooks;--no-post-deployskips them entirely.spel -- ...forwards raw spel CLI arguments to the project-vendoredspelbinary so any spel subcommand (inspect,pda,generate-idl, …) runs against the project's pinned version without a global install.basecamp setuppins basecamp +lgpm(read from[repos.basecamp]/[repos.lgpm]— bothbuild = "nix-flake"), builds both (logged to.scaffold/logs/<timestamp>-setup-*.log), and seeds per-profile XDG directories foraliceandbobunder.scaffold/basecamp/profiles/. Runtime config (port_base,port_stride) is in[basecamp].basecamp modulesis the sole writer of the captured module set, which lives in top-level[modules.<name>]sections (each withflakeandrole = "project" | "dependency"). Modules aren't basecamp's property — they're the project's Logos modules, which basecamp happens to be one consumer of. Zero-arg runs auto-discovery: walks project flakes (root.#lgxfirst, else immediate sub-flakes), derives amodule_nameper source (frommetadata.json.namefor local paths; heuristic from the github repo slug for remote refs, with a one-line assumption note you can correct inscaffold.toml), then resolves each declared dep name by: (1) already keyed in[modules], (2) basecamp preinstall list, (3) the source's ownflake.lock, (4) scaffold-default pin. Unresolved deps fail fast — no silent skip.--flake <ref>/--path <file>capture explicit project sources;--showprints the current set without mutating. Re-runs are idempotent: existing[modules]entries are preserved so hand-edits survive. Project contract: see docs/basecamp-module-requirements.md.basecamp installis pure replay: builds every captured source (dependencies first, then project modules — fail-fast on a broken companion pin) and installs them into bothaliceandbobvialgpm. No source-set flags. If the state is empty on first call it transparently invokesbasecamp modulesin auto-discover mode, prints what was captured, and proceeds. Each nix build logs to.scaffold/logs/<timestamp>-install.logwith a one-line progress status (duration on both success and failure);--print-output(orLOGOS_SCAFFOLD_PRINT_OUTPUT=1) opts back into streaming nix output directly for CI.basecamp launch <profile>scrubs the profile's data/cache, replays captured modules, assigns per-profile ports, and execsbasecampwith the profile's XDG environment. Before exec, prints a one-line variant-check summary of installed modules so the freeze-on-first-click case (upstream manifest variant mismatch) is visible. Destructive by default — requires one of:--yes(confirm scrub),--no-clean(launch without scrubbing), or--dry-run(preview the plan).basecamp build-portablerebuilds everyrole = "project"entry in[modules]with attr-swapped#lgx-portablefor hand-loading into a basecamp AppImage. Zero-arg: sources come from scaffold.toml (managed viabasecamp modules).role = "dependency"entries are intentionally skipped — the target AppImage provides its own release companion modules via its Package Manager catalog. Output is ordered topologically bymetadata.jsondependencies (leaves first, so basecamp's AppImage can resolve each module's deps before loading it), and symlinked into.scaffold/basecamp/portable/as<NN>-<module_name>.lgxso the AppImage's "install lgx" file picker has browsable, human-named files in the right order. The directory is wiped and recreated per run.basecamp doctoremits a basecamp-specific health report: captured modules summary (each entry's flake ref, parsed tag/commit annotation for github refs, and any API headers already installed in alice's profile), manifest variant check per seeded profile (flags modules whosemainis missing the current-platform-devkey — the freeze-on-first-click failure mode), dep-pin drift (capturedrole = "dependency"rev vs. scaffold default), and auto-discovery drift (project sources discoverable today but absent from the captured set).--jsonfor machine-readable output.doctorprints actionable checks and next steps;--jsonis for CI/machine parsing.reportcreates a.tar.gzdiagnostics bundle for GitHub issues using strict allowlist collection with redaction and explicit skip reporting.completions <shell>prints a shell completion script to stdout. Supported shells:bash,zsh. The generated script covers bothlgsandlogos-scaffold.- Wallet-facing commands accept
LOGOS_SCAFFOLD_WALLET_PASSWORDfor password override (fallback: local dev default).
lgs new my-app
cd my-app
lgs setup
lgs localnet start
lgs build
lgs deploy
lgs wallet topup
lgs wallet -- check-healthIf you already have a Rust/LEZ project, add scaffold to it without regenerating:
cd my-existing-project
lgs init
lgs setupinit only writes scaffold.toml and creates .scaffold/ directories.
It does not touch your Cargo.toml or src/. Edit scaffold.toml if you
need non-default framework settings (e.g. lez-framework).
If scaffold.toml predates a section scaffold now requires (e.g.
[repos.spel]), commands fail with an error pointing at init. Migrate with:
cd my-existing-project
lgs init # appends the missing section to scaffold.toml in place
lgs setup # picks up the new sectionExisting fields are preserved verbatim.
Or use run to do build → IDL → localnet → topup → deploy in one step:
lgs new my-app
cd my-app
lgs runTo run one or more post-deploy hooks automatically (e.g. submit a transaction
with spel), add a [run] section to
scaffold.toml. post_deploy is a list of shell commands executed in order;
the run aborts at the first non-zero exit:
[run]
post_deploy = [
"lgs spel -- --idl $SCAFFOLD_IDL_DIR/counter.json -p $SCAFFOLD_GUEST_BIN init",
"lgs spel -- --idl $SCAFFOLD_IDL_DIR/counter.json -p $SCAFFOLD_GUEST_BIN increment --by 5",
]The lgs spel -- passthrough invokes the project-vendored spel binary
so hooks pick up the same pinned version deploy used.
A single command may also be written as a plain string for brevity:
post_deploy = "echo done".
Each hook runs via sh -c with cwd set to the project root and these
environment variables pre-set:
| Variable | Value |
|---|---|
SEQUENCER_URL |
http://127.0.0.1:<port> (from scaffold.toml) |
NSSA_WALLET_HOME_DIR |
Absolute path to project wallet directory |
SCAFFOLD_PROJECT_ROOT |
Absolute path to project root |
SCAFFOLD_IDL_DIR |
Absolute path to IDL output directory |
SCAFFOLD_PROGRAM_ID |
risc0 image ID (hex) of the deployed program. Set only when the project has exactly one deployable program; unset if spel inspect cannot extract the ID |
SCAFFOLD_GUEST_BIN |
Absolute path to the guest .bin. Set only when the project has exactly one deployable program |
SCAFFOLD_PROGRAM_ID and SCAFFOLD_GUEST_BIN are unset for
multi-program projects so hooks fail loudly rather than silently
picking up the wrong program.
To run a different hook without editing scaffold.toml:
lgs run --post-deploy "scripts/smoke.sh"
lgs run --post-deploy "step-a" --post-deploy "step-b" # repeatable
lgs run --no-post-deploy # skip all hooks--post-deploy and --no-post-deploy conflict with each other and
both override whatever [run].post_deploy defines.
Checkpoint commands:
logos-scaffold localnet status
logos-scaffold doctorTo use the LEZ Framework for an ergonomic developer experience similar to Anchor on Solana:
logos-scaffold new <name> --template lez-framework
See LEZ Framework Template for details.
- If
localnet startfails, inspect:
logos-scaffold localnet logs --tail 200- If status reports
ownership: foreign, stop external listeners on127.0.0.1:3040before starting scaffold localnet. - If status reports stale state, run:
logos-scaffold localnet stop
logos-scaffold localnet start- JSON status for tooling:
logos-scaffold localnet status --json
logos-scaffold doctor --json
logos-scaffold report --tail 500Run examples directly without passing .bin paths:
cargo run --bin run_hello_world -- <public_account_id>
cargo run --bin run_hello_world_private -- <private_account_id>
cargo run --bin run_hello_world_with_authorization -- <public_account_id>
cargo run --bin run_hello_world_with_move_function -- write-public <public_account_id> <text>
cargo run --bin run_hello_world_through_tail_call -- <public_account_id>
cargo run --bin run_hello_world_through_tail_call_private -- <private_account_id>
cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pdaOptional overrides for custom binaries:
export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/target/riscv-guest/example_program_deployment_methods/example_program_deployment_programs/riscv32im-risc0-zkvm-elf/release
cargo run --bin run_hello_world -- --program-path "$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin" <public_account_id>
cargo run --bin run_hello_world_through_tail_call_private -- --simple-tail-call-path "$EXAMPLE_PROGRAMS_BUILD_DIR/simple_tail_call.bin" --hello-world-path "$EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin" <private_account_id>