lez-programs/CLAUDE.md
2026-07-02 18:16:40 +02:00

7.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Overview

This repo contains essential programs for the Logos Execution Zone (LEZ) — a zkVM-based execution environment built on RISC Zero. Programs run inside the RISC Zero zkVM (riscv32im-risc0-zkvm-elf target) and interact with the LEZ runtime via the nssa_core library from logos-execution-zone.

Five programs are implemented:

  • token — Fungible and non-fungible token program (create, mint, burn, transfer, print NFTs)
  • amm — Automated market maker (constant product AMM with add/remove liquidity and swaps)
  • ata — Associated Token Account program (derives and initializes deterministic token accounts for a given owner and token definition)
  • stablecoin — Collateral-backed position program (open positions, repay debt, withdraw collateral)
  • twap_oracle — TWAP oracle (provides canonical on-chain price accounts consumed by other programs)

Build Commands

# Check all workspace crates (skips expensive guest ZK builds)
make clippy

# Run all tests (dev mode skips ZK proof generation)
make test

# Run tests for a single package
RISC0_DEV_MODE=1 cargo test -p token_program
RISC0_DEV_MODE=1 cargo test -p amm_program
RISC0_DEV_MODE=1 cargo test -p ata_program
RISC0_DEV_MODE=1 cargo test -p stablecoin_program
RISC0_DEV_MODE=1 cargo test -p twap_oracle_program

# Format
make fmt

# Build all guest ZK binaries
make build-programs

# Build one guest directly only when debugging a single guest build
cargo risczero build --manifest-path programs/<program>/methods/guest/Cargo.toml

make build-programs uses scripts/build-guests.sh and scripts/build-guests.Dockerfile to compile every guest crate in one Docker BuildKit build. Cargo git, registry, and target artifacts are shared through BuildKit cache mounts. Each raw guest ELF is packaged into the deployable RISC Zero .bin format, and only those final program binaries are exported.

Built binaries output to: target/guest/<program>.bin

Use RISC0_DOCKER_CONTAINER_TAG to override the guest builder image tag. Use RISC0_BUILD_CACHE_ID to isolate BuildKit caches for clean benchmarks or parallel experiments. Use RISC0_DOCKER_BUILD_NETWORK to opt in to a custom Docker build network mode, such as host, when the default Docker build network is not sufficient.

IDL Generation

Using the idl-gen crate (no external toolchain required — this is what CI uses):

make idl

Or individually per program:

cargo run -p idl-gen -- programs/<program>/methods/guest/src/bin/<program>.rs > artifacts/<program>-idl.json

Alternatively, using the spel CLI (requires the SPEL toolchain):

spel generate-idl programs/<program>/methods/guest/src/bin/<program>.rs > artifacts/<program>-idl.json

Generated IDL files live in artifacts/. CI checks that every program under */methods/guest/src/bin/ has a corresponding artifacts/<program>-idl.json that matches the source.

Deployment

wallet and spel are CLI tools that ship with the SPEL toolchain. wallet requires NSSA_WALLET_HOME_DIR to point to a directory containing the wallet config.

Note: spel and wallet may use different versions of the wallet package. If spel --idl <IDL> <PROGRAM_FUNCTION> ... fails, ensure seq_poll_timeout_millis is set in the wallet config at ~/.nssa/wallet.

For testnet and mainnet deployments, build guest binaries with the release profile in each guest manifest:

[profile.release]
debug = 0
strip = "symbols"

That profile is part of program identity. After every release build, inspect the binary and update every value that depends on the ImageID before submitting transactions: deployed program IDs, client/config files, PDA-derived account addresses, AMM token_program_id and twap_oracle_program_id, and ATA token_program_id inputs. Do not mix raw or old ImageIDs with release-profile binaries.

# Build all guest binaries with the shared BuildKit cache
make build-programs

# Deploy a program binary to the sequencer
wallet deploy-program target/guest/<program>.bin

# Inspect the ProgramId of a built binary
spel inspect target/guest/<program>.bin

Workspace Structure

Cargo.toml              # Workspace root (excludes guest crates)
programs/
  token/
    core/src/lib.rs     # Data types & Instruction enum (shared with guest)
    src/                # Program logic: burn, mint, transfer, initialize, new_definition, print_nft
    methods/            # Host-side zkVM method embedding (build.rs uses risc0_build::embed_methods)
    methods/guest/      # Guest binary (separate workspace, riscv32im target)
  amm/
    core/src/lib.rs     # Data types, Instruction enum, PDA computation helpers
    src/                # Program logic: add, remove, swap, new_definition
    methods/            # Host-side zkVM method embedding
    methods/guest/      # Guest binary (separate workspace)
  ata/
    core/src/lib.rs     # Data types & Instruction enum
    src/                # Program logic: create, burn, transfer
    methods/            # Host-side zkVM method embedding
    methods/guest/      # Guest binary (separate workspace)
  stablecoin/
    core/src/lib.rs     # Data types & Instruction enum
    src/                # Program logic: open_position, repay_debt, withdraw_collateral
    methods/            # Host-side zkVM method embedding
    methods/guest/      # Guest binary (separate workspace)
  twap_oracle/
    core/src/lib.rs     # Data types & Instruction enum
    src/                # Program logic: noop (price account initialization)
    methods/            # Host-side zkVM method embedding
    methods/guest/      # Guest binary (separate workspace)
  integration_tests/
    tests/              # End-to-end tests through the zkVM (token, amm, ata)
apps/
  amm/                  # QML-based UI for the AMM program (Nix flake)

Architecture

Each program follows a layered pattern:

  1. *_core crate — shared types (Instructions, account data structs) serialized with Borsh for on-chain storage, serde for instruction passing. Also contains PDA seed computation (amm_core).

  2. Program crate — pure functions that take AccountWithMetadata inputs and return Vec<AccountPostState> (and Vec<ChainedCall> for AMM). No I/O or state — all state transitions are deterministic and testable without the zkVM.

  3. methods/guest — the guest binary wired to the LEZ framework via spel-framework using the #[lez_program] and #[instruction] proc macros. This is what gets compiled to RISC-V and ZK-proven.

  4. methods — host crate that embeds the guest ELF for use in tests and deployment.

Key Patterns

Account data serialization: On-chain account data uses Borsh (BorshSerialize/BorshDeserialize). Instructions use serde JSON. Both implement TryFrom<&Data> and From<&T> for Data for conversion.

Program-Derived Addresses (PDAs): The AMM uses SHA-256-based PDAs (compute_pool_pda, compute_vault_pda, compute_liquidity_token_pda in amm_core) to derive deterministic account addresses for pools, vaults, and liquidity tokens.

Chained calls: The AMM's swap and liquidity operations compose with the token program via ChainedCall — the AMM instructs the token program to execute transfers as part of the same atomic operation.

Testing: Tests call program functions directly (no zkVM overhead). Set RISC0_DEV_MODE=1 to skip ZK proof generation when running integration tests that go through the zkVM. The Rust toolchain version is pinned in rust-toolchain.toml.