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}