chia_sdk_driver/primitives/cat/
cat_info.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::cat::CatArgs;
3use chia_sdk_types::{puzzles::RevocationArgs, Mod};
4use clvm_utils::TreeHash;
5use clvmr::{Allocator, NodePtr};
6
7use crate::{CatLayer, DriverError, Layer, Puzzle, RevocationLayer, SpendContext};
8
9/// Information needed to construct the outer puzzle of a CAT.
10/// This includes the [`CatLayer`] and [`RevocationLayer`] if present.
11/// However, it does not include the inner puzzle, which must be stored separately.
12///
13/// This type can be used on its own for parsing, or as part of the [`Cat`](crate::Cat) primitive.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct CatInfo {
16    /// The hash of the TAIL (Token and Asset Issuance Limitations) program.
17    /// This is what controls the supply, and thus the main way to identify a CAT.
18    /// You can spend multiple CAT coins at once, as long as they have the same [`asset_id`](Self::asset_id).
19    pub asset_id: Bytes32,
20
21    /// The hash of the hidden puzzle, if this is a revocable CAT.
22    /// A revocable CAT is one in which the inner puzzle is wrapped in the [`RevocationLayer`].
23    pub hidden_puzzle_hash: Option<Bytes32>,
24
25    /// The hash of the inner puzzle to this CAT. For revocable CATs, it's the inner puzzle of the [`RevocationLayer`].
26    /// If you encode this puzzle hash as bech32m, it's the same as the current owner's address.
27    pub p2_puzzle_hash: Bytes32,
28}
29
30impl CatInfo {
31    pub fn new(
32        asset_id: Bytes32,
33        hidden_puzzle_hash: Option<Bytes32>,
34        p2_puzzle_hash: Bytes32,
35    ) -> Self {
36        Self {
37            asset_id,
38            hidden_puzzle_hash,
39            p2_puzzle_hash,
40        }
41    }
42
43    /// Parses a [`CatInfo`] from a [`Puzzle`] by extracting the [`CatLayer`] and [`RevocationLayer`] if present.
44    ///
45    /// This will return a tuple of the [`CatInfo`] and its p2 puzzle. If the CAT is
46    /// revocable, the p2 puzzle will be [`None`], since it's not revealed until the CAT is spent.
47    ///
48    /// If the puzzle is not a CAT, this will return [`None`] instead of an error.
49    /// However, if the puzzle should have been a CAT but had a parsing error, this will return an error.
50    pub fn parse(
51        allocator: &Allocator,
52        puzzle: Puzzle,
53    ) -> Result<Option<(Self, Option<Puzzle>)>, DriverError> {
54        let Some(cat_layer) = CatLayer::<Puzzle>::parse_puzzle(allocator, puzzle)? else {
55            return Ok(None);
56        };
57
58        if let Some(revocation_layer) =
59            RevocationLayer::parse_puzzle(allocator, cat_layer.inner_puzzle)?
60        {
61            let info = Self::new(
62                cat_layer.asset_id,
63                Some(revocation_layer.hidden_puzzle_hash),
64                revocation_layer.inner_puzzle_hash,
65            );
66            Ok(Some((info, None)))
67        } else {
68            let info = Self::new(
69                cat_layer.asset_id,
70                None,
71                cat_layer.inner_puzzle.curried_puzzle_hash().into(),
72            );
73            Ok(Some((info, Some(cat_layer.inner_puzzle))))
74        }
75    }
76
77    /// Calculates the inner puzzle hash of the CAT.
78    ///
79    /// This is only different than the [`p2_puzzle_hash`](Self::p2_puzzle_hash) for revocable CATs.
80    pub fn inner_puzzle_hash(&self) -> TreeHash {
81        let mut inner_puzzle_hash = TreeHash::from(self.p2_puzzle_hash);
82
83        if let Some(hidden_puzzle_hash) = self.hidden_puzzle_hash {
84            inner_puzzle_hash =
85                RevocationArgs::new(hidden_puzzle_hash, inner_puzzle_hash.into()).curry_tree_hash();
86        }
87
88        inner_puzzle_hash
89    }
90
91    /// Calculates the full puzzle hash of the CAT, which is the hash of the outer [`CatLayer`].
92    pub fn puzzle_hash(&self) -> TreeHash {
93        CatArgs::curry_tree_hash(self.asset_id, self.inner_puzzle_hash())
94    }
95
96    /// Calculates the full puzzle of the CAT. If the CAT is revocable, the [`Self::p2_puzzle_hash`]
97    /// if used instead of the passed in p2 puzzle reveal. This is because the revocation layer
98    /// reveals the inner puzzle in the solution instead of the puzzle.
99    pub fn construct_puzzle(
100        &self,
101        ctx: &mut SpendContext,
102        p2_puzzle: NodePtr,
103    ) -> Result<NodePtr, DriverError> {
104        let mut inner_puzzle = p2_puzzle;
105
106        if let Some(hidden_puzzle_hash) = self.hidden_puzzle_hash {
107            inner_puzzle =
108                ctx.curry(RevocationArgs::new(hidden_puzzle_hash, self.p2_puzzle_hash))?;
109        }
110
111        ctx.curry(CatArgs::new(self.asset_id, inner_puzzle))
112    }
113}