1use chia_protocol::{Bytes, Bytes32};
2use chia_puzzles::SINGLETON_TOP_LAYER_V1_1_HASH;
3use chia_sdk_types::{
4 puzzles::{
5 AnyMetadataUpdater, CatNftMetadata, DefaultCatMakerArgs, PrecommitLayer1stCurryArgs,
6 PrecommitLayer2ndCurryArgs, PrecommitLayerSolution, PRECOMMIT_LAYER_PUZZLE_HASH,
7 },
8 Conditions, Mod,
9};
10use clvm_traits::{clvm_quote, clvm_tuple, ClvmEncoder, FromClvm, ToClvm, ToClvmError};
11use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
12use clvmr::{Allocator, NodePtr};
13
14use crate::{DriverError, Layer, Puzzle, SpendContext};
15
16#[derive(Debug, Clone)]
17#[must_use]
18pub struct PrecommitLayer<V> {
19 pub controller_singleton_struct_hash: Bytes32,
20 pub relative_block_height: u32,
21 pub payout_puzzle_hash: Bytes32,
22 pub refund_puzzle_hash: Bytes32,
23 pub value: V,
24}
25
26impl<V> PrecommitLayer<V> {
27 pub fn new(
28 controller_singleton_struct_hash: Bytes32,
29 relative_block_height: u32,
30 payout_puzzle_hash: Bytes32,
31 refund_puzzle_hash: Bytes32,
32 value: V,
33 ) -> Self {
34 Self {
35 controller_singleton_struct_hash,
36 relative_block_height,
37 payout_puzzle_hash,
38 refund_puzzle_hash,
39 value,
40 }
41 }
42
43 pub fn first_curry_hash(
44 controller_singleton_struct_hash: Bytes32,
45 relative_block_height: u32,
46 payout_puzzle_hash: Bytes32,
47 ) -> TreeHash {
48 PrecommitLayer1stCurryArgs {
49 singleton_mod_hash: SINGLETON_TOP_LAYER_V1_1_HASH.into(),
50 singleton_struct_hash: controller_singleton_struct_hash,
51 relative_block_height,
52 payout_puzzle_hash,
53 }
54 .curry_tree_hash()
55 }
56
57 pub fn puzzle_hash(
58 controller_singleton_struct_hash: Bytes32,
59 relative_block_height: u32,
60 payout_puzzle_hash: Bytes32,
61 refund_puzzle_hash: Bytes32,
62 value_hash: TreeHash,
63 ) -> TreeHash {
64 CurriedProgram {
65 program: Self::first_curry_hash(
66 controller_singleton_struct_hash,
67 relative_block_height,
68 payout_puzzle_hash,
69 ),
70 args: PrecommitLayer2ndCurryArgs {
71 refund_puzzle_hash,
72 value: value_hash,
73 },
74 }
75 .tree_hash()
76 }
77
78 pub fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError>
79 where
80 V: Clone + ToClvm<Allocator>,
81 {
82 let prog_1st_curry = ctx.curry(PrecommitLayer1stCurryArgs {
83 singleton_mod_hash: SINGLETON_TOP_LAYER_V1_1_HASH.into(),
84 singleton_struct_hash: self.controller_singleton_struct_hash,
85 relative_block_height: self.relative_block_height,
86 payout_puzzle_hash: self.payout_puzzle_hash,
87 })?;
88
89 Ok(CurriedProgram {
90 program: prog_1st_curry,
91 args: PrecommitLayer2ndCurryArgs {
92 refund_puzzle_hash: self.refund_puzzle_hash,
93 value: self.value.clone(),
94 },
95 }
96 .to_clvm(ctx)?)
97 }
98
99 pub fn construct_solution(
100 &self,
101 ctx: &mut SpendContext,
102 solution: PrecommitLayerSolution,
103 ) -> Result<NodePtr, DriverError> {
104 ctx.alloc(&solution)
105 }
106}
107
108impl<V> Layer for PrecommitLayer<V>
109where
110 V: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
111{
112 type Solution = PrecommitLayerSolution;
113
114 fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
115 let Some(puzzle_2nd_curry) = puzzle.as_curried() else {
116 return Ok(None);
117 };
118
119 let Some(curried) = CurriedProgram::<NodePtr, NodePtr>::parse_puzzle(allocator, puzzle)?
120 else {
121 return Ok(None);
122 };
123 let puzzle_1st_curry = Puzzle::parse(allocator, curried.program);
124 let Some(puzzle_1st_curry) = puzzle_1st_curry.as_curried() else {
125 return Ok(None);
126 };
127
128 if puzzle_1st_curry.mod_hash != PRECOMMIT_LAYER_PUZZLE_HASH {
129 return Ok(None);
130 }
131
132 let args_2nd_curry =
133 PrecommitLayer2ndCurryArgs::<V>::from_clvm(allocator, puzzle_2nd_curry.args)?;
134 let args_1st_curry =
135 PrecommitLayer1stCurryArgs::from_clvm(allocator, puzzle_1st_curry.args)?;
136
137 Ok(Some(Self {
138 controller_singleton_struct_hash: args_1st_curry.singleton_struct_hash,
139 relative_block_height: args_1st_curry.relative_block_height,
140 payout_puzzle_hash: args_1st_curry.payout_puzzle_hash,
141 refund_puzzle_hash: args_2nd_curry.refund_puzzle_hash,
142 value: args_2nd_curry.value,
143 }))
144 }
145
146 fn parse_solution(
147 allocator: &Allocator,
148 solution: NodePtr,
149 ) -> Result<Self::Solution, DriverError> {
150 PrecommitLayerSolution::from_clvm(allocator, solution).map_err(DriverError::FromClvm)
151 }
152
153 fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
154 self.construct_puzzle(ctx)
155 }
156
157 fn construct_solution(
158 &self,
159 ctx: &mut SpendContext,
160 solution: Self::Solution,
161 ) -> Result<NodePtr, DriverError> {
162 self.construct_solution(ctx, solution)
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub struct CatalogPrecommitValue<T = NodePtr, S = ()>
168where
169 S: ToTreeHash,
170{
171 pub tail_reveal: T,
172 pub initial_inner_puzzle_hash: Bytes32,
173 pub cat_maker_hash: Bytes32,
174 pub cat_maker_solution: S,
175}
176
177impl<T> CatalogPrecommitValue<T> {
178 pub fn with_default_cat_maker(
179 payment_asset_tail_hash_hash: TreeHash,
180 initial_inner_puzzle_hash: Bytes32,
181 tail_reveal: T,
182 ) -> Self {
183 Self {
184 tail_reveal,
185 initial_inner_puzzle_hash,
186 cat_maker_hash: DefaultCatMakerArgs::new(payment_asset_tail_hash_hash.into())
187 .curry_tree_hash()
188 .into(),
189 cat_maker_solution: (),
190 }
191 }
192
193 pub fn initial_inner_puzzle(
194 ctx: &mut SpendContext,
195 owner_inner_puzzle_hash: Bytes32,
196 initial_metadata: &CatNftMetadata,
197 ) -> Result<NodePtr, DriverError> {
198 let mut conds = Conditions::new().create_coin(
199 owner_inner_puzzle_hash,
200 1,
201 ctx.hint(owner_inner_puzzle_hash)?,
202 );
203 let updater_solution = ctx.alloc(&initial_metadata)?;
204 conds = conds.update_nft_metadata(ctx.alloc_mod::<AnyMetadataUpdater>()?, updater_solution);
205 conds = conds.remark(ctx.alloc(&"MEOW".to_string())?);
206
207 ctx.alloc(&clvm_quote!(conds))
208 }
209}
210
211impl<N, E: ClvmEncoder<Node = N>, T, S> ToClvm<E> for CatalogPrecommitValue<T, S>
213where
214 S: ToTreeHash,
215 T: ToClvm<E> + Clone,
216{
217 fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
218 let hash: Bytes32 = clvm_tuple!(
219 self.initial_inner_puzzle_hash,
220 clvm_tuple!(self.cat_maker_hash, self.cat_maker_solution.tree_hash())
221 )
222 .tree_hash()
223 .into();
224
225 clvm_tuple!(self.tail_reveal.clone(), hash).to_clvm(encoder)
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct XchandlesPrecommitValue<CS = (), PS = TreeHash, S = Bytes32>
236where
237 CS: ToTreeHash,
238 PS: ToTreeHash,
239 S: ToTreeHash,
240{
241 pub cat_maker_hash: Bytes32,
242 pub cat_maker_solution: CS,
243 pub pricing_puzzle_hash: Bytes32,
244 pub pricing_solution: PS,
245 pub handle: String,
246 pub secret: S,
247 pub owner_launcher_id: Bytes32,
248 pub resolved_data: Bytes,
249}
250
251impl<CS, PS, S> XchandlesPrecommitValue<CS, PS, S>
252where
253 CS: ToTreeHash,
254 PS: ToTreeHash,
255 S: ToTreeHash,
256{
257 #[allow(clippy::too_many_arguments)]
258 pub fn new(
259 cat_maker_hash: Bytes32,
260 cat_maker_solution: CS,
261 pricing_puzzle_hash: Bytes32,
262 pricing_solution: PS,
263 handle: String,
264 secret: S,
265 owner_launcher_id: Bytes32,
266 resolved_data: Bytes,
267 ) -> Self {
268 Self {
269 cat_maker_hash,
270 cat_maker_solution,
271 pricing_puzzle_hash,
272 pricing_solution,
273 handle,
274 secret,
275 owner_launcher_id,
276 resolved_data,
277 }
278 }
279}
280
281impl XchandlesPrecommitValue<(), TreeHash, Bytes32> {
282 pub fn for_normal_registration<PS>(
283 payment_tail_hash_hash: TreeHash,
284 pricing_puzzle_hash: TreeHash,
285 pricing_puzzle_solution: &PS,
286 handle: String,
287 secret: Bytes32,
288 owner_launcher_id: Bytes32,
289 resolved_data: Bytes,
290 ) -> Self
291 where
292 PS: ToTreeHash,
293 {
294 Self::new(
295 DefaultCatMakerArgs::new(payment_tail_hash_hash.into())
296 .curry_tree_hash()
297 .into(),
298 (),
299 pricing_puzzle_hash.into(),
300 pricing_puzzle_solution.tree_hash(),
301 handle,
302 secret,
303 owner_launcher_id,
304 resolved_data,
305 )
306 }
307}
308
309impl<N, E: ClvmEncoder<Node = N>, CS, PS, S> ToClvm<E> for XchandlesPrecommitValue<CS, PS, S>
311where
312 CS: ToTreeHash,
313 PS: ToTreeHash,
314 S: ToTreeHash,
315{
316 fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
317 let data_hash: Bytes32 = clvm_tuple!(
318 clvm_tuple!(
319 clvm_tuple!(self.cat_maker_hash, self.cat_maker_solution.tree_hash()),
320 clvm_tuple!(self.pricing_puzzle_hash, self.pricing_solution.tree_hash())
321 ),
322 clvm_tuple!(
323 clvm_tuple!(self.handle.tree_hash(), self.secret.tree_hash()),
324 clvm_tuple!(self.owner_launcher_id, self.resolved_data.tree_hash())
325 )
326 )
327 .tree_hash()
328 .into();
329
330 data_hash.to_clvm(encoder)
331 }
332}