1use std::fmt::Debug;
2
3use chia_protocol::{Bytes32, Coin};
4use chia_puzzles::{SETTLEMENT_PAYMENT_HASH, SINGLETON_LAUNCHER_HASH};
5use chia_sdk_types::{
6 conditions::{
7 AssertPuzzleAnnouncement, CreateCoin, NewMetadataOutput, TransferNft, UpdateNftMetadata,
8 },
9 Conditions,
10};
11use clvm_traits::clvm_list;
12use clvmr::NodePtr;
13
14use crate::{
15 Asset, Did, DidInfo, DriverError, FungibleSpend, HashedPtr, Launcher, Nft, NftInfo,
16 OptionContract, OutputSet, Spend, SpendContext, SpendKind,
17};
18
19#[derive(Debug, Clone)]
20pub struct SingletonSpends<A>
21where
22 A: SingletonAsset,
23{
24 pub lineage: Vec<SingletonSpend<A>>,
25 pub ephemeral: bool,
26}
27
28impl<A> SingletonSpends<A>
29where
30 A: SingletonAsset,
31{
32 pub fn new(asset: A, ephemeral: bool) -> Self {
33 Self {
34 lineage: vec![SingletonSpend::new(asset)],
35 ephemeral,
36 }
37 }
38
39 pub fn last(&self) -> Result<&SingletonSpend<A>, DriverError> {
40 self.lineage.last().ok_or(DriverError::NoSourceForOutput)
41 }
42
43 pub fn last_mut(&mut self) -> Result<&mut SingletonSpend<A>, DriverError> {
44 self.lineage
45 .last_mut()
46 .ok_or(DriverError::NoSourceForOutput)
47 }
48
49 pub fn last_or_create_settlement(
50 &mut self,
51 ctx: &mut SpendContext,
52 ) -> Result<usize, DriverError> {
53 let last = self
54 .lineage
55 .last_mut()
56 .ok_or(DriverError::NoSourceForOutput)?;
57
58 if !last.kind.missing_singleton_output() {
59 return Err(DriverError::NoSourceForOutput);
60 }
61
62 if last.kind.is_settlement() {
63 return Ok(self.lineage.len() - 1);
64 }
65
66 let Some(child) = A::finalize(
67 ctx,
68 last,
69 SETTLEMENT_PAYMENT_HASH.into(),
70 SETTLEMENT_PAYMENT_HASH.into(),
71 )?
72 else {
73 return Err(DriverError::NoSourceForOutput);
74 };
75
76 self.lineage.push(child);
77
78 Ok(self.lineage.len() - 1)
79 }
80
81 pub fn finalize(
82 &mut self,
83 ctx: &mut SpendContext,
84 intermediate_puzzle_hash: Bytes32,
85 change_puzzle_hash: Bytes32,
86 ) -> Result<Option<A>, DriverError> {
87 let asset = loop {
88 let last = self
89 .lineage
90 .last_mut()
91 .ok_or(DriverError::NoSourceForOutput)?;
92
93 if !last.kind.missing_singleton_output() {
94 break None;
95 }
96
97 let Some(child) = A::finalize(ctx, last, intermediate_puzzle_hash, change_puzzle_hash)?
98 else {
99 break None;
100 };
101
102 if A::needs_additional_spend(&child.child_info) {
103 self.lineage.push(child);
104 } else {
105 break Some(child.asset);
106 }
107 };
108
109 Ok(asset)
110 }
111
112 pub fn intermediate_fungible_xch_spend(
113 &mut self,
114 ctx: &mut SpendContext,
115 intermediate_puzzle_hash: Bytes32,
116 ) -> Result<Option<FungibleSpend<Coin>>, DriverError> {
117 let Some((index, amount)) = self.lineage.iter().enumerate().find_map(|(index, item)| {
118 item.kind
119 .find_amount(intermediate_puzzle_hash, &item.asset.constraints())
120 .map(|amount| (index, amount))
121 }) else {
122 return Ok(None);
123 };
124
125 let source = &mut self.lineage[index];
126
127 let hint = ctx.hint(intermediate_puzzle_hash)?;
128
129 source.kind.create_intermediate_coin(CreateCoin::new(
130 intermediate_puzzle_hash,
131 amount,
132 hint,
133 ));
134
135 let child = FungibleSpend::new(
136 Coin::new(source.asset.coin_id(), intermediate_puzzle_hash, amount),
137 true,
138 );
139
140 Ok(Some(child))
141 }
142
143 pub fn launcher_source(&mut self) -> Result<(usize, u64), DriverError> {
144 let Some((index, amount)) = self.lineage.iter().enumerate().find_map(|(index, item)| {
145 item.kind
146 .find_amount(SINGLETON_LAUNCHER_HASH.into(), &item.asset.constraints())
147 .map(|amount| (index, amount))
148 }) else {
149 return Err(DriverError::NoSourceForOutput);
150 };
151
152 Ok((index, amount))
153 }
154
155 pub fn create_launcher(
156 &mut self,
157 singleton_amount: u64,
158 ) -> Result<(usize, Launcher), DriverError> {
159 let (index, launcher_amount) = self.launcher_source()?;
160
161 let (create_coin, launcher) =
162 Launcher::create_early(self.lineage[index].asset.coin_id(), launcher_amount);
163
164 self.lineage[index]
165 .kind
166 .create_intermediate_coin(create_coin);
167
168 Ok((index, launcher.with_singleton_amount(singleton_amount)))
169 }
170}
171
172#[derive(Debug, Clone)]
173pub struct SingletonSpend<A>
174where
175 A: SingletonAsset,
176{
177 pub asset: A,
178 pub kind: SpendKind,
179 pub child_info: A::ChildInfo,
180 pub payment_assertions: Vec<AssertPuzzleAnnouncement>,
181}
182
183impl<A> SingletonSpend<A>
184where
185 A: SingletonAsset,
186{
187 pub fn new(asset: A) -> Self {
188 let kind = if asset.p2_puzzle_hash() == SETTLEMENT_PAYMENT_HASH.into() {
189 SpendKind::settlement()
190 } else {
191 SpendKind::conditions()
192 };
193 let child_info = A::default_child_info(&asset, &kind);
194
195 Self {
196 asset,
197 kind,
198 child_info,
199 payment_assertions: Vec::new(),
200 }
201 }
202}
203
204pub trait SingletonAsset: Debug + Clone + Asset {
205 type ChildInfo: Debug + Clone;
206
207 fn default_child_info(asset: &Self, spend_kind: &SpendKind) -> Self::ChildInfo;
208 fn needs_additional_spend(child_info: &Self::ChildInfo) -> bool;
209 fn finalize(
210 ctx: &mut SpendContext,
211 singleton: &mut SingletonSpend<Self>,
212 intermediate_puzzle_hash: Bytes32,
213 change_puzzle_hash: Bytes32,
214 ) -> Result<Option<SingletonSpend<Self>>, DriverError>;
215}
216
217impl SingletonAsset for Did<HashedPtr> {
218 type ChildInfo = ChildDidInfo;
219
220 fn default_child_info(asset: &Self, spend_kind: &SpendKind) -> Self::ChildInfo {
221 ChildDidInfo {
222 recovery_list_hash: asset.info.recovery_list_hash,
223 num_verifications_required: asset.info.num_verifications_required,
224 metadata: asset.info.metadata,
225 destination: None,
226 new_spend_kind: spend_kind.empty_copy(),
227 needs_update: false,
228 }
229 }
230
231 fn needs_additional_spend(child_info: &Self::ChildInfo) -> bool {
232 child_info.needs_update
233 }
234
235 fn finalize(
236 ctx: &mut SpendContext,
237 singleton: &mut SingletonSpend<Self>,
238 _conditions_puzzle_hash: Bytes32,
239 change_puzzle_hash: Bytes32,
240 ) -> Result<Option<SingletonSpend<Self>>, DriverError> {
241 let change_hint = ctx.hint(change_puzzle_hash)?;
242
243 let current_info = singleton.asset.info;
244 let child_info = &singleton.child_info;
245
246 let needs_update = current_info.recovery_list_hash != child_info.recovery_list_hash
248 || current_info.num_verifications_required != child_info.num_verifications_required
249 || current_info.metadata != child_info.metadata;
250
251 let final_destination = child_info.destination;
252
253 let destination = if needs_update {
254 let p2_puzzle_hash = current_info.p2_puzzle_hash;
255 let hint = ctx.hint(p2_puzzle_hash)?;
256 SingletonDestination::CreateCoin(CreateCoin::new(
257 p2_puzzle_hash,
258 singleton.asset.coin.amount,
259 hint,
260 ))
261 } else {
262 child_info
263 .destination
264 .unwrap_or(SingletonDestination::CreateCoin(CreateCoin::new(
265 change_puzzle_hash,
266 singleton.asset.coin.amount,
267 change_hint,
268 )))
269 };
270
271 match destination {
272 SingletonDestination::CreateCoin(destination) => {
273 let child_info = DidInfo::new(
274 current_info.launcher_id,
275 child_info.recovery_list_hash,
276 child_info.num_verifications_required,
277 child_info.metadata,
278 destination.puzzle_hash,
279 );
280
281 let create_coin = CreateCoin::new(
283 child_info.inner_puzzle_hash().into(),
284 destination.amount,
285 destination.memos,
286 );
287 let parent_puzzle_hash = singleton.asset.full_puzzle_hash();
288 singleton.kind.create_coin_with_assertion(
289 ctx,
290 parent_puzzle_hash,
291 &mut singleton.payment_assertions,
292 create_coin,
293 );
294
295 let mut new_spend = SingletonSpend::new(
298 singleton
299 .asset
300 .child_with(child_info, singleton.asset.coin.amount),
301 );
302
303 new_spend.child_info.needs_update = needs_update;
305
306 if needs_update {
307 new_spend.child_info.destination = final_destination;
308 }
309
310 Ok(Some(new_spend))
311 }
312 SingletonDestination::Melt => {
313 match &mut singleton.kind {
314 SpendKind::Conditions(conditions) => {
315 conditions.add_conditions(Conditions::new().melt_singleton());
316 }
317 SpendKind::Settlement(_) => {
318 return Err(DriverError::CannotEmitConditions);
319 }
320 }
321
322 Ok(None)
323 }
324 }
325 }
326}
327
328impl SingletonAsset for Nft<HashedPtr> {
329 type ChildInfo = ChildNftInfo;
330
331 fn default_child_info(_asset: &Self, spend_kind: &SpendKind) -> Self::ChildInfo {
332 ChildNftInfo {
333 metadata_update_spends: Vec::new(),
334 transfer_condition: None,
335 destination: None,
336 new_spend_kind: spend_kind.empty_copy(),
337 }
338 }
339
340 fn needs_additional_spend(child_info: &Self::ChildInfo) -> bool {
341 !child_info.metadata_update_spends.is_empty() || child_info.transfer_condition.is_some()
342 }
343
344 fn finalize(
345 ctx: &mut SpendContext,
346 singleton: &mut SingletonSpend<Self>,
347 intermediate_puzzle_hash: Bytes32,
348 change_puzzle_hash: Bytes32,
349 ) -> Result<Option<SingletonSpend<Self>>, DriverError> {
350 if !singleton.kind.is_conditions()
351 && (!singleton.child_info.metadata_update_spends.is_empty()
352 || singleton.child_info.transfer_condition.is_some())
353 {
354 let create_coin = CreateCoin::new(
355 intermediate_puzzle_hash,
356 singleton.asset.coin.amount,
357 ctx.hint(intermediate_puzzle_hash)?,
358 );
359 let parent_puzzle_hash = singleton.asset.full_puzzle_hash();
360 singleton.kind.create_coin_with_assertion(
361 ctx,
362 parent_puzzle_hash,
363 &mut singleton.payment_assertions,
364 create_coin,
365 );
366
367 let new_info = NftInfo {
368 p2_puzzle_hash: intermediate_puzzle_hash,
369 ..singleton.asset.info
370 };
371
372 let mut spend = SingletonSpend::new(
373 singleton
374 .asset
375 .child_with(new_info, singleton.asset.coin.amount),
376 );
377
378 spend.child_info = singleton.child_info.clone();
379
380 return Ok(Some(spend));
381 }
382
383 let change_hint = ctx.hint(change_puzzle_hash)?;
384
385 let mut new_child_info = singleton.child_info.clone();
386
387 let metadata_update_spend = new_child_info.metadata_update_spends.pop();
388 let transfer_condition = new_child_info.transfer_condition.take();
389 let needs_additional_spend = Self::needs_additional_spend(&new_child_info);
390
391 let destination = if needs_additional_spend {
392 let p2_puzzle_hash = singleton.asset.info.p2_puzzle_hash;
393 let hint = ctx.hint(p2_puzzle_hash)?;
394 CreateCoin::new(p2_puzzle_hash, singleton.asset.coin.amount, hint)
395 } else {
396 new_child_info.destination.unwrap_or(CreateCoin::new(
397 change_puzzle_hash,
398 singleton.asset.coin.amount,
399 change_hint,
400 ))
401 };
402
403 let mut nft_info = singleton.asset.info;
404 nft_info.p2_puzzle_hash = destination.puzzle_hash;
405
406 let parent_puzzle_hash = singleton.asset.full_puzzle_hash();
408
409 singleton.kind.create_coin_with_assertion(
410 ctx,
411 parent_puzzle_hash,
412 &mut singleton.payment_assertions,
413 destination,
414 );
415
416 let mut conditions = Conditions::new();
417
418 if let Some(spend) = metadata_update_spend {
419 conditions.push(UpdateNftMetadata::new(spend.puzzle, spend.solution));
420
421 let metadata_updater_solution = ctx.alloc(&clvm_list!(
422 singleton.asset.info.metadata,
423 singleton.asset.info.metadata_updater_puzzle_hash,
424 spend.solution
425 ))?;
426 let ptr = ctx.run(spend.puzzle, metadata_updater_solution)?;
427 let output = ctx.extract::<NewMetadataOutput<HashedPtr, NodePtr>>(ptr)?;
428
429 nft_info.metadata = output.metadata_info.new_metadata;
430 nft_info.metadata_updater_puzzle_hash = output.metadata_info.new_updater_puzzle_hash;
431 }
432
433 if let Some(transfer_condition) = transfer_condition {
434 nft_info.current_owner = transfer_condition.launcher_id;
435 conditions.push(transfer_condition);
436 }
437
438 if !conditions.is_empty() {
439 match &mut singleton.kind {
440 SpendKind::Conditions(spend) => {
441 spend.add_conditions(conditions);
442 }
443 SpendKind::Settlement(_) => {
444 return Err(DriverError::CannotEmitConditions);
445 }
446 }
447 }
448
449 let mut spend = SingletonSpend::new(
451 singleton
452 .asset
453 .child_with(nft_info, singleton.asset.coin.amount),
454 );
455
456 spend.child_info = new_child_info;
457
458 Ok(Some(spend))
459 }
460}
461
462impl SingletonAsset for OptionContract {
463 type ChildInfo = ChildOptionInfo;
464
465 fn default_child_info(_asset: &Self, spend_kind: &SpendKind) -> Self::ChildInfo {
466 ChildOptionInfo {
467 destination: None,
468 new_spend_kind: spend_kind.empty_copy(),
469 }
470 }
471
472 fn needs_additional_spend(_child_info: &Self::ChildInfo) -> bool {
473 false
474 }
475
476 fn finalize(
477 ctx: &mut SpendContext,
478 singleton: &mut SingletonSpend<Self>,
479 _conditions_puzzle_hash: Bytes32,
480 change_puzzle_hash: Bytes32,
481 ) -> Result<Option<SingletonSpend<Self>>, DriverError> {
482 let change_hint = ctx.hint(change_puzzle_hash)?;
483
484 let default_destination = SingletonDestination::CreateCoin(CreateCoin::new(
485 change_puzzle_hash,
486 singleton.asset.coin.amount,
487 change_hint,
488 ));
489
490 let destination = singleton
491 .child_info
492 .destination
493 .unwrap_or(default_destination);
494
495 match destination {
496 SingletonDestination::CreateCoin(destination) => {
497 let parent_puzzle_hash = singleton.asset.full_puzzle_hash();
499 singleton.kind.create_coin_with_assertion(
500 ctx,
501 parent_puzzle_hash,
502 &mut singleton.payment_assertions,
503 destination,
504 );
505
506 Ok(Some(SingletonSpend::new(singleton.asset.child(
508 destination.puzzle_hash,
509 singleton.asset.coin.amount,
510 ))))
511 }
512 SingletonDestination::Melt => {
513 let message = singleton.asset.info.underlying_delegated_puzzle_hash.into();
515 let data = ctx.alloc(&singleton.asset.info.underlying_coin_id)?;
516
517 match &mut singleton.kind {
518 SpendKind::Conditions(spend) => {
519 spend.add_conditions(Conditions::new().melt_singleton().send_message(
520 23,
521 message,
522 vec![data],
523 ));
524 }
525 SpendKind::Settlement(_) => {
526 return Err(DriverError::CannotEmitConditions);
527 }
528 }
529
530 Ok(None)
531 }
532 }
533 }
534}
535
536#[derive(Debug, Clone, Copy)]
537pub enum SingletonDestination {
538 CreateCoin(CreateCoin<NodePtr>),
539 Melt,
540}
541
542#[derive(Debug, Clone)]
543pub struct ChildDidInfo {
544 pub recovery_list_hash: Option<Bytes32>,
545 pub num_verifications_required: u64,
546 pub metadata: HashedPtr,
547 pub destination: Option<SingletonDestination>,
548 pub new_spend_kind: SpendKind,
549 pub needs_update: bool,
550}
551
552#[derive(Debug, Clone)]
553pub struct ChildNftInfo {
554 pub metadata_update_spends: Vec<Spend>,
555 pub transfer_condition: Option<TransferNft>,
556 pub destination: Option<CreateCoin<NodePtr>>,
557 pub new_spend_kind: SpendKind,
558}
559
560#[derive(Debug, Clone)]
561pub struct ChildOptionInfo {
562 pub destination: Option<SingletonDestination>,
563 pub new_spend_kind: SpendKind,
564}