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
1,111 changes: 486 additions & 625 deletions Cargo.lock

Large diffs are not rendered by default.

38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Developer framework for building SPEL 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.
Write your program logic with proc macros. Get IDL generation, a full CLI with TX submission, a Wallet-ready transaction-resolution API, and project scaffolding for free.

## Quick Start

Expand Down Expand Up @@ -158,6 +158,42 @@ This provides:
- `--dry-run` mode for testing
- `inspect` subcommand to extract ProgramId from binaries

### Transaction Resolution API

Use `spel::tx` when an application owns `WalletCore` and needs validated inputs
for a direct Wallet build call. The resolvers select one IDL instruction, resolve
accounts and PDAs, and serialize its arguments in IDL declaration order.

Argument text accepts canonical scalars and JSON containers. It also accepts
the established CLI forms for boolean aliases, options, arrays and vectors,
and program IDs, so applications can use the same values accepted by `spel`.
Canonical JSON remains the preferred representation for structured values.

```rust
use std::collections::BTreeMap;

use spel::tx::{resolve_public_instruction, SpelInstructionRequest};

// `idl`, `program_id`, and `wallet` are owned by the application.
let request = SpelInstructionRequest {
idl: &idl,
instruction: "initialize",
accounts: BTreeMap::new(),
arguments: BTreeMap::new(),
};
let resolved = resolve_public_instruction(request, program_id)?;
let (program_id, accounts, instruction_data) = resolved.into_parts();
let _build = wallet
.build_pub_tx(accounts, instruction_data, program_id)
.await?;
```

For private programs, use `resolve_private_instruction` with a
`ProgramWithDependencies`, then pass the resolved parts to
`WalletCore::build_privacy_preserving_tx`. Resolvers return `SpelTxError` for
invalid IDL or caller input. They do not initialize a wallet, build, prove,
sign, submit, poll, print, or change CLI behavior; WalletCore owns those steps.

### Account Types

Types that represent on-chain account data can be annotated with `#[account_type]`. This causes them to appear in the generated IDL so `spel inspect` can decode raw account bytes into readable JSON.
Expand Down
20 changes: 8 additions & 12 deletions scripts/ffi-call-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,15 @@ for candidate in wallet "$HOME/bin/wallet" "$LSSA_DIR/target/release/wallet"; do
done
[ -n "$WALLET_BIN" ] || fail "wallet not found"

# LEZ v0.2.0 moved the debug configs under a lez/ subdirectory. Prefer the
# new location, fall back to the pre-rc6 path for older LEZ revisions.
if [ -z "${NSSA_WALLET_HOME_DIR:-}" ]; then
# Prefer current LEZ layout, fall back to older checkout layout.
if [ -z "${LEE_WALLET_HOME_DIR:-}" ]; then
if [ -f "${LSSA_DIR}/lez/wallet/configs/debug/wallet_config.json" ]; then
NSSA_WALLET_HOME_DIR="${LSSA_DIR}/lez/wallet/configs/debug"
LEE_WALLET_HOME_DIR="${LSSA_DIR}/lez/wallet/configs/debug"
else
NSSA_WALLET_HOME_DIR="${LSSA_DIR}/wallet/configs/debug"
LEE_WALLET_HOME_DIR="${LSSA_DIR}/wallet/configs/debug"
fi
fi
export NSSA_WALLET_HOME_DIR
# LEZ v0.2.0 renamed the wallet home env var to LEE_WALLET_HOME_DIR.
# Export both so the wallet finds its config on old and new LEZ revisions.
export LEE_WALLET_HOME_DIR="$NSSA_WALLET_HOME_DIR"
export LEE_WALLET_HOME_DIR
WALLET_PASSWORD="${WALLET_PASSWORD:-test}"

# Determine SPEL ref for testing (PR head or commit SHA)
Expand All @@ -111,7 +107,7 @@ CLIENT_GEN_BIN="$SPEL_DIR/target/release/spel-client-gen"
# ─── Step 1: Scaffold project ──────────────────────────────────────────────

log "Step 1: Creating SPEL project (LEZ=${LEZ_TAG})..."
"$SPEL_BIN" init --lez-tag "$LEZ_TAG" --spel-rev "$SPEL_TAG" "$PROJECT_NAME" 2>&1 | tee "$WORK_DIR/init.log" || { echo ''; echo '=== INIT LOG ==='; cat "$WORK_DIR/init.log"; echo '================='; fail "spel init failed"; }
"$SPEL_BIN" init --lez-rev "$LEZ_TAG" --spel-rev "$SPEL_TAG" "$PROJECT_NAME" 2>&1 | tee "$WORK_DIR/init.log" || { echo ''; echo '=== INIT LOG ==='; cat "$WORK_DIR/init.log"; echo '================='; fail "spel init failed"; }
cd "$PROJECT_NAME"

# Regenerate lockfiles so the patch takes effect
Expand Down Expand Up @@ -198,7 +194,7 @@ cd "$WORK_DIR/$PROJECT_NAME"

log " Waiting for sequencer..."
for i in $(seq 1 60); do
if curl -sf -o /dev/null -w '%{http_code}' "$SEQUENCER_URL" 2>/dev/null | grep -qE '200|405'; then
if curl -s -o /dev/null -w '%{http_code}' "$SEQUENCER_URL" 2>/dev/null | grep -qE '200|405'; then
log " ✓ Sequencer up"; break
fi
kill -0 "$SEQ_PID" 2>/dev/null || fail "Sequencer died"
Expand All @@ -221,7 +217,7 @@ done
# ─── Step 5: Update wallet config for correct port ────────────────────────

log "Step 5: Updating wallet config for port ${SEQUENCER_PORT}..."
WALLET_CONFIG="${NSSA_WALLET_HOME_DIR}/wallet_config.json"
WALLET_CONFIG="${LEE_WALLET_HOME_DIR}/wallet_config.json"
if [ -f "$WALLET_CONFIG" ]; then
python3 -c "
import json
Expand Down
18 changes: 7 additions & 11 deletions scripts/init-e2e-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,15 @@ WALLET_BIN="${LSSA_DIR}/target/release/wallet"
SPEL_BIN="/tmp/lssa/target/release/spel"
[ -x "$SPEL_BIN" ] || fail "spel binary not found at $SPEL_BIN"

# LEZ v0.2.0 moved the debug configs under a lez/ subdirectory. Prefer the
# new location, fall back to the pre-rc6 path for older LEZ revisions.
if [ -z "${NSSA_WALLET_HOME_DIR:-}" ]; then
# Prefer current LEZ layout, fall back to older checkout layout.
if [ -z "${LEE_WALLET_HOME_DIR:-}" ]; then
if [ -f "${LSSA_DIR}/lez/wallet/configs/debug/wallet_config.json" ]; then
NSSA_WALLET_HOME_DIR="${LSSA_DIR}/lez/wallet/configs/debug"
LEE_WALLET_HOME_DIR="${LSSA_DIR}/lez/wallet/configs/debug"
else
NSSA_WALLET_HOME_DIR="${LSSA_DIR}/wallet/configs/debug"
LEE_WALLET_HOME_DIR="${LSSA_DIR}/wallet/configs/debug"
fi
fi
export NSSA_WALLET_HOME_DIR
# LEZ v0.2.0 renamed the wallet home env var to LEE_WALLET_HOME_DIR.
# Export both so the wallet finds its config on old and new LEZ revisions.
export LEE_WALLET_HOME_DIR="$NSSA_WALLET_HOME_DIR"
export LEE_WALLET_HOME_DIR
WALLET_PASSWORD="${WALLET_PASSWORD:-test}"

# ─── Setup ─────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -189,7 +185,7 @@ cd "$WORK_DIR/$PROJECT_NAME"

log " Waiting for sequencer..."
for i in $(seq 1 90); do
if curl -sf -o /dev/null -w '%{http_code}' "$SEQUENCER_URL" 2>/dev/null | grep -qE '200|405'; then
if curl -s -o /dev/null -w '%{http_code}' "$SEQUENCER_URL" 2>/dev/null | grep -qE '200|405'; then
log " ✓ Sequencer up"; break
fi
kill -0 "$SEQ_PID" 2>/dev/null || fail "Sequencer died"
Expand All @@ -213,7 +209,7 @@ done
# to the default port. We need to update it to match our non-default port.

log "Step 5: Updating wallet config for port ${SEQUENCER_PORT}..."
WALLET_CONFIG="${NSSA_WALLET_HOME_DIR}/wallet_config.json"
WALLET_CONFIG="${LEE_WALLET_HOME_DIR}/wallet_config.json"
if [ -f "$WALLET_CONFIG" ]; then
python3 -c "
import json
Expand Down
40 changes: 27 additions & 13 deletions scripts/smoke-test-privacy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Usage: ./smoke-test-privacy.sh [WORK_DIR]
#
# Required Environment Variables:
# LEZ_TAG - LEZ revision/tag to test against (e.g., "v0.2.0-rc1" or a commit hash)
# LEZ_TAG - LEZ revision/tag to test against
# LSSA_DIR - Path to logos-execution-zone directory with sequencer built
#
# Optional Environment Variables:
Expand Down Expand Up @@ -87,19 +87,15 @@ for candidate in wallet "$HOME/bin/wallet" "$LSSA_DIR/target/release/wallet"; do
done
[ -n "$WALLET_BIN" ] || fail "wallet not found"

# LEZ v0.2.0 moved the debug configs under a lez/ subdirectory. Prefer the
# new location, fall back to the pre-rc6 path for older LEZ revisions.
if [ -z "${NSSA_WALLET_HOME_DIR:-}" ]; then
# Prefer current LEZ layout, fall back to older checkout layout.
if [ -z "${LEE_WALLET_HOME_DIR:-}" ]; then
if [ -f "${LSSA_DIR}/lez/wallet/configs/debug/wallet_config.json" ]; then
NSSA_WALLET_HOME_DIR="${LSSA_DIR}/lez/wallet/configs/debug"
LEE_WALLET_HOME_DIR="${LSSA_DIR}/lez/wallet/configs/debug"
else
NSSA_WALLET_HOME_DIR="${LSSA_DIR}/wallet/configs/debug"
LEE_WALLET_HOME_DIR="${LSSA_DIR}/wallet/configs/debug"
fi
fi
export NSSA_WALLET_HOME_DIR
# LEZ v0.2.0 renamed the wallet home env var to LEE_WALLET_HOME_DIR.
# Export both so the wallet finds its config on old and new LEZ revisions.
export LEE_WALLET_HOME_DIR="$NSSA_WALLET_HOME_DIR"
export LEE_WALLET_HOME_DIR
WALLET_PASSWORD="${WALLET_PASSWORD:-test}"

# ─── Verify LSSA version matches LEZ_TAG ──────────────────────────────────
Expand Down Expand Up @@ -147,7 +143,7 @@ log " Using local spel: $SPEL_BIN"
# ─── Step 1: Scaffold project ──────────────────────────────────────────────

log "Step 1: Creating SPEL project (LEZ=${LEZ_TAG})..."
"$SPEL_BIN" init --lez-tag "$LEZ_TAG" --spel-rev "$SPEL_TAG" "$PROJECT_NAME" \
"$SPEL_BIN" init --lez-rev "$LEZ_TAG" --spel-rev "$SPEL_TAG" "$PROJECT_NAME" \
> "$LOG_DIR/init.log" 2>&1 || fail "spel init failed (see $LOG_DIR/init.log)"
cd "$PROJECT_NAME"
log " ✓ Project scaffolded"
Expand Down Expand Up @@ -273,7 +269,8 @@ json.dump(cfg, open(dst, "w"))
SEQ_CONFIGS="$SEQ_CONFIG_PATCHED"

cd "$SEQ_HOME"
RUST_LOG=info $SEQUENCER_BIN "$SEQ_CONFIGS" > "$LOG_DIR/sequencer.log" 2>&1 &
RUST_LOG=info $SEQUENCER_BIN --port "$SEQUENCER_PORT" "$SEQ_CONFIGS" \
> "$LOG_DIR/sequencer.log" 2>&1 &
SEQ_PID=$!
sleep 2
if ! kill -0 $SEQ_PID 2>/dev/null; then
Expand All @@ -285,7 +282,7 @@ cd "$WORK_DIR/$PROJECT_NAME"

log " Waiting for sequencer..."
for i in $(seq 1 60); do
if [ $(curl -sf -o /dev/null -w '%{http_code}' "$SEQUENCER_URL" 2>/dev/null | grep -qE '200|405'; echo $?) -eq 0 ]; then
if curl -s -o /dev/null -w '%{http_code}' "$SEQUENCER_URL" 2>/dev/null | grep -qE '200|405'; then
log " ✓ Sequencer up"; break
fi
kill -0 "$SEQ_PID" 2>/dev/null || fail "Sequencer died"
Expand All @@ -309,6 +306,23 @@ for i in $(seq 1 60); do
echo -n "."
done

# Keep the wallet CLI on the same sequencer endpoint used by this test.
log " Updating wallet config for port ${SEQUENCER_PORT}..."
WALLET_CONFIG="${LEE_WALLET_HOME_DIR}/wallet_config.json"
if [ -f "$WALLET_CONFIG" ]; then
python3 -c '
import json, sys
path, sequencer_url = sys.argv[1], sys.argv[2]
with open(path, "r") as wallet_file:
config = json.load(wallet_file)
config["sequencer_addr"] = sequencer_url
with open(path, "w") as wallet_file:
json.dump(config, wallet_file, indent=4)
' "$WALLET_CONFIG" "$SEQUENCER_URL" || fail "Failed to update wallet config"
else
fail "Wallet config not found at $WALLET_CONFIG"
fi

# ─── Step 6: Deploy ───────────────────────────────────────────────────────

log "Step 6: Deploying program..."
Expand Down
2 changes: 1 addition & 1 deletion scripts/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ else
exit 0
fi

export NSSA_WALLET_HOME_DIR="${NSSA_WALLET_HOME_DIR:-${LSSA_DIR}/wallet/configs/debug}"
export LEE_WALLET_HOME_DIR="${LEE_WALLET_HOME_DIR:-${LSSA_DIR}/wallet/configs/debug}"
WALLET_PASSWORD="${WALLET_PASSWORD:-test}"

# Wallet needs password on stdin; first run creates storage
Expand Down
21 changes: 11 additions & 10 deletions spel-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@ path = "src/bin/main.rs"

[dependencies]
spel-framework-core = { path = "../spel-framework-core", features = ["idl-gen"] }
# Pinned to lez-core-v0.2.0, which matches the live testnet: `/LEE/v0.3` public-tx
# signing (spel/co issue #234) and the wallet storage schema written by the wallet
# CLI (#235). nssa_core/nssa were renamed to lee_core/lee upstream — aliased back
# via `package` so spel's source keeps compiling.
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0", package = "lee_core" }
nssa = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0", package = "lee" }
common = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0" }
sequencer_service_rpc = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0", features = ["client"] }
wallet = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0" }
# Pinned to the Wallet build API commit on top of lez-core-v0.2.0. This retains
# the live testnet's `/LEE/v0.3` signing and wallet storage compatibility.
# nssa_core/nssa were renamed to lee_core/lee upstream — aliased back via
# `package` so spel's source keeps compiling.
nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "7a40979c7f5b04a46a8665a3d07cd3a300dcae63", package = "lee_core" }
nssa = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "7a40979c7f5b04a46a8665a3d07cd3a300dcae63", package = "lee" }
common = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "7a40979c7f5b04a46a8665a3d07cd3a300dcae63" }
sequencer_service_rpc = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "7a40979c7f5b04a46a8665a3d07cd3a300dcae63", features = ["client"] }
wallet = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", rev = "7a40979c7f5b04a46a8665a3d07cd3a300dcae63" }
risc0-zkvm = { version = "3.0.3", features = ["std"] }
base58 = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
borsh = "1.5"
tokio = { version = "1.28.2", features = ["net", "rt-multi-thread", "sync", "macros"] }
hex = "0.4"
toml = "0.8"
thiserror = "1.0"

[dev-dependencies]
tempfile = "3"
Expand Down
2 changes: 1 addition & 1 deletion spel-cli/src/generate_idl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ mod tests {

impl Drop for TempDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
drop(fs::remove_dir_all(&self.0));
}
}

Expand Down
6 changes: 4 additions & 2 deletions spel-cli/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use std::fs;
use std::path::Path;

const DEFAULT_LEZ_REV: &str = "7a40979c7f5b04a46a8665a3d07cd3a300dcae63";

pub fn init_project(
name: &str,
lez_tag: Option<&str>,
Expand Down Expand Up @@ -97,7 +99,7 @@ ui/
let lez_ref_ffi = match (lez_tag, lez_rev) {
(Some(t), _) => format!("tag = \"{}\"", t),
(_, Some(r)) => format!("rev = \"{}\"", r),
_ => "tag = \"v0.2.0\"".to_string(),
_ => format!("rev = \"{DEFAULT_LEZ_REV}\""),
};
let spel_ref_ffi = match (spel_tag, spel_rev) {
(Some(t), _) => format!("tag = \"{}\"", t),
Expand Down Expand Up @@ -688,7 +690,7 @@ risc0-zkvm = {{ version = "=3.0.5", features = ["std"] }}
let lez_ref = match (lez_tag, lez_rev) {
(Some(t), _) => format!("tag = \"{}\"", t),
(_, Some(r)) => format!("rev = \"{}\"", r),
_ => "tag = \"v0.2.0\"".to_string(),
_ => format!("rev = \"{DEFAULT_LEZ_REV}\""),
};
let spel_ref = match (spel_tag, spel_rev) {
(Some(t), _) => format!("tag = \"{}\"", t),
Expand Down
Loading
Loading