chik_sdk_bindings/mips/
spend.rs

1use std::sync::{Arc, Mutex};
2
3use binky::Result;
4use chik_bls::PublicKey;
5use chik_consensus::opcodes::{
6    CREATE_COIN_ANNOUNCEMENT, CREATE_PUZZLE_ANNOUNCEMENT, RECEIVE_MESSAGE, SEND_MESSAGE,
7};
8use chik_protocol::{Bytes, Bytes32};
9use chik_sdk_driver::{self as sdk, member_puzzle_hash, InnerPuzzleSpend, MofN, SpendContext};
10use chik_sdk_types::{
11    puzzles::{
12        BlsMember, FixedPuzzleMember, Force1of2RestrictedVariable,
13        Force1of2RestrictedVariableSolution, K1Member, K1MemberPuzzleAssert,
14        K1MemberPuzzleAssertSolution, K1MemberSolution, PasskeyMember, PasskeyMemberPuzzleAssert,
15        PasskeyMemberPuzzleAssertSolution, PasskeyMemberSolution, PreventConditionOpcode,
16        PreventMultipleCreateCoinsMod, R1Member, R1MemberPuzzleAssert,
17        R1MemberPuzzleAssertSolution, R1MemberSolution, SingletonMember, SingletonMemberSolution,
18        Timelock, PREVENT_MULTIPLE_CREATE_COINS_PUZZLE_HASH,
19    },
20    Mod,
21};
22use klvm_utils::TreeHash;
23use klvmr::NodePtr;
24
25use crate::{K1PublicKey, K1Signature, Program, R1PublicKey, R1Signature, Spend};
26
27use super::{convert_restrictions, MemberConfig, Vault};
28
29#[derive(Clone)]
30pub struct MipsSpend {
31    pub(crate) klvm: Arc<Mutex<SpendContext>>,
32    pub(crate) spend: Arc<Mutex<sdk::MipsSpend>>,
33    pub(crate) coin: chik_protocol::Coin,
34}
35
36impl MipsSpend {
37    pub fn spend(&self, custody_hash: TreeHash) -> Result<Spend> {
38        let mut ctx = self.klvm.lock().unwrap();
39
40        let spend = self.spend.lock().unwrap().spend(&mut ctx, custody_hash)?;
41
42        Ok(Spend {
43            puzzle: Program(self.klvm.clone(), spend.puzzle),
44            solution: Program(self.klvm.clone(), spend.solution),
45        })
46    }
47
48    pub fn spend_vault(&self, vault: Vault) -> Result<()> {
49        let mut ctx = self.klvm.lock().unwrap();
50        let vault = sdk::Vault::from(vault);
51        let mips_spend = self.spend.lock().unwrap();
52        vault.spend(&mut ctx, &mips_spend)?;
53        Ok(())
54    }
55
56    pub fn m_of_n(&self, config: MemberConfig, required: u32, items: Vec<TreeHash>) -> Result<()> {
57        let restrictions = convert_restrictions(config.restrictions);
58
59        let member = MofN::new(required as usize, items.clone());
60
61        let member_hash = member_puzzle_hash(
62            config.nonce.try_into().unwrap(),
63            restrictions.clone(),
64            member.inner_puzzle_hash(),
65            config.top_level,
66        );
67
68        self.spend.lock().unwrap().members.insert(
69            member_hash,
70            InnerPuzzleSpend::m_of_n(
71                config.nonce.try_into().unwrap(),
72                restrictions,
73                required.try_into().unwrap(),
74                items,
75            ),
76        );
77
78        Ok(())
79    }
80
81    pub fn k1_member(
82        &self,
83        config: MemberConfig,
84        public_key: K1PublicKey,
85        signature: K1Signature,
86        fast_forward: bool,
87    ) -> Result<()> {
88        let mut ctx = self.klvm.lock().unwrap();
89
90        let nonce = config.nonce.try_into().unwrap();
91        let restrictions = convert_restrictions(config.restrictions);
92
93        let (member_hash, member_puzzle) = if fast_forward {
94            let member = K1MemberPuzzleAssert::new(public_key.0);
95            let tree_hash = member.curry_tree_hash();
96            (tree_hash, ctx.curry(member)?)
97        } else {
98            let member = K1Member::new(public_key.0);
99            let tree_hash = member.curry_tree_hash();
100            (tree_hash, ctx.curry(member)?)
101        };
102
103        let member_hash =
104            member_puzzle_hash(nonce, restrictions.clone(), member_hash, config.top_level);
105
106        let member_solution = if fast_forward {
107            ctx.alloc(&K1MemberPuzzleAssertSolution::new(
108                self.coin.puzzle_hash,
109                signature.0,
110            ))?
111        } else {
112            ctx.alloc(&K1MemberSolution::new(self.coin.coin_id(), signature.0))?
113        };
114
115        self.spend.lock().unwrap().members.insert(
116            member_hash,
117            InnerPuzzleSpend::new(
118                nonce,
119                restrictions,
120                sdk::Spend::new(member_puzzle, member_solution),
121            ),
122        );
123
124        Ok(())
125    }
126
127    pub fn r1_member(
128        &self,
129        config: MemberConfig,
130        public_key: R1PublicKey,
131        signature: R1Signature,
132        fast_forward: bool,
133    ) -> Result<()> {
134        let mut ctx = self.klvm.lock().unwrap();
135
136        let nonce = config.nonce.try_into().unwrap();
137        let restrictions = convert_restrictions(config.restrictions);
138
139        let (member_hash, member_puzzle) = if fast_forward {
140            let member = R1MemberPuzzleAssert::new(public_key.0);
141            let tree_hash = member.curry_tree_hash();
142            (tree_hash, ctx.curry(member)?)
143        } else {
144            let member = R1Member::new(public_key.0);
145            let tree_hash = member.curry_tree_hash();
146            (tree_hash, ctx.curry(member)?)
147        };
148
149        let member_hash =
150            member_puzzle_hash(nonce, restrictions.clone(), member_hash, config.top_level);
151
152        let member_solution = if fast_forward {
153            ctx.alloc(&R1MemberPuzzleAssertSolution::new(
154                self.coin.puzzle_hash,
155                signature.0,
156            ))?
157        } else {
158            ctx.alloc(&R1MemberSolution::new(self.coin.coin_id(), signature.0))?
159        };
160
161        self.spend.lock().unwrap().members.insert(
162            member_hash,
163            InnerPuzzleSpend::new(
164                nonce,
165                restrictions,
166                sdk::Spend::new(member_puzzle, member_solution),
167            ),
168        );
169
170        Ok(())
171    }
172
173    pub fn bls_member(&self, config: MemberConfig, public_key: PublicKey) -> Result<()> {
174        let mut ctx = self.klvm.lock().unwrap();
175
176        let nonce = config.nonce.try_into().unwrap();
177        let restrictions = convert_restrictions(config.restrictions);
178
179        let member = BlsMember::new(public_key);
180        let member_hash = member.curry_tree_hash();
181        let member_hash =
182            member_puzzle_hash(nonce, restrictions.clone(), member_hash, config.top_level);
183
184        let member_puzzle = ctx.curry(member)?;
185        let member_solution = ctx.alloc(&NodePtr::NIL)?;
186
187        self.spend.lock().unwrap().members.insert(
188            member_hash,
189            InnerPuzzleSpend::new(
190                nonce,
191                restrictions,
192                sdk::Spend::new(member_puzzle, member_solution),
193            ),
194        );
195
196        Ok(())
197    }
198
199    #[allow(clippy::too_many_arguments)]
200    pub fn passkey_member(
201        &self,
202        config: MemberConfig,
203        public_key: R1PublicKey,
204        signature: R1Signature,
205        authenticator_data: Bytes,
206        client_data_json: Bytes,
207        challenge_index: u32,
208        fast_forward: bool,
209    ) -> Result<()> {
210        let mut ctx = self.klvm.lock().unwrap();
211
212        let nonce = config.nonce.try_into().unwrap();
213        let restrictions = convert_restrictions(config.restrictions);
214
215        let (member_hash, member_puzzle) = if fast_forward {
216            let member = PasskeyMemberPuzzleAssert::new(public_key.0);
217            let tree_hash = member.curry_tree_hash();
218            (tree_hash, ctx.curry(member)?)
219        } else {
220            let member = PasskeyMember::new(public_key.0);
221            let tree_hash = member.curry_tree_hash();
222            (tree_hash, ctx.curry(member)?)
223        };
224
225        let member_hash =
226            member_puzzle_hash(nonce, restrictions.clone(), member_hash, config.top_level);
227
228        let member_solution = if fast_forward {
229            ctx.alloc(&PasskeyMemberPuzzleAssertSolution {
230                authenticator_data,
231                client_data_json,
232                challenge_index: challenge_index.try_into().unwrap(),
233                signature: signature.0,
234                puzzle_hash: self.coin.puzzle_hash,
235            })?
236        } else {
237            ctx.alloc(&PasskeyMemberSolution {
238                authenticator_data,
239                client_data_json,
240                challenge_index: challenge_index.try_into().unwrap(),
241                signature: signature.0,
242                coin_id: self.coin.coin_id(),
243            })?
244        };
245
246        self.spend.lock().unwrap().members.insert(
247            member_hash,
248            InnerPuzzleSpend::new(
249                nonce,
250                restrictions,
251                sdk::Spend::new(member_puzzle, member_solution),
252            ),
253        );
254
255        Ok(())
256    }
257
258    pub fn singleton_member(
259        &self,
260        config: MemberConfig,
261        launcher_id: Bytes32,
262        singleton_inner_puzzle_hash: Bytes32,
263        singleton_amount: u64,
264    ) -> Result<()> {
265        let mut ctx = self.klvm.lock().unwrap();
266
267        let nonce = config.nonce.try_into().unwrap();
268        let restrictions = convert_restrictions(config.restrictions);
269
270        let member = SingletonMember::new(launcher_id);
271
272        let member_hash = member_puzzle_hash(
273            nonce,
274            restrictions.clone(),
275            member.curry_tree_hash(),
276            config.top_level,
277        );
278
279        let member_puzzle = ctx.curry(member)?;
280
281        let member_solution = ctx.alloc(&SingletonMemberSolution::new(
282            singleton_inner_puzzle_hash,
283            singleton_amount,
284        ))?;
285
286        self.spend.lock().unwrap().members.insert(
287            member_hash,
288            InnerPuzzleSpend::new(
289                nonce,
290                restrictions,
291                sdk::Spend::new(member_puzzle, member_solution),
292            ),
293        );
294
295        Ok(())
296    }
297
298    pub fn fixed_puzzle_member(
299        &self,
300        config: MemberConfig,
301        fixed_puzzle_hash: Bytes32,
302    ) -> Result<()> {
303        let mut ctx = self.klvm.lock().unwrap();
304
305        let nonce = config.nonce.try_into().unwrap();
306        let restrictions = convert_restrictions(config.restrictions);
307
308        let member = FixedPuzzleMember::new(fixed_puzzle_hash);
309
310        let member_hash = member_puzzle_hash(
311            nonce,
312            restrictions.clone(),
313            member.curry_tree_hash(),
314            config.top_level,
315        );
316
317        let member_puzzle = ctx.curry(member)?;
318
319        self.spend.lock().unwrap().members.insert(
320            member_hash,
321            InnerPuzzleSpend::new(
322                nonce,
323                restrictions,
324                sdk::Spend::new(member_puzzle, NodePtr::NIL),
325            ),
326        );
327
328        Ok(())
329    }
330
331    pub fn custom_member(&self, config: MemberConfig, spend: Spend) -> Result<()> {
332        let ctx = self.klvm.lock().unwrap();
333
334        let nonce = config.nonce.try_into().unwrap();
335        let restrictions = convert_restrictions(config.restrictions);
336
337        let member_hash = member_puzzle_hash(
338            nonce,
339            restrictions.clone(),
340            ctx.tree_hash(spend.puzzle.1),
341            config.top_level,
342        );
343
344        self.spend.lock().unwrap().members.insert(
345            member_hash,
346            InnerPuzzleSpend::new(
347                nonce,
348                restrictions,
349                chik_sdk_driver::Spend {
350                    puzzle: spend.puzzle.1,
351                    solution: spend.solution.1,
352                },
353            ),
354        );
355
356        Ok(())
357    }
358
359    pub fn timelock(&self, timelock: u64) -> Result<()> {
360        let restriction = Timelock::new(timelock);
361        let puzzle = self.klvm.lock().unwrap().curry(restriction)?;
362        self.spend.lock().unwrap().restrictions.insert(
363            restriction.curry_tree_hash(),
364            sdk::Spend::new(puzzle, NodePtr::NIL),
365        );
366        Ok(())
367    }
368
369    pub fn force_1_of_2_restricted_variable(
370        &self,
371        left_side_subtree_hash: Bytes32,
372        nonce: u32,
373        member_validator_list_hash: Bytes32,
374        delegated_puzzle_validator_list_hash: Bytes32,
375        new_right_side_member_hash: Bytes32,
376    ) -> Result<()> {
377        let mut ctx = self.klvm.lock().unwrap();
378
379        let restriction = Force1of2RestrictedVariable::new(
380            left_side_subtree_hash,
381            nonce.try_into().unwrap(),
382            member_validator_list_hash,
383            delegated_puzzle_validator_list_hash,
384        );
385
386        let puzzle = ctx.curry(restriction)?;
387        let solution = ctx.alloc(&Force1of2RestrictedVariableSolution::new(
388            new_right_side_member_hash,
389        ))?;
390
391        self.spend.lock().unwrap().restrictions.insert(
392            restriction.curry_tree_hash(),
393            sdk::Spend::new(puzzle, solution),
394        );
395
396        Ok(())
397    }
398
399    pub fn prevent_condition_opcode(&self, condition_opcode: u16) -> Result<()> {
400        let mut ctx = self.klvm.lock().unwrap();
401
402        let restriction = PreventConditionOpcode::new(condition_opcode);
403        let puzzle = ctx.curry(restriction)?;
404        let solution = ctx.alloc(&NodePtr::NIL)?;
405
406        self.spend.lock().unwrap().restrictions.insert(
407            restriction.curry_tree_hash(),
408            sdk::Spend::new(puzzle, solution),
409        );
410
411        Ok(())
412    }
413
414    pub fn prevent_multiple_create_coins(&self) -> Result<()> {
415        let mut ctx = self.klvm.lock().unwrap();
416
417        let puzzle = ctx.alloc_mod::<PreventMultipleCreateCoinsMod>()?;
418        let solution = ctx.alloc(&NodePtr::NIL)?;
419
420        self.spend.lock().unwrap().restrictions.insert(
421            PREVENT_MULTIPLE_CREATE_COINS_PUZZLE_HASH,
422            sdk::Spend::new(puzzle, solution),
423        );
424
425        Ok(())
426    }
427
428    pub fn prevent_vault_side_effects(&self) -> Result<()> {
429        self.prevent_condition_opcode(CREATE_COIN_ANNOUNCEMENT)?;
430        self.prevent_condition_opcode(CREATE_PUZZLE_ANNOUNCEMENT)?;
431        self.prevent_condition_opcode(SEND_MESSAGE)?;
432        self.prevent_condition_opcode(RECEIVE_MESSAGE)?;
433        self.prevent_multiple_create_coins()?;
434        Ok(())
435    }
436}