Write and deploy an LEZ program with logos-scaffold
Use logos-scaffold to create, build, and deploy a guest program on a local Logos Execution Zone sequencer.
This document is accurate for logos-scaffold 0.4.0 built from master at commit d4b15ff, which pins LEZ v0.1.2. It deploys to a local sequencer only; see Deploy to the testnet.
logos-scaffold is a project scaffold and CLI tool that manages the full lifecycle of a LEZ guest program—from project creation to deployment. It pins LEZ and SPEL dependencies, builds a project-local sequencer, and handles wallet interactions, so you can focus on writing your program logic.
This guide walks each stage separately so you can see what the tool does. Once the pieces are familiar, logos-scaffold run chains them into a single command. For what else scaffold manages, including Basecamp modules, see About Logos Scaffold.
-
A supported OS:
- Linux: x86_64
- macOS
-
git, and Rust 1.81 or newer withcargo. -
The Unix process helpers
lsof,ps, andkill, andcurl. -
The RISC Zero toolchain, installed with
rzup:curl -L https://risczero.com/install | bash # installs rzup; restart your shell afterwardsrzup install rustrzup install cargo-risczero 3.0.5rustbuilds the guest programs.cargo-risczeroprovidesr0vm, which the sequencer needs to execute transactions, and it must be the version LEZv0.1.2pins,3.0.5: scaffold only looks for that version, under~/.risc0/extensions/. Without it,localnet startstill reportslocalnet ready, with a warning aboutRISC0_SERVER_PATH, anddeploythen fails. If you installedrzupwith a customRISC0_HOME, exportRISC0_SERVER_PATHwith the full path to thatr0vm. -
Docker, with the daemon running. Scaffold builds guest programs inside the pinned
risczero/risc0-guest-buildercontainer by default, so that aprogram_idis the same on every machine. Without Docker, opt out as described in Step 5. -
About 5 GB of free disk space for the pinned LEZ and
spelbuilds.
Nix is not needed for this guide, only for scaffold's basecamp commands. If it is not installed, logos-scaffold doctor reports a tool nix warning that you can ignore.
What to expect
- You can create an LEZ program project and write a guest program that runs inside the RISC0 zkVM.
- You can build your program and deploy it to a project-local LEZ sequencer.
- You can call your deployed program and repeat the whole loop with one command.
Step 1: Install logos-scaffold
-
Install the CLI from the scaffold repository, pinned to the commit this guide was tested with:
cargo install --locked --git https://github.com/logos-co/scaffold --rev d4b15ff logos-scaffoldThis installs two binaries on your PATH:
logos-scaffoldand the shorter aliaslgs. They are functionally identical. Thelogos-scaffold0.4.0 release on crates.io predates the Docker guest build in Step 5, socargo install logos-scaffoldgives you a CLI without--guestand with guest artifacts in a different place. -
Verify the installation:
logos-scaffold --version
Step 2: Create a new project
-
Create a new LEZ program project. Replace
my-programwith your project name:logos-scaffold new my-programcd my-programThis generates a project with the
defaulttemplate, which includes sample guest programs and runner scripts. For an Anchor-style framework with IDL and generated client bindings, pass--template lez-frameworkinstead; this guide follows the default template.infonewalso clones the pinned LEZ source into scaffold's shared cache; with the builds from Step 3 the cache grows to about 3.5 GB. To keep it somewhere else, pass--cache-root <path>or setLOGOS_SCAFFOLD_CACHE_ROOT. -
Inspect the project layout:
my-program/├── scaffold.toml # Project configuration and dependency pins├── methods/│ └── guest/│ └── src/bin/ # Guest programs run inside the RISC0 zkVM├── src/│ └── bin/ # Runner scripts that submit transactions├── AGENTS.md # Guidance for AI coding assistants├── .claude/skills/ # The same guidance for Claude Code├── .cursor/rules/ # The same guidance for Cursor└── .scaffold/ # Local state, wallet home, and build artifacts
Step 3: Set up the project
-
Run
setupto sync the LEZ and SPEL repositories to their pinned commits, build the project-local sequencer and wallet binaries, and seed the default wallet:warningScaffold unlocks the wallet it seeds with a deterministic local password, so the onboarding flow needs no prompts. To use your own, export it before the first
setup(or the firstrun, which chainssetup):export LOGOS_SCAFFOLD_WALLET_PASSWORD='<your-local-dev-password>'If the storage was already created under the default password, export the override and re-seed with
logos-scaffold run --reset. These are development-only keys; never use them for real funds.logos-scaffold setupOn a cold cache this step builds the sequencer, wallet, and
spelfrom source, which takes about 20 minutes on a four-core machine. Later runs reuse the build. -
Check the project's environment.
doctorreports missing tools and configuration problems, with a next step for each:logos-scaffold doctorAt this point expect three
WARNrows about the sequencer, because the localnet is not running yet. Step 6 clears them. AFAILon theguest buildrow meansdockerorcargo-risczerois not on yourPATH; see Step 5.
Step 4: Write your guest program
Guest programs run inside the RISC0 zkVM and define the on-chain logic of your LEZ program. Each guest program in methods/guest/src/bin/ becomes a deployable program with its own program_id.
-
Open the sample guest program:
$EDITOR methods/guest/src/bin/hello_world.rs -
Read how the program works. It receives a
ProgramInputstruct via the zkVM environment, applies your logic, and writes aProgramOutputstruct to the journal. The sequencer verifies the proof and updates the on-chain account state.Key concepts:
- Instructions are passed as bytes.
hello_worldtakes its instruction as a rawVec<u8>and appends it to the account's data. - Account data is stored in
AccountWithMetadatastructs. - Use
RISC0_DEV_MODE=1during development to skip ZK proof generation for faster iteration.
- Instructions are passed as bytes.
Step 5: Build the project
-
Build the workspace. In development, use
RISC0_DEV_MODE=1to skip proof generation:RISC0_DEV_MODE=1 logos-scaffold buildThe build compiles the workspace, then builds each guest program inside the pinned
risczero/risc0-guest-builderDocker image and writes the.binartifacts totarget/riscv-guest-docker/riscv32im-risc0-zkvm-elf/docker/. The same source then produces the same bytes, and therefore the sameprogram_id, on any machine. The first build takes several minutes.If the Docker daemon is not running,
buildstops straight away:error: [build].guest = "docker" needs a running Docker daemon; `docker info` failed. -
To build without Docker, use the host RISC Zero toolchain instead. For one build, pass
--guest local:RISC0_DEV_MODE=1 logos-scaffold build --guest localTo make it the project default, which
logos-scaffold runneeds because it has no--guestflag, add this toscaffold.toml:[build]guest = "local"A local build writes the artifacts to
target/riscv-guest/…/riscv32im-risc0-zkvm-elf/release/instead. Itsprogram_iddepends on your host's Rust and clang versions, so it can differ from the one another machine builds from the same source. Eachbuildremoves the other mode's artifacts, sodeployalways ships the output of the lastbuild.logos-scaffold doctorshows the active mode in itsguest buildrow.
Step 6: Start a local sequencer
-
Start a project-local sequencer to deploy your program to:
RISC0_DEV_MODE=1 logos-scaffold localnet startstartreturns once the sequencer process is alive and its RPC port answers. The sequencer is daemonised and survives terminal or tmux session closure. Uselogos-scaffold localnet statusto check that it is running andlogos-scaffold localnet stopto stop it.warningThe local sequencer always uses port
3040. If something else already listens there, such as another scaffold project's sequencer,localnet startfails withAddress already in use (os error 98), andlocalnet statusreportsownership: foreign. Stop the other process first. Changingportunder[localnet]inscaffold.tomldoes not help: the sequencer still binds3040and the wallet keeps pointing at it, sodeployandwallet topupwould talk to the other sequencer.For any other failure, read the sequencer log with
logos-scaffold localnet logs --tail 200. To wipe chain state and start over, runlogos-scaffold localnet reset --yes.
Step 7: Deploy your program
-
Deploy all guest programs to the running sequencer:
RISC0_DEV_MODE=1 logos-scaffold deploy- After each successful submission,
logos-scaffoldprintsprogram_id: <hex>, the RISC0 image ID computed locally from the submitted ELF. It printsprogram_id: unavailableif the project'sspelbinary has not been built yet. The example runner scripts in Step 8 load the program from its embedded ELF, so you do not need to copy aprogram_idto complete this guide. deploychecks that the sequencer is reachable before submitting, and stops with a hint if it is not. It submits one program per block, so deploying the five sample programs takes about a minute.- Add
--jsonfor machine-readable output: one object per program under{"deploys": [...]}, or a single object with--program-path … --json.
infodeployconfirms submission, not inclusion. Redeploying a program that is already on the sequencer still reportssubmitted, while the sequencer skips the transaction and logsProgramAlreadyExists, which you can see withlogos-scaffold localnet logs. - After each successful submission,
-
To deploy a specific program by name:
RISC0_DEV_MODE=1 logos-scaffold deploy hello_world
Step 8: Interact with your program
Use the project-local wallet CLI to submit transactions to your deployed program. The wallet is available at logos-scaffold wallet.
-
With the sequencer from Step 6 running, top up the default wallet on your local network, then list your accounts (
wallet listshows accounts, not a balance):logos-scaffold wallet topuplogos-scaffold wallet listtopupinitialises the account first if it is new, then claims from the Piñata faucet. To confirm the wallet can reach the sequencer, runlogos-scaffold wallet -- check-health. Everything after--goes to the project's wallet binary unchanged. -
Create a fresh public account for the program to claim.
hello_worldonly writes to an account that is uninitialised or already owned by the program, so the topped-up default account does not work:logos-scaffold wallet -- account new publicThe output includes
account_id Public/<ID>. Pass the part afterPublic/to the runner. -
Run one of the example runner scripts that submit transactions to your program:
export NSSA_WALLET_HOME_DIR="$(pwd)/.scaffold/wallet"export LEE_WALLET_HOME_DIR="$(pwd)/.scaffold/wallet"RISC0_DEV_MODE=1 cargo run --bin run_hello_world -- <ID>The LEZ
v0.1.2wallet that scaffold pins by default readsNSSA_WALLET_HOME_DIR, and LEZ v0.2.0 and later readLEE_WALLET_HOME_DIR. Exporting both keeps the runner working on either pin.The runner prints the transaction hash. Once the next block is produced, the account holds the greeting
Hola mundo!, hex-encoded indata:logos-scaffold wallet -- account get --account-id Public/<ID>{"balance":0,"program_owner":"...","data":"486f6c61206d756e646f21","nonce":1}The runner scripts in
src/bin/demonstrate how to construct and sign aPublicTransaction, set theprogram_id, encode an instruction, and submit the transaction via the sequencer RPC.
Use logos-scaffold run for the inner loop
Steps 5 to 8 are often repeated when working on developing an LEZ program. logos-scaffold run chains them into one command, and works with no configuration:
RISC0_DEV_MODE=1 logos-scaffold run
It runs these steps in order, printing progress for each:
- Build, which chains
setupinternally. - Build the IDL, a no-op for projects that are not built on the
lez-frameworktemplate. - Start localnet, or reuse the one already running.
- Top up the default wallet.
- Deploy, skipped when the guest binaries, IDL, config, and sequencer are all unchanged since the last
run. A manuallogos-scaffold deploydoes not count, so the firstrundeploys again. - Run
post_deployhooks, if the project configures any.
Useful flags:
| Flag | Effect |
|---|---|
--profile NAME | Select a named pipeline from [run.profiles.<name>]. |
--reset / --no-reset | Wipe sequencer state and wallet and re-seed before the run, or override a config-set default. |
--post-deploy <cmd> | Replace the configured hooks for this run. Repeatable. |
--no-post-deploy | Skip hooks entirely. |
--watch | Re-run the pipeline when files change. |
Configure the loop in scaffold.toml:
[run]
post_deploy = ["cargo run --bin run_hello_world -- <ID>"]
[run.profiles.demo]
topup = false
deploy = false
post_deploy = ["scripts/demo.sh"]
topup = falsesuits a project that funds its own accounts, anddeploy = falseone that deploys from a hook. Both default totrue.
A named profile is used as-is: selecting one shadows the inline [run] values instead of inheriting them. A key the profile does not state falls back to its own default, not to your [run] value. Set every key you need in each profile.
Hooks run through sh -c from the project root with these variables set:
| Variable | Value |
|---|---|
SEQUENCER_URL | The localnet RPC URL. |
NSSA_WALLET_HOME_DIR, LEE_WALLET_HOME_DIR | The project wallet directory, under both the name LEZ v0.1.2 reads and the one v0.2.0 and later read. |
SCAFFOLD_PROJECT_ROOT, SCAFFOLD_IDL_DIR | Absolute paths to the project root and the IDL output directory. |
SCAFFOLD_TOPUP_SKIPPED, SCAFFOLD_DEPLOY_SKIPPED | 1 or 0. Always set, so branch on the value rather than on whether the variable exists. |
SCAFFOLD_PROGRAMS | Space-separated names of the project's deployable programs. |
SCAFFOLD_PROGRAM_ID, SCAFFOLD_GUEST_BIN | The deployed program's image ID and guest binary. Set only when the project has exactly one deployable program, so a multi-program project fails loudly instead of picking the wrong one. |
run covers the deploy loop only. It does not run any basecamp command; for that side of scaffold see Develop a Logos module with Logos Scaffold.
Deploy to the testnet
logos-scaffold cannot deploy to the current LEZ public testnet. Use the local sequencer from this guide.
The public testnet runs LEZ v0.3, and scaffold pins v0.1.2 by default. Pointing the project wallet at https://testnet.lez.logos.co/ fails the compatibility check: logos-scaffold wallet -- check-health panics with Missing authenticated transfer ID from remote, and logos-scaffold doctor reports FAIL | wallet usability.
To deploy to the testnet today, set up a standalone wallet as described in Run an LEZ wallet via the CLI, built from the LEZ release the testnet runs, and use its deploy-program command with a guest program built against that same release. Check the wallet with wallet check-health first: at the time of writing, a wallet built from LEZ v0.3.1 passes against the testnet and one built from scaffold's v0.1.2 pin does not.