Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 30 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# spel-framework
# SPEL — Smart Program Execution Layer

[![CI](https://github.com/logos-co/spel/actions/workflows/ci.yml/badge.svg)](https://github.com/logos-co/spel/actions/workflows/ci.yml)

Developer framework for building SPEL programs — inspired by [Anchor](https://www.anchor-lang.com/) for Solana.
Developer framework for building [LEZ](https://github.com/logos-blockchain/lssa) programs — inspired by [Anchor](https://www.anchor-lang.com/) for Solana.

Write your program logic with proc macros. Get IDL generation, a full CLI with TX submission, and project scaffolding for free.

## Documentation

- **[Tutorial: Build Your First LEZ Program](docs/tutorial.md)** — step-by-step guide from zero to deployed program
- **[Reference Docs](docs/reference/)** — macros, types, CLI, IDL, and client generation
- **[Multi-Seed PDA Guide](docs/multi-seed-pda.md)** — advanced PDA derivation patterns

## Quick Start

### Scaffold a new project
Expand All @@ -16,7 +22,7 @@ spel init my-program
cd my-program
```

This generates a complete project:
This generates a complete project with a `Cargo.lock` for reproducible builds:

```
my-program/
Expand All @@ -27,6 +33,7 @@ my-program/
│ └── src/lib.rs
├── methods/
│ └── guest/ # RISC Zero guest (runs on-chain)
│ ├── Cargo.lock # Pinned deps for reproducible Docker builds
│ └── src/bin/my_program.rs
└── examples/
└── src/bin/
Expand All @@ -41,16 +48,14 @@ make build # Build the guest binary (risc0)
make idl # Generate IDL from #[lez_program] annotations
make deploy # Deploy to sequencer
make cli ARGS="--help" # See auto-generated commands
make cli ARGS="-p <binary> initialize --owner-account <BASE58>"
make cli ARGS="-p <binary> -- initialize --owner <BASE58>"
```

## Writing Programs

```rust
#![no_main]

use nssa_core::account::AccountWithMetadata;
use nssa_core::program::AccountPostState;
use spel_framework::prelude::*;

risc0_zkvm::guest::entry!(main);
Expand All @@ -67,11 +72,8 @@ mod my_program {
#[account(signer)]
owner: AccountWithMetadata,
) -> SpelResult {
// Your logic here
Ok(SpelOutput::states_only(vec![
AccountPostState::new_claimed(state.account.clone(), Claim::Authorized),
AccountPostState::new(owner.account.clone()),
]))
// Your logic here — mutate state.account.data if you need to write state.
Ok(SpelOutput::execute(vec![state, owner], vec![]))
}

#[instruction]
Expand All @@ -84,21 +86,21 @@ mod my_program {
amount: u128,
) -> SpelResult {
// Your logic here
Ok(SpelOutput::states_only(vec![
AccountPostState::new(state.account.clone()),
AccountPostState::new(recipient.account.clone()),
AccountPostState::new(sender.account.clone()),
]))
Ok(SpelOutput::execute(vec![state, recipient, sender], vec![]))
}
}
```

> **Note:** Import everything from `spel_framework::prelude::*` — this provides `AccountWithMetadata`, `SpelOutput`, `SpelResult`, `SpelError`, `AccountPostState`, `Claim`, `AutoClaim`, `BorshSerialize`, `BorshDeserialize`, and more. Do not import from `nssa_core` directly to avoid version conflicts.
>
> The `#[lez_program]` macro reads each handler's `#[account(…)]` attributes and generates the correct claim metadata for every entry in the `vec![…]` passed to `SpelOutput::execute(…)` — you never write `AccountPostState::new_claimed(…, Claim::Authorized)` by hand. (That legacy API is still available via the `#[deprecated]` `SpelOutput::states_only` / `with_chained_calls` constructors.)

### Account Attributes

| Attribute | Description |
|-----------|-------------|
| `#[account(mut)]` | Account is writable |
| `#[account(init)]` | Account is being created (use `new_claimed`) |
| `#[account(init)]` | Account is being created; the macro emits the correct `AutoClaim::Claimed(…)` automatically when you return `SpelOutput::execute(…)` |
| `#[account(signer)]` | Account must sign the transaction |
| `#[account(pda = literal("seed"))]` | PDA derived from a constant string |
| `#[account(pda = account("other"))]` | PDA derived from another account's ID |
Expand Down Expand Up @@ -145,7 +147,7 @@ Every program gets a full CLI for free. The wrapper is just:
```rust
#[tokio::main]
async fn main() {
spel_cli::run().await;
spel::run().await;
}
```

Expand All @@ -156,7 +158,7 @@ This provides:
- risc0-compatible serialization
- Transaction building and submission with wallet integration
- `--dry-run` mode for testing
- `inspect` subcommand to extract ProgramId from binaries
- `inspect` subcommand to extract ProgramId from binaries and decode account data

### Account Types

Expand Down Expand Up @@ -205,15 +207,15 @@ It reads the `#[lez_program]` annotations at compile time and generates a comple

The generated IDL is a superset of the lssa-lang IDL spec. In addition to our core fields, each instruction includes:

- **discriminator** -- SHA256 of global:name, first 8 bytes, matching lssa-lang convention
- **execution** -- public/private_owned flags (default: public execution)
- **variant** -- PascalCase variant name
- **discriminator** SHA256 of global:name, first 8 bytes, matching lssa-lang convention
- **execution** public/private_owned flags (default: public execution)
- **variant** PascalCase variant name

Each account field includes:

- **visibility** -- list of visibility tags (default: public)
- **visibility** list of visibility tags (default: public)

These fields are optional and backward-compatible -- existing IDL consumers that do not know about them will simply ignore them.
These fields are optional and backward-compatible existing IDL consumers that do not know about them will simply ignore them.

## CLI Usage

Expand All @@ -233,6 +235,9 @@ spel inspect <account-id> --idl my_program-idl.json --type VaultState
# Same, but supply raw borsh bytes directly instead of fetching from the network
spel inspect <account-id> --idl my_program-idl.json --type VaultState --data <borsh-hex>

# Inspect with raw data (offline, no sequencer needed)
lez-cli inspect --data <hex-encoded-borsh> --idl program-idl.json

# Show available commands
spel --idl program-idl.json --help

Expand Down Expand Up @@ -344,4 +349,4 @@ spel inspect 0000...0000 \

## License

MIT
Dual-licensed under [MIT](LICENSE-MIT) and [Apache-2.0](LICENSE-APACHE-v2).
15 changes: 15 additions & 0 deletions docs/reference/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPEL Framework Reference

Comprehensive API reference for the SPEL framework (`logos-co/spel`). This document covers every macro, type, CLI command, IDL schema, and code generation feature.

For a guided walkthrough, see the [Tutorial](../tutorial.md).

---

## Reference Pages

- [**Macros**](macros.md) — `#[lez_program]`, `#[instruction]`, `generate_idl!`, and generated validation functions
- [**Types**](types.md) — Framework types: `SpelOutput`, `SpelError`, `AccountConstraint`, `ChainedCall`, `PdaSeed`, and the prelude
- [**CLI**](cli.md) — All `spel` commands (`init`, `inspect`, `idl`, `pda`, instruction execution) with flags, examples, and type format table
- [**IDL Format**](idl.md) — IDL JSON schema, instruction/account/arg definitions, discriminators, and lssa-lang compatibility fields
- [**Client Code Generation**](client-gen.md) — `spel-client-gen` CLI, library API, generated Rust client, C FFI wrappers, C header, and C++/Qt integration example
Loading