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
41 changes: 21 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 70 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,75 @@ This provides:
- `--dry-run` mode for testing
- `inspect` subcommand to extract ProgramId from binaries

### Transaction Resolution API

Use `spel::tx` when your application owns `WalletCore`. Start with
`SpelProgram` to select an IDL instruction, provide named inputs, and build a
native Wallet transaction.

```rust
use spel::tx::SpelProgram;

// `idl`, `program_id`, `owner_account_id`, and `wallet` are owned by the
// application.
let transaction = SpelProgram::new(&idl)
.program(program_id)
.public("initialize")?
.input("owner", owner_account_id)?
.build(&wallet)
.await?;
```

`build` resolves the selected instruction and delegates native transaction
construction to `WalletCore`. It returns `PublicTransaction` for public
programs. For privacy-preserving programs, bind a borrowed
`ProgramWithDependencies`, select `.private(...)`, and receive Wallet's native
`(PrivacyPreservingTransaction, Vec<SharedSecretKey>)` result.

Bare `AccountId` inputs infer public signing intent from the IDL. Use an
explicit `AccountIdentity` when the caller needs private, shared, keycard, or
other explicit Wallet identity intent.

#### Direct resolver escape hatch

Use `SpelInstructionRequest` when your application needs direct control over
the account and argument maps before calling Wallet. The resolver selects one
IDL instruction, resolves accounts and PDAs, and serializes 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`. Direct resolvers return `SpelTxError`
for invalid IDL or caller input.

Neither `SpelProgram::build` nor the direct resolver API submits, polls, prints,
or changes CLI behavior. `WalletCore` owns native construction, proving,
signing, submission, and confirmation through its separate APIs.

### 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
56 changes: 36 additions & 20 deletions scripts/smoke-test-privacy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ if [ -z "${LEZ_TAG:-}" ]; then
exit 1
fi

LEZ_INIT_OPTION=--lez-tag
if [[ "$LEZ_TAG" =~ ^[[:xdigit:]]{7,40}$ ]]; then
LEZ_INIT_OPTION=--lez-rev
fi

# SPEL_TAG defaults to current local state (for local testing) or can be set explicitly
SPEL_TAG="${SPEL_TAG:-local}"
SPEL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
Expand Down Expand Up @@ -147,7 +152,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_INIT_OPTION" "$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 @@ -320,33 +325,19 @@ log " ✓ Program deployed"

log "Step 7: Generating test accounts..."

# Create a public account (random)
PUBLIC_ACCOUNT="0x$(openssl rand -hex 32)"
log " Public account: ${PUBLIC_ACCOUNT:0:20}..."

# Create a private account via wallet (wallet holds the ZK keys)
PRIVATE_ACCOUNT=$(echo "$WALLET_PASSWORD" | $WALLET_BIN account new private 2>&1 | grep -o "Private/[^ ]*" | head -1)
[ -n "$PRIVATE_ACCOUNT" ] || fail "Could not create private account from wallet"
log " Private account: ${PRIVATE_ACCOUNT:0:30}..."

# ─── Step 8: Test PUBLIC transaction ────────────────────────────────────

log "Step 8: Testing PUBLIC transaction..."
# Create a public account owned by Wallet for the build-only and CLI checks.
FRESH_ACCOUNT=$(echo "$WALLET_PASSWORD" | $WALLET_BIN account new public 2>&1 | grep -o "Public/[^ ]*" | head -1)
[ -n "$FRESH_ACCOUNT" ] || fail "Could not create public account from wallet"
log " Fresh account: ${FRESH_ACCOUNT:0:20}..."

SEQUENCER_URL="$SEQUENCER_URL" "$SPEL_BIN" --idl "$IDL_ABS" -p "$GUEST_BIN_ABS" \
greet \
--account "$FRESH_ACCOUNT" \
--greeting "72,101,108,108,111,32,80,117,98,108,105,99" \
> "$LOG_DIR/public-tx.log" 2>&1 || fail "Public TX failed (see $LOG_DIR/public-tx.log)"

log " ✓ Public TX submitted and confirmed"

# ─── Step 9: Init auth-transfer for private account ─────────────────────
# ─── Step 8: Init auth-transfer for private account ─────────────────────

log "Step 9: Initializing auth-transfer for private account..."
log "Step 8: Initializing auth-transfer for private account..."
echo "$WALLET_PASSWORD" | $WALLET_BIN auth-transfer init --account-id "$PRIVATE_ACCOUNT" \
> "$LOG_DIR/auth-transfer.log" 2>&1 || fail "auth-transfer init failed (see $LOG_DIR/auth-transfer.log)"
log " ✓ auth-transfer initialized"
Expand All @@ -355,9 +346,33 @@ log " ✓ auth-transfer initialized"
log " Waiting for auth-transfer to be confirmed..."
sleep 20

# ─── Step 10: Test PRIVACY-PRESERVING transaction ───────────────────────
# ─── Step 9: Test runtime-IDL build-only API ─────────────────────────────

log "Step 9: Testing runtime-IDL build-only API..."
SPEL_RUNTIME_IDL_SMOKE_IDL="$IDL_ABS" \
SPEL_RUNTIME_IDL_SMOKE_GUEST_BIN="$GUEST_BIN_ABS" \
SPEL_RUNTIME_IDL_SMOKE_PUBLIC_ACCOUNT="$FRESH_ACCOUNT" \
SPEL_RUNTIME_IDL_SMOKE_PRIVATE_ACCOUNT="$PRIVATE_ACCOUNT" \
cargo test --manifest-path "$SPEL_DIR/Cargo.toml" -p spel \
--test runtime_idl_wallet_smoke runtime_idl_builds_do_not_submit_or_change_state \
-- --ignored --exact \
> "$LOG_DIR/runtime-idl-build.log" 2>&1 || { cat "$LOG_DIR/runtime-idl-build.log"; fail "Runtime-IDL build-only smoke failed"; }
log " ✓ Runtime-IDL transactions built without submission"

# ─── Step 10: Test PUBLIC transaction ───────────────────────────────────

log "Step 10: Testing PUBLIC transaction..."
SEQUENCER_URL="$SEQUENCER_URL" "$SPEL_BIN" --idl "$IDL_ABS" -p "$GUEST_BIN_ABS" \
greet \
--account "$FRESH_ACCOUNT" \
--greeting "72,101,108,108,111,32,80,117,98,108,105,99" \
> "$LOG_DIR/public-tx.log" 2>&1 || fail "Public TX failed (see $LOG_DIR/public-tx.log)"

log " ✓ Public TX submitted and confirmed"

# ─── Step 11: Test PRIVACY-PRESERVING transaction ───────────────────────

log "Step 10: Testing PRIVACY-PRESERVING transaction..."
log "Step 11: Testing PRIVACY-PRESERVING transaction..."
SEQUENCER_URL="$SEQUENCER_URL" "$SPEL_BIN" --idl "$IDL_ABS" -p "$GUEST_BIN_ABS" \
greet \
--account "$PRIVATE_ACCOUNT" \
Expand All @@ -370,6 +385,7 @@ log " ✓ Privacy-preserving TX submitted and confirmed"

log ""
log "🎉 Privacy smoke test PASSED!"
log " Runtime-IDL: $LOG_DIR/runtime-idl-build.log"
log " Public TX: $LOG_DIR/public-tx.log"
log " Auth-transfer: $LOG_DIR/auth-transfer.log"
log " Private TX: $LOG_DIR/private-tx.log"
Expand Down
Loading
Loading