logos-devnet 0.3.6

A local LEZ chain for Logos development: one command to start it, fund accounts and deploy programs
logos-devnet-0.3.6 is not a library.

logos-devnet

A local LEZ chain for Logos development. One command starts a sequencer on your machine with 1 second blocks and a funded faucet, and a project file in your repo says what else it needs: programs, a hundred funded test accounts, logosctl sessions. A transfer lands in about 2 seconds and a program deploy in about 10, and you never run out of testnet funds.

Keep the public testnet for final checks. Messaging (chat, discovery) isn't part of this: it stays on the Logos fleets.

Install

cargo install --locked logos-devnet

Or take a binary from the latest release, built for macOS (aarch64-apple-darwin, x86_64-apple-darwin) and Linux (x86_64-unknown-linux-musl, aarch64-unknown-linux-musl, static, so any distribution), and check it against its .sha256:

v=v0.3.6 t=aarch64-apple-darwin
curl -fsSL --remote-name-all https://github.com/drptbl/logos-devnet/releases/download/$v/logos-devnet-$v-$t.tar.gz{,.sha256}
shasum -a 256 -c logos-devnet-$v-$t.tar.gz.sha256 \
  && tar -xzf logos-devnet-$v-$t.tar.gz \
  && mkdir -p ~/.local/bin && install logos-devnet-$v-$t/logos-devnet ~/.local/bin/

Put ~/.local/bin on your PATH if it isn't. cargo install --locked --git https://github.com/drptbl/logos-devnet builds the latest main.

It runs on macOS and Linux, and needs Docker (OrbStack, Docker Desktop or Colima). There's no Windows build. With --native you need git, a Rust toolchain and LEZ's build dependencies instead: clang, pkg-config and OpenSSL (brew install pkg-config openssl on macOS), plus libpcsclite-dev on Linux. The first native build takes a few minutes.

Use it

logos-devnet doctor                                # what's missing, and how to fix it
logos-devnet up                                    # start, or resume
logos-devnet fund Public/<your account id>         # 10B lepta from the faucet
logos-devnet deploy my-program my_program.bin      # prints LOGOS_PROGRAM_MY_PROGRAM=<id>
logos-devnet wallet-config ~/my-app/devnet-wallet  # a wallet_config.json for lez_core
source ~/.logos-devnet/env                         # LOGOS_DEVNET_SEQUENCER, LOGOS_PROGRAM_*
Command What it does
up Starts the devnet, or resumes it, and makes it match your logos-devnet.toml. Takes --docker or --native, --lez <tag>, --port, --block-time, --max-txs-per-block, --dev-mode and --log-filter, and remembers them. --rebuild builds the image or binaries again.
down Stops it. The chain is kept.
reset Deletes the chain and starts a new one, every account group funded at genesis. Takes the same options as up.
status What runs, the chain head, the faucet, programs, account groups and sessions.
doctor Checks the project file, Docker and the image (or git and cargo with --native), whether the chain takes the project's settings, the port, the faucet and, when sessions need it, logosctl, and says how to fix what fails.
fund <account> [amount] Sends lepta from the faucet to a public account, 10B by default.
deploy <name> <bin> [--mutable] Deploys a program, from its .bin (what cargo risczero build makes, not the bare ELF), and adds LOGOS_PROGRAM_<NAME> and LOGOS_PROGRAM_<NAME>_HEX to the env file. Names are lower-case letters, digits and -. With --mutable, a new build updates it in place.
wallet-config <dir> Writes a wallet_config.json pointing at the devnet into a new wallet directory.
env Prints the env file.
logs [-f] Prints the sequencer's log.
session new <name> [--module <m>] Makes a logosctl session with a funded devnet wallet, see below.
session ls, session env <name>, session rm <name> Lists sessions, prints one's env to source, deletes one.
accounts new <name> --count <n> [--amount <a>] Makes a group of funded accounts, see below.
accounts ls Lists account groups.

Every command takes --json for scripts and agents, --config <file> for a project file elsewhere, and --home <dir>. A bad option changes nothing: up and reset check it, and build what they need, before they stop the running chain.

examples/ has LEZ's hello-world tutorial running on a devnet, two logosctl sessions paying each other, and a GitHub Actions workflow to copy. For a whole project on it, logos-agents deploys a released program and runs two sessions from its logos-devnet.toml.

For AI agents

skills/logos-devnet is an agent skill: the loop to run, the choices that matter, and what to do when logos-devnet refuses. For Claude Code, copy it into ~/.claude/skills/ for every project, or into a project's .claude/skills/:

git clone --depth 1 https://github.com/drptbl/logos-devnet.git /tmp/logos-devnet
mkdir -p ~/.claude/skills && cp -R /tmp/logos-devnet/skills/logos-devnet ~/.claude/skills/

The files are plain Markdown, so other agents can read SKILL.md from their own instructions.

Set up a project

Commit a logos-devnet.toml at your project's root, and logos-devnet up anywhere below it makes the devnet match:

[devnet]                 # optional, the same settings as up's flags
block_time = "1s"
max_txs_per_block = 100
dev_mode = true          # fake proofs: private calls take a second
log_filter = "info,sequencer_core=debug"   # the sequencer's RUST_LOG, for `logs`

[programs]               # name = guest binary, relative to this file
counter = "target/riscv32im-risc0-zkvm-elf/docker/counter.bin"
shop = { path = "target/riscv32im-risc0-zkvm-elf/docker/shop.bin", mutable = true }

[accounts.users]         # a group of funded accounts
count = 100
amount = 5_000_000_000   # lepta each, 10B when left out

[sessions.alice]         # a logosctl session, as session new
modules = ["chat_module"]

up deploys missing programs, makes and funds missing accounts and sessions, and changes nothing the second time (0.6 s). A mutable program whose binary changed is updated in place, with the same id. An immutable one keeps its old build, and up and deploy fail saying so, so tests don't run against it: reset deploys the new build. A module added to a session's modules is installed into it. up checks the whole file before it touches the devnet.

Projects share one devnet, ~/.logos-devnet on port 3040, unless the file sets home, e.g. home = ".logos-devnet", for a devnet of the project's own (give it its own [devnet] port too, if another one runs). On a shared devnet, a [devnet] section that disagrees with the running chain is refused rather than restarting the chain under the other projects: down and up apply it, or reset for lez and dev_mode, which only a new chain takes. A reset there funds every project's account groups again, and each project's next up deploys its programs on the new chain.

Many accounts

An account group is any number of funded accounts for tests with many users: a hundred parties calling one program, say. Declare it in the project file or make one by hand:

logos-devnet accounts new users --count 100 --amount 5000000000

Group names follow the rules for program names, and can't end in -wallet. A group's keys live in one LEZ wallet, ~/.logos-devnet/accounts/users/wallet, which lez_core and the LEZ CLI wallet can both use: LOGOS_DEVNET_ACCOUNTS_USERS_WALLET in the env file points at it, for LEE_WALLET_HOME_DIR. A new chain, from reset or a first up, makes it forget the old one, as it does for sessions: stop an app that has it open first, or it writes the old chain back. Its accounts.json lists every account, and LOGOS_DEVNET_ACCOUNTS_USERS points at it:

{
  "amount": 5000000000,
  "accounts": [
    { "index": 0, "id": "CfcghzvosNM114Akg71vuQyDApHPYpPVC9p4uPFQygye", "hex": "ad54f9c3…" }
  ]
}

A group that exists when a chain starts is funded at genesis, so a hundred accounts, or a thousand, are ready at once. One added to a running chain is funded one transfer at a time, about 2.4 s each (100 accounts took 237 s), so declare groups before the first up, or reset. A group never shrinks: a lower count keeps the accounts it has. A new amount applies to new accounts, and to every account after reset. Making the keys takes longer as a group grows, since each one is written to the group's wallet: 500 took 3 s, and growing a group from 2,000 to 6,000 took about 80 s. It happens once, as keys outlive reset. Each up checks every account is funded, which took about 1 s with 6,000. Accounts that send need more than they move: every call holds about 1.6M lepta of fees up front, so give a sending group at least a few million each (the default is 10B).

How many transactions fit is set by max_txs_per_block. Upstream allows 20 per block, so at 1 s blocks about 20 per second. Measured with every account of a group sending one transfer at the same moment:

max_txs_per_block Burst Landed
20 100 transfers 5 blocks of 20, 5.5 s
100 100 transfers 1 block, 1.6 s
200 200 transfers 197 in 1 block. 3 were refused by the RPC, which takes 100 connections at a time

Up to 1000 is accepted. Above 100 clients at the same instant, the RPC answers 429 to the rest, so retry or spread them.

Point your project at it

The env file is the contract. A project reads LOGOS_DEVNET_SEQUENCER for the RPC URL, and LOGOS_PROGRAM_<NAME> (base58) or LOGOS_PROGRAM_<NAME>_HEX (what lez_core takes as program_id_hex) for its programs, falling back to the testnet values when they're not set.

Program ids stay the same. deploy my-program always deploys from the dev wallet's program-my-program header key, and a program id is that key's account id, so reset and redeploy give you the same LOGOS_PROGRAM_MY_PROGRAM. A second deploy of the same name on the same chain does nothing, unless the program is mutable and the binary changed: then it's updated in place.

Programs are immutable unless you ask, because undisclosed private calls only run against a program that was immutable from the start. A mutable one is for iterating: up after a rebuild takes about 10 s, against a reset. Each update writes new segment accounts, and the old ones stay. A program keeps its mutability on a chain, so a missing --mutable can't freeze it by accident: reset deploys it the other way.

For a wallet, give it its own directory. wallet-config refuses a directory that already holds a wallet, because repointing a testnet wallet at the devnet mixes up its sync state. Then create the wallet the usual way, e.g. logosctl call lez_core create_new str:<dir>/wallet_config.json str:<dir>/storage.json str:, and fund its public account. After a reset, such a wallet still holds the old chain's sync position and private notes, and skips the new chain's blocks up to the old height: make a new one.

Sessions

A session is a throwaway logosctl session wired to the devnet, for an app or an agent that talks to Logos through logosctl. You need logosctl on your PATH.

logos-devnet session new alice                          # lez_core and a funded wallet
logos-devnet session new bob --module chat_module       # plus any module from the catalog
logos-devnet session new carol --amount 5000000         # fund it with less than 10B lepta
source <(logos-devnet session env alice)                # LOGOSCTL_CONFIG_DIR and the wallet

Each session runs its own daemon in ~/.logos-devnet/sessions/<name>, with lez_core and any --module installed from the official catalog. Its wallet points at the devnet, is open in that daemon, and holds a public account the faucet funded. session env prints the devnet's env plus:

Variable
LOGOSCTL_CONFIG_DIR the session, so logosctl and anything built on it talk to its daemon
LOGOS_DEVNET_WALLET_CONFIG the wallet's wallet_config.json
LOGOS_DEVNET_WALLET_STORAGE the wallet's storage.json
LOGOS_DEVNET_ACCOUNT its funded account, Public/<id>

Sessions follow the chain. down stops their daemons. up starts them again, loads their modules and opens their wallets, also after a --port change. reset funds each account again with what it was created with, and makes each wallet forget the old chain: its sync position and the state of its private accounts. Its keys and accounts stay. session rm deletes a session and its wallet.

The catalog's lez_core 0.5.1 is built on LEZ v0.3.1, the default --lez tag, so sessions match it.

What runs

LEZ's own sequencer_service runs, built with two features. standalone replaces Bedrock, the L1, with an in-process mock that accepts every block. prove runs programs in-process, so you don't need r0vm. The default is v0.3.1. --lez picks another v0.3 tag, and a chain only runs one tag: reset --lez <tag> to switch. LEZ v0.4 changes the sequencer's config and commands, and isn't supported yet.

devnet testnet
Block time 1 s (--block-time) 60 s, often several minutes
Funding faucet, funded at genesis testnet faucet
Base fee 8 8
Block timestamp your clock the sequencer's clock
L1 finality, indexer none yes

Fees are the testnet's. Every call holds gas_limit × base fee up front, plus a little for its size, so about 1.6M lepta at a gas_limit of 200,000. The faucet makes that irrelevant.

Private calls still prove on your machine, so they take as long as on the testnet: a shielded transfer from a session took 118 s on an Apple M-series laptop. Dev mode skips that.

Dev mode

up --dev-mode, or dev_mode = true under [devnet], runs the chain with RISC0_DEV_MODE=1: provers make fake proofs and the sequencer accepts them. The same transfer lands in 1 s.

The env file sets RISC0_DEV_MODE for your app and tests, 1 or 0, so a value left in your shell can't send fake proofs to a chain that checks them. Sessions' daemons get it too. Real proofs still work in dev mode, so an app that doesn't set it is only slow.

A chain keeps its dev mode: one holding fake proofs crashes when it restarts without it. reset --dev-mode and reset --dev-mode false start a new chain with or without it, and status shows which runs. Don't use dev mode for anything that should prove a private call is valid.

Where things live

Everything is in ~/.logos-devnet (or LOGOS_DEVNET_HOME):

Path
settings.json LEZ tag, runtime, port, block time, transactions per block, dev mode, log filter, faucet
env the env file
chain/ the sequencer's config, database and keys, and whether the chain runs in dev mode. reset deletes it.
wallet/ the dev wallet: the faucet and each program's header key. Kept across resets.
compose.yaml the Docker runtime's compose file
native/ the native runtime's LEZ checkout, build, log and pid file
sessions/ one directory per session: its logosctl home, wallet and session.json
accounts/ one directory per account group: its wallet and accounts.json
programs.json the binary hash each program was deployed from
lock held while a command changes the devnet, so two projects sharing the home take turns

The RPC listens on 127.0.0.1 only. It has no authentication, so don't publish the port.

JSON

With --json, a command prints one JSON document and nothing else on stdout. Notes go to stderr. The fields:

Command Fields
up, reset, status lez, runtime, sequencer (url, running, block, max_txs_per_block, block_time, dev_mode), faucet (account, balance), programs, account_groups, sessions, env_file
deploy name, id, hex, var, deployed_now, updated
fund account, balance
accounts new, accounts ls name, accounts, amount, file, wallet, var (a list for ls)
session new name, modules, vars
session ls a list of name, running, account, balance, error
env, session env an object of variable to value
wallet-config path
down, session rm message
doctor ok, checks (name, ok, detail, fix)

In CI

The repo is a GitHub Action. On a Linux runner it installs logos-devnet, starts the devnet from your project file and exports the env into the job:

- uses: drptbl/logos-devnet@v0.3.6
  with:
    args: --max-txs-per-block 100     # optional, more options for up
- run: cargo test                     # LOGOS_DEVNET_SEQUENCER, LOGOS_PROGRAM_*, LOGOS_DEVNET_ACCOUNTS_*, RISC0_DEV_MODE are set
- if: failure()
  run: logos-devnet logs              # the sequencer's log, when a test fails

Inputs: version (default: the release tag you use the action at, or, pinned to a commit, the release that commit's Cargo.toml names), config, working-directory, args (space-separated, without shell quoting. Set the home with LOGOS_DEVNET_HOME, not --home), start: "false" to only install, which also works on macOS runners, and logosctl-version (default 0.3.2). When the project file has sessions and no logosctl is on PATH, the action installs that release of the Logos Core CLI, checked against its SHA-256. Output: sequencer. When up itself fails, the action prints the sequencer's log. Starting the devnet needs a Linux runner, for Docker.

The image

ghcr.io/drptbl/logos-devnet-sequencer:<lez tag> holds the sequencer and the LEZ wallet for linux/amd64 and linux/arm64. up pulls it, and builds it locally from docker/Dockerfile when no image is published for your tag. To build it yourself:

docker build --build-arg LEZ_TAG=v0.3.1 -t ghcr.io/drptbl/logos-devnet-sequencer:v0.3.1 - < docker/Dockerfile

License

MIT or Apache-2.0, at your option.