A Rust library for building Action Layer (CHIP-0050) singleton spends on the Chia blockchain.
The Action Layer is a pattern for Chia singletons that enables:
- State Management: Singletons that maintain and update state across spends
- Action Dispatch: Multiple actions (puzzles) that can be invoked via a merkle tree
- Child Spawning: Parent singletons that can create child singletons
- Composable Patterns: Building blocks for complex on-chain applications
This library provides a high-level Rust API for constructing Action Layer spends, handling proofs, and managing singleton lifecycle.
SingletonDriver<S>- Generic driver for Action Layer singletonsPuzzleModule- Load and curry CLVM puzzles with typed argumentsActionLayerConfig- Configure action merkle trees- Automatic proof management (eve and lineage proofs)
- Child singleton spawning helpers
- Compatible with
chia-wallet-sdk0.30+
Add to your Cargo.toml:
[dependencies]
action-layer-driver = "0.1.0"use action_layer_driver::{SingletonDriver, PuzzleModule, LaunchResult};
use chia::protocol::{Bytes32, Coin};
use chia_wallet_sdk::driver::SpendContext;
use clvm_traits::{ToClvm, FromClvm};
use clvm_utils::ToTreeHash;
// 1. Define your state type
#[derive(Debug, Clone, ToClvm, FromClvm)]
#[clvm(list)]
pub struct MyState {
pub counter: u64,
#[clvm(rest)]
pub data: Bytes32,
}
// 2. Compute action puzzle hashes
let action_hashes = vec![
compute_action_1_hash(),
compute_action_2_hash(),
];
// 3. Create driver with initial state
let initial_state = MyState { counter: 0, data: Bytes32::default() };
let mut driver = SingletonDriver::new(action_hashes, hint, initial_state);
// 4. Launch the singleton
let ctx = &mut SpendContext::new();
let result = driver.launch(ctx, &funding_coin, 1)?;
println!("Launched with ID: {}", hex::encode(result.launcher_id));
// 5. Build action spends
driver.build_action_spend(ctx, action_index, action_puzzle_ptr, solution_ptr)?;
// 6. Apply state after confirmation
driver.apply_spend(new_state);The core driver for Action Layer singletons, generic over state type S.
// Create a new driver (not yet launched)
let driver = SingletonDriver::new(action_hashes, hint, initial_state);
// Create from existing on-chain singleton
let driver = SingletonDriver::from_coin(singleton_coin, state, action_hashes, hint);
// Launch the singleton
let result = driver.launch(ctx, funding_coin, amount)?;
// Build an action spend
driver.build_action_spend(ctx, action_index, action_puzzle, action_solution)?;
// Update state after confirmation
driver.apply_spend(new_state);
// Mark as melted (destroyed)
driver.mark_melted();
// Accessors
driver.launcher_id() // Option<Bytes32>
driver.current_coin() // Option<&Coin>
driver.state() // &S
driver.proof() // Option<Proof>
driver.inner_puzzle_hash() // TreeHash
driver.expected_new_coin(&new_state) // Option<Coin>
driver.expected_child_launcher_id() // Option<Bytes32>Load and curry CLVM puzzles with typed arguments.
// Load from hex string
let module = PuzzleModule::from_hex(PUZZLE_HEX)?;
// Get module hash
let mod_hash = module.mod_hash();
// Curry with typed arguments
#[derive(ToClvm)]
#[clvm(curry)]
struct MyCurryArgs {
pub field1: Bytes32,
pub field2: u64,
}
let curried_ptr = module.curry_puzzle(ctx, MyCurryArgs { ... })?;
let curried_hash = module.curry_tree_hash(MyCurryArgs { ... });Configure action layer with merkle tree of action hashes.
let config = ActionLayerConfig::new(action_hashes, hint);
// Get inner puzzle hash for a state
let inner_hash = config.inner_puzzle_hash(&state);
// Build action spend (returns inner puzzle and solution)
let (inner_puzzle, inner_solution) = config.build_action_spend(
ctx, state, action_index, action_puzzle, action_solution
)?;// Proof creation
let eve_proof = create_eve_proof(launcher_parent_id, amount);
let lineage_proof = create_lineage_proof(&parent_coin, parent_inner_hash);
// Puzzle hash computation
let puzzle_hash = singleton_puzzle_hash(launcher_id, inner_hash);
let child_hash = child_singleton_puzzle_hash(child_launcher_id, child_inner_hash);
let child_id = expected_child_launcher_id(parent_coin_id);
// Child spawning
let result = spawn_child_singleton(ctx, parent_coin_id, child_inner_hash)?;Demonstrates a two-action singleton that spawns child singletons:
# Build the example
cargo build -p singlelaunch
# Show help
cargo run -p singlelaunch -- --help
# Create a wallet
cargo run -p singlelaunch -- wallet create
# Check balance
cargo run -p singlelaunch -- wallet balance
# Run the two-action test (requires funded wallet)
cargo run -p singlelaunch -- two-actions --password YOUR_PASSWORDA wallet management library for Chia L2 applications:
cargo build -p l2-walletThe library works with Rue-compiled puzzles. Example puzzles are in puzzles/src/:
emit_child_action.rue- Action that spawns a child singletonchild_inner_puzzle.rue- Inner puzzle for child type 1child_inner_puzzle_2.rue- Inner puzzle for child type 2constants.rue- Shared constants
To compile puzzles with Rue:
cd puzzles
rue buildCompiled outputs are in puzzles/output/.
Your state type S must implement:
S: Clone + ToClvm<Allocator> + FromClvm<Allocator> + ToTreeHashThe easiest way is to derive these traits:
#[derive(Debug, Clone, ToClvm, FromClvm)]
#[clvm(list)]
pub struct MyState {
pub field1: u64,
#[clvm(rest)]
pub field2: Bytes32,
}action-layer-driver/
├── src/
│ ├── lib.rs # Library entry point
│ ├── puzzle.rs # PuzzleModule for loading/currying puzzles
│ ├── action_layer.rs # ActionLayerConfig for merkle tree
│ ├── error.rs # Error types
│ └── singleton/
│ ├── mod.rs # Singleton module exports
│ ├── driver.rs # SingletonDriver<S>
│ ├── types.rs # Core types (LaunchResult, etc.)
│ ├── helpers.rs # Standalone helper functions
│ └── spend_options.rs # SpendOptions for broadcasts
├── examples/
│ ├── singlelaunch/ # Two-action singleton demo
│ └── wallet/ # Wallet utilities
├── puzzles/
│ ├── src/ # Rue puzzle sources
│ └── output/ # Compiled puzzle hex
└── README.md
- CHIP-0050 - Action Layer specification
- chia-wallet-sdk - Chia wallet SDK
- Rue - Chia puzzle language
MIT