action_layer_driver/singleton/
helpers.rs

1//! Singleton helper functions
2//!
3//! Standalone helper functions for common singleton operations.
4
5use chia::protocol::{Bytes32, Coin};
6use chia::puzzles::singleton::SingletonArgs;
7use chia::puzzles::{EveProof, LineageProof, Proof};
8use chia_wallet_sdk::driver::{Launcher, SpendContext};
9use chia_wallet_sdk::types::Conditions;
10use clvm_utils::TreeHash;
11use clvmr::NodePtr;
12
13use super::driver::SINGLETON_LAUNCHER_PUZZLE_HASH;
14use crate::DriverError;
15
16// ============================================================================
17// Proof Creation (for backward compatibility)
18// ============================================================================
19
20/// Create an eve proof for the first singleton spend (after launch)
21pub fn create_eve_proof(launcher_parent_coin_id: Bytes32, singleton_amount: u64) -> Proof {
22    Proof::Eve(EveProof {
23        parent_parent_coin_info: launcher_parent_coin_id,
24        parent_amount: singleton_amount,
25    })
26}
27
28/// Create a lineage proof for subsequent singleton spends
29pub fn create_lineage_proof(parent_coin: &Coin, parent_inner_puzzle_hash: TreeHash) -> Proof {
30    Proof::Lineage(LineageProof {
31        parent_parent_coin_info: parent_coin.parent_coin_info,
32        parent_inner_puzzle_hash: parent_inner_puzzle_hash.into(),
33        parent_amount: parent_coin.amount,
34    })
35}
36
37// ============================================================================
38// Puzzle Hash Computation
39// ============================================================================
40
41/// Compute the full singleton puzzle hash given launcher_id and inner puzzle hash
42pub fn singleton_puzzle_hash(launcher_id: Bytes32, inner_puzzle_hash: TreeHash) -> Bytes32 {
43    SingletonArgs::curry_tree_hash(launcher_id, inner_puzzle_hash).into()
44}
45
46/// Compute the puzzle hash for a child singleton given its launcher ID and inner puzzle hash
47pub fn child_singleton_puzzle_hash(
48    child_launcher_id: Bytes32,
49    child_inner_puzzle_hash: TreeHash,
50) -> Bytes32 {
51    SingletonArgs::curry_tree_hash(child_launcher_id, child_inner_puzzle_hash).into()
52}
53
54/// Compute the expected child launcher ID given the parent singleton coin ID
55pub fn expected_child_launcher_id(parent_singleton_coin_id: Bytes32) -> Bytes32 {
56    Coin::new(
57        parent_singleton_coin_id,
58        Bytes32::new(SINGLETON_LAUNCHER_PUZZLE_HASH),
59        0,
60    )
61    .coin_id()
62}
63
64// ============================================================================
65// Child Singleton Spawning
66// ============================================================================
67
68/// Result of spawning a child singleton via ephemeral launcher
69#[derive(Debug, Clone)]
70pub struct ChildLaunchResult {
71    pub child_launcher_id: Bytes32,
72    pub child_singleton: Coin,
73}
74
75/// Spawn a child singleton via ephemeral (0-amount) launcher.
76///
77/// The launcher coin is parented by the parent singleton coin.
78/// This is used when an action emits a child singleton.
79pub fn spawn_child_singleton(
80    ctx: &mut SpendContext,
81    parent_coin_id: Bytes32,
82    child_inner_puzzle_hash: TreeHash,
83) -> Result<ChildLaunchResult, DriverError> {
84    // Child launcher coin (ephemeral, 0-amount)
85    let child_launcher_coin = Coin::new(
86        parent_coin_id,
87        Bytes32::new(SINGLETON_LAUNCHER_PUZZLE_HASH),
88        0,
89    );
90    let child_launcher_id = child_launcher_coin.coin_id();
91
92    // Spend the launcher to create the child singleton
93    let (_child_conds, child_singleton_info) =
94        Launcher::from_coin(child_launcher_coin, Conditions::new())
95            .with_singleton_amount(1)
96            .mint_vault(ctx, child_inner_puzzle_hash, ())
97            .map_err(|e| DriverError::Launcher(format!("child spawn: {:?}", e)))?;
98
99    Ok(ChildLaunchResult {
100        child_launcher_id,
101        child_singleton: child_singleton_info.coin,
102    })
103}
104
105// ============================================================================
106// Low-Level Puzzle Building (for advanced use cases)
107// ============================================================================
108
109use chia::puzzles::singleton::{SingletonSolution, SingletonStruct};
110use clvm_utils::CurriedProgram;
111
112/// Build a singleton puzzle NodePtr
113pub fn build_singleton_puzzle(
114    ctx: &mut SpendContext,
115    launcher_id: Bytes32,
116    inner_puzzle: NodePtr,
117) -> Result<NodePtr, DriverError> {
118    let singleton_mod_hash = TreeHash::new(chia_puzzles::SINGLETON_TOP_LAYER_V1_1_HASH);
119    let singleton_ptr = ctx
120        .puzzle(singleton_mod_hash, &chia_puzzles::SINGLETON_TOP_LAYER_V1_1)
121        .map_err(|e| DriverError::PuzzleLoad(format!("singleton: {:?}", e)))?;
122
123    ctx.alloc(&CurriedProgram {
124        program: singleton_ptr,
125        args: SingletonArgs {
126            singleton_struct: SingletonStruct::new(launcher_id),
127            inner_puzzle,
128        },
129    })
130    .map_err(|e| DriverError::Alloc(format!("singleton curry: {:?}", e)))
131}
132
133/// Build a singleton solution NodePtr
134pub fn build_singleton_solution(
135    ctx: &mut SpendContext,
136    proof: Proof,
137    amount: u64,
138    inner_solution: NodePtr,
139) -> Result<NodePtr, DriverError> {
140    ctx.alloc(&SingletonSolution {
141        lineage_proof: proof,
142        amount,
143        inner_solution,
144    })
145    .map_err(|e| DriverError::Alloc(format!("singleton solution: {:?}", e)))
146}
147
148/// Create and insert a singleton coin spend
149pub fn create_singleton_coin_spend(
150    ctx: &mut SpendContext,
151    singleton_coin: &Coin,
152    singleton_puzzle: NodePtr,
153    singleton_solution: NodePtr,
154) -> Result<(), DriverError> {
155    use chia::protocol::CoinSpend;
156
157    let puzzle_reveal = ctx
158        .serialize(&singleton_puzzle)
159        .map_err(|e| DriverError::Serialize(format!("{:?}", e)))?;
160    let solution = ctx
161        .serialize(&singleton_solution)
162        .map_err(|e| DriverError::Serialize(format!("{:?}", e)))?;
163
164    let coin_spend = CoinSpend::new(*singleton_coin, puzzle_reveal, solution);
165    ctx.insert(coin_spend);
166    Ok(())
167}
168
169/// Launch a new singleton with the given inner puzzle hash
170///
171/// For most use cases, prefer using `SingletonDriver::launch()` instead.
172pub fn launch_singleton(
173    ctx: &mut SpendContext,
174    funding_coin: &Coin,
175    inner_puzzle_hash: Bytes32,
176    singleton_amount: u64,
177) -> Result<super::LaunchResult, DriverError> {
178    let launcher = Launcher::new(funding_coin.coin_id(), singleton_amount);
179    let launcher_id = launcher.coin().coin_id();
180
181    let (launcher_conditions, singleton_coin) = launcher
182        .spend(ctx, inner_puzzle_hash, ())
183        .map_err(|e| DriverError::Launcher(format!("{:?}", e)))?;
184
185    Ok(super::LaunchResult {
186        launcher_id,
187        coin: singleton_coin,
188        conditions: launcher_conditions,
189    })
190}