chia_sdk_driver/layers/
nft_state_layer.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::nft::{NftStateLayerArgs, NftStateLayerSolution};
3use chia_puzzles::NFT_STATE_LAYER_HASH;
4use chia_sdk_types::{
5    conditions::{NewMetadataOutput, UpdateNftMetadata},
6    run_puzzle,
7};
8use clvm_traits::{FromClvm, ToClvm};
9use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
10use clvmr::{Allocator, NodePtr};
11
12use crate::{DriverError, Layer, Puzzle, SpendContext};
13
14/// The NFT state [`Layer`] keeps track of the current metadata of the NFT and how to change it.
15/// It's typically an inner layer of the [`SingletonLayer`](crate::SingletonLayer).
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct NftStateLayer<M, I> {
18    /// The NFT metadata. The standard metadata type is [`NftMetadata`](chia_puzzle_types::nft::NftMetadata).
19    pub metadata: M,
20    /// The tree hash of the metadata updater puzzle.
21    pub metadata_updater_puzzle_hash: Bytes32,
22    /// The inner puzzle layer. Typically, this is the [`NftOwnershipLayer`](crate::NftOwnershipLayer).
23    /// However, for the NFT0 standard this can be the p2 layer itself.
24    pub inner_puzzle: I,
25}
26
27impl<M, I> NftStateLayer<M, I> {
28    pub fn new(metadata: M, metadata_updater_puzzle_hash: Bytes32, inner_puzzle: I) -> Self {
29        Self {
30            metadata,
31            metadata_updater_puzzle_hash,
32            inner_puzzle,
33        }
34    }
35
36    pub fn with_metadata<N>(self, metadata: N) -> NftStateLayer<N, I> {
37        NftStateLayer {
38            metadata,
39            metadata_updater_puzzle_hash: self.metadata_updater_puzzle_hash,
40            inner_puzzle: self.inner_puzzle,
41        }
42    }
43}
44
45impl<M, I> Layer for NftStateLayer<M, I>
46where
47    M: ToClvm<Allocator> + FromClvm<Allocator>,
48    I: Layer,
49{
50    type Solution = NftStateLayerSolution<I::Solution>;
51
52    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
53        let Some(puzzle) = puzzle.as_curried() else {
54            return Ok(None);
55        };
56
57        if puzzle.mod_hash != NFT_STATE_LAYER_HASH.into() {
58            return Ok(None);
59        }
60
61        let args = NftStateLayerArgs::<NodePtr, M>::from_clvm(allocator, puzzle.args)?;
62
63        if args.mod_hash != NFT_STATE_LAYER_HASH.into() {
64            return Err(DriverError::InvalidModHash);
65        }
66
67        let Some(inner_puzzle) =
68            I::parse_puzzle(allocator, Puzzle::parse(allocator, args.inner_puzzle))?
69        else {
70            return Ok(None);
71        };
72
73        Ok(Some(Self {
74            metadata: args.metadata,
75            metadata_updater_puzzle_hash: args.metadata_updater_puzzle_hash,
76            inner_puzzle,
77        }))
78    }
79
80    fn parse_solution(
81        allocator: &Allocator,
82        solution: NodePtr,
83    ) -> Result<Self::Solution, DriverError> {
84        let solution = NftStateLayerSolution::<NodePtr>::from_clvm(allocator, solution)?;
85        Ok(NftStateLayerSolution {
86            inner_solution: I::parse_solution(allocator, solution.inner_solution)?,
87        })
88    }
89
90    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
91        let inner_puzzle = self.inner_puzzle.construct_puzzle(ctx)?;
92        ctx.curry(NftStateLayerArgs {
93            mod_hash: NFT_STATE_LAYER_HASH.into(),
94            metadata: &self.metadata,
95            metadata_updater_puzzle_hash: self.metadata_updater_puzzle_hash,
96            inner_puzzle,
97        })
98    }
99
100    fn construct_solution(
101        &self,
102        ctx: &mut SpendContext,
103        solution: Self::Solution,
104    ) -> Result<NodePtr, DriverError> {
105        let inner_solution = self
106            .inner_puzzle
107            .construct_solution(ctx, solution.inner_solution)?;
108        ctx.alloc(&NftStateLayerSolution { inner_solution })
109    }
110}
111
112impl<M, I> ToTreeHash for NftStateLayer<M, I>
113where
114    M: ToTreeHash,
115    I: ToTreeHash,
116{
117    fn tree_hash(&self) -> TreeHash {
118        let metadata_hash = self.metadata.tree_hash();
119        let inner_puzzle_hash = self.inner_puzzle.tree_hash();
120        CurriedProgram {
121            program: TreeHash::new(NFT_STATE_LAYER_HASH),
122            args: NftStateLayerArgs {
123                mod_hash: NFT_STATE_LAYER_HASH.into(),
124                metadata: metadata_hash,
125                metadata_updater_puzzle_hash: self.metadata_updater_puzzle_hash,
126                inner_puzzle: inner_puzzle_hash,
127            },
128        }
129        .tree_hash()
130    }
131}
132
133impl<M, I> NftStateLayer<M, I> {
134    pub fn get_next_metadata(
135        allocator: &mut Allocator,
136        current_metadata: &M,
137        curent_metadata_updater_puzzle_hash: Bytes32,
138        condition: UpdateNftMetadata<NodePtr, NodePtr>,
139    ) -> Result<M, DriverError>
140    where
141        M: ToClvm<Allocator> + FromClvm<Allocator>,
142    {
143        let real_metadata_updater_solution: Vec<NodePtr> = vec![
144            current_metadata.to_clvm(allocator)?,
145            curent_metadata_updater_puzzle_hash.to_clvm(allocator)?,
146            condition.updater_solution,
147        ];
148        let real_metadata_updater_solution = real_metadata_updater_solution.to_clvm(allocator)?;
149
150        let output = run_puzzle(
151            allocator,
152            condition.updater_puzzle_reveal,
153            real_metadata_updater_solution,
154        )?;
155
156        let parsed = NewMetadataOutput::<M, NodePtr>::from_clvm(allocator, output)?;
157
158        Ok(parsed.metadata_info.new_metadata)
159    }
160}