action_layer_driver/
action_layer.rs

1//! Action Layer configuration and spend building
2//!
3//! Provides utilities for constructing action layer spends following the CHIP-0050 pattern.
4
5use std::marker::PhantomData;
6
7use chia::protocol::Bytes32;
8use chia_wallet_sdk::driver::{
9    ActionLayer, ActionLayerSolution, Finalizer, Layer, Spend, SpendContext,
10};
11use chia_wallet_sdk::types::puzzles::{ActionLayerArgs, DefaultFinalizer2ndCurryArgs};
12use chia_wallet_sdk::types::MerkleTree;
13use clvm_traits::{FromClvm, ToClvm};
14use clvm_utils::{ToTreeHash, TreeHash};
15use clvmr::{Allocator, NodePtr};
16
17use crate::DriverError;
18
19/// Configuration for building action layer spends.
20///
21/// Generic over the state type `S`, which must implement proper CLVM traits.
22/// The state should be a proper cons cell (use `#[clvm(list)]` with `#[clvm(rest)]` on last field).
23///
24/// # Example
25///
26/// ```rust,ignore
27/// // State must be a cons cell
28/// #[derive(Debug, Clone, ToClvm, FromClvm)]
29/// #[clvm(list)]
30/// pub struct MyState {
31///     pub counter: u64,
32///     #[clvm(rest)]
33///     pub data: Bytes32,
34/// }
35///
36/// let config = ActionLayerConfig::<MyState>::new(action_hashes, hint);
37/// let inner_hash = config.inner_puzzle_hash(&state);
38/// ```
39pub struct ActionLayerConfig<S> {
40    action_hashes: Vec<Bytes32>,
41    merkle_tree: MerkleTree,
42    hint: Bytes32,
43    _phantom: PhantomData<S>,
44}
45
46impl<S> ActionLayerConfig<S>
47where
48    S: Clone + ToClvm<Allocator> + FromClvm<Allocator> + ToTreeHash,
49{
50    /// Create a new action layer configuration.
51    ///
52    /// # Arguments
53    /// * `action_hashes` - The curried puzzle hashes for each action
54    /// * `hint` - The hint for the default finalizer
55    pub fn new(action_hashes: Vec<Bytes32>, hint: Bytes32) -> Self {
56        let merkle_tree = MerkleTree::new(&action_hashes);
57        Self {
58            action_hashes,
59            merkle_tree,
60            hint,
61            _phantom: PhantomData,
62        }
63    }
64
65    /// Get the action hashes
66    pub fn action_hashes(&self) -> &[Bytes32] {
67        &self.action_hashes
68    }
69
70    /// Get the merkle root of action hashes
71    pub fn merkle_root(&self) -> Bytes32 {
72        self.merkle_tree.root()
73    }
74
75    /// Get the hint
76    pub fn hint(&self) -> Bytes32 {
77        self.hint
78    }
79
80    /// Compute the action layer inner puzzle hash for a given state.
81    ///
82    /// This uses:
83    /// - DefaultFinalizer2ndCurryArgs for the finalizer hash
84    /// - The merkle root of action hashes
85    /// - The state tree hash
86    pub fn inner_puzzle_hash(&self, state: &S) -> TreeHash {
87        // CRITICAL: Use 2nd curry for finalizer (not 1st)
88        let finalizer_hash = DefaultFinalizer2ndCurryArgs::curry_tree_hash(self.hint);
89
90        ActionLayerArgs::<TreeHash, TreeHash>::curry_tree_hash(
91            finalizer_hash,
92            self.merkle_tree.root(),
93            state.tree_hash(),
94        )
95    }
96
97    /// Create an ActionLayer instance for spending
98    pub fn create_action_layer(&self, state: S) -> ActionLayer<S> {
99        ActionLayer::from_action_puzzle_hashes(
100            &self.action_hashes,
101            state,
102            Finalizer::Default { hint: self.hint },
103        )
104    }
105
106    /// Build an action layer spend.
107    ///
108    /// Returns the inner puzzle and solution NodePtrs.
109    ///
110    /// # Arguments
111    /// * `ctx` - The spend context
112    /// * `state` - The current state
113    /// * `action_index` - Which action to invoke (index into action_hashes)
114    /// * `action_puzzle` - The curried action puzzle (use PuzzleModule::curry_puzzle)
115    /// * `action_solution` - The action solution NodePtr
116    pub fn build_action_spend(
117        &self,
118        ctx: &mut SpendContext,
119        state: S,
120        action_index: usize,
121        action_puzzle: NodePtr,
122        action_solution: NodePtr,
123    ) -> Result<(NodePtr, NodePtr), DriverError> {
124        // Validate action index
125        if action_index >= self.action_hashes.len() {
126            return Err(DriverError::InvalidActionIndex {
127                index: action_index,
128                count: self.action_hashes.len(),
129            });
130        }
131
132        let action_hash = self.action_hashes[action_index];
133
134        // Create action layer
135        let action_layer = self.create_action_layer(state);
136
137        // Build action layer inner puzzle
138        let inner_puzzle = action_layer
139            .construct_puzzle(ctx)
140            .map_err(|e| DriverError::ActionLayer(format!("construct_puzzle: {:?}", e)))?;
141
142        // CRITICAL: Use MerkleTree::proof directly (not ActionLayer::get_proofs)
143        let merkle_proof = self
144            .merkle_tree
145            .proof(action_hash)
146            .ok_or(DriverError::MerkleProofNotFound)?;
147
148        // Build action layer solution
149        let action_layer_solution = ActionLayerSolution {
150            proofs: vec![merkle_proof],
151            action_spends: vec![Spend::new(action_puzzle, action_solution)],
152            finalizer_solution: NodePtr::NIL,
153        };
154
155        let inner_solution = action_layer
156            .construct_solution(ctx, action_layer_solution)
157            .map_err(|e| DriverError::ActionLayer(format!("construct_solution: {:?}", e)))?;
158
159        Ok((inner_puzzle, inner_solution))
160    }
161}
162
163impl<S> Clone for ActionLayerConfig<S> {
164    fn clone(&self) -> Self {
165        Self {
166            action_hashes: self.action_hashes.clone(),
167            merkle_tree: MerkleTree::new(&self.action_hashes),
168            hint: self.hint,
169            _phantom: PhantomData,
170        }
171    }
172}
173
174impl<S> std::fmt::Debug for ActionLayerConfig<S> {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        f.debug_struct("ActionLayerConfig")
177            .field("action_count", &self.action_hashes.len())
178            .field("merkle_root", &hex::encode(self.merkle_tree.root()))
179            .field("hint", &hex::encode(self.hint))
180            .finish()
181    }
182}