1use std::{collections::HashMap, mem};
2
3use chia_bls::PublicKey;
4use chia_protocol::{Bytes32, Coin};
5use chia_puzzle_types::offer::SettlementPaymentsSolution;
6use chia_sdk_types::{Conditions, conditions::AssertPuzzleAnnouncement};
7use indexmap::IndexMap;
8
9use crate::{
10 Action, Asset, Cat, CatSpend, ConditionsSpend, Delta, Deltas, Did, DriverError, FungibleSpend,
11 FungibleSpends, Id, Layer, Nft, OptionContract, Relation, SettlementLayer, SingletonSpends,
12 Spend, SpendAction, SpendContext, SpendKind, SpendWithConditions, SpendableAsset,
13 StandardLayer,
14};
15
16#[derive(Debug, Clone)]
17#[must_use]
18pub struct Spends<S = Unfinished> {
19 pub xch: FungibleSpends<Coin>,
20 pub cats: IndexMap<Id, FungibleSpends<Cat>>,
21 pub dids: IndexMap<Id, SingletonSpends<Did>>,
22 pub nfts: IndexMap<Id, SingletonSpends<Nft>>,
23 pub options: IndexMap<Id, SingletonSpends<OptionContract>>,
24 pub intermediate_puzzle_hash: Bytes32,
25 pub change_puzzle_hash: Bytes32,
26 pub outputs: Outputs,
27 pub conditions: ConditionConfig,
28 _state: S,
29}
30
31#[derive(Debug, Default, Clone)]
32pub struct ConditionConfig {
33 pub optional: Conditions,
34 pub required: Conditions,
35 pub disable_settlement_assertions: bool,
36}
37
38#[derive(Debug, Default, Clone)]
39pub struct Outputs {
40 pub xch: Vec<Coin>,
41 pub cats: IndexMap<Id, Vec<Cat>>,
42 pub dids: IndexMap<Id, Did>,
43 pub nfts: IndexMap<Id, Nft>,
44 pub options: IndexMap<Id, OptionContract>,
45 pub fee: u64,
46 pub reserved_fee: u64,
47}
48
49#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
50pub struct Unfinished;
51
52#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
53pub struct Finished;
54
55impl Spends<Unfinished> {
56 pub fn new(change_puzzle_hash: Bytes32) -> Self {
57 Self::with_separate_change_puzzle_hash(change_puzzle_hash, change_puzzle_hash)
58 }
59
60 pub fn with_separate_change_puzzle_hash(
61 intermediate_puzzle_hash: Bytes32,
62 change_puzzle_hash: Bytes32,
63 ) -> Self {
64 Self {
65 xch: FungibleSpends::new(),
66 cats: IndexMap::new(),
67 dids: IndexMap::new(),
68 nfts: IndexMap::new(),
69 options: IndexMap::new(),
70 intermediate_puzzle_hash,
71 change_puzzle_hash,
72 outputs: Outputs::default(),
73 conditions: ConditionConfig::default(),
74 _state: Unfinished,
75 }
76 }
77
78 pub fn add(&mut self, asset: impl AddAsset) {
79 asset.add(self);
80 }
81
82 pub fn apply(
83 &mut self,
84 ctx: &mut SpendContext,
85 actions: &[Action],
86 ) -> Result<Deltas, DriverError> {
87 let deltas = Deltas::from_actions(actions);
88 for (index, action) in actions.iter().enumerate() {
89 action.spend(ctx, self, index)?;
90 }
91 Ok(deltas)
92 }
93
94 fn create_change(
95 &mut self,
96 ctx: &mut SpendContext,
97 deltas: &Deltas,
98 ) -> Result<(), DriverError> {
99 if let Some(change) = self.xch.create_change(
100 ctx,
101 deltas.get(&Id::Xch).unwrap_or(&Delta::default()),
102 self.change_puzzle_hash,
103 )? {
104 self.outputs.xch.push(change);
105 }
106
107 for (&id, cat) in &mut self.cats {
108 if let Some(change) = cat.create_change(
109 ctx,
110 deltas.get(&id).unwrap_or(&Delta::default()),
111 self.change_puzzle_hash,
112 )? {
113 self.outputs.cats.entry(id).or_default().push(change);
114 }
115 }
116
117 for (&id, did) in &mut self.dids {
118 if let Some(change) =
119 did.finalize(ctx, self.intermediate_puzzle_hash, self.change_puzzle_hash)?
120 {
121 self.outputs.dids.insert(id, change);
122 }
123 }
124
125 for (&id, nft) in &mut self.nfts {
126 if let Some(change) =
127 nft.finalize(ctx, self.intermediate_puzzle_hash, self.change_puzzle_hash)?
128 {
129 self.outputs.nfts.insert(id, change);
130 }
131 }
132
133 for (&id, option) in &mut self.options {
134 if let Some(change) =
135 option.finalize(ctx, self.intermediate_puzzle_hash, self.change_puzzle_hash)?
136 {
137 self.outputs.options.insert(id, change);
138 }
139 }
140
141 Ok(())
142 }
143
144 fn payment_assertions(&self) -> Vec<AssertPuzzleAnnouncement> {
145 let mut payment_assertions = self.xch.payment_assertions.clone();
146
147 for cat in self.cats.values() {
148 payment_assertions.extend_from_slice(&cat.payment_assertions);
149 }
150
151 for did in self.dids.values() {
152 for item in &did.lineage {
153 payment_assertions.extend_from_slice(&item.payment_assertions);
154 }
155 }
156
157 for nft in self.nfts.values() {
158 for item in &nft.lineage {
159 payment_assertions.extend_from_slice(&item.payment_assertions);
160 }
161 }
162
163 for option in self.options.values() {
164 for item in &option.lineage {
165 payment_assertions.extend_from_slice(&item.payment_assertions);
166 }
167 }
168
169 payment_assertions
170 }
171
172 fn iter_conditions_spends(&mut self) -> impl Iterator<Item = (Coin, &mut ConditionsSpend)> {
173 self.xch
174 .items
175 .iter_mut()
176 .filter_map(|item| {
177 if let SpendKind::Conditions(spend) = &mut item.kind {
178 Some((item.asset, spend))
179 } else {
180 None
181 }
182 })
183 .chain(self.cats.values_mut().filter_map(|cat| {
184 cat.items.iter_mut().find_map(|item| {
185 if let SpendKind::Conditions(spend) = &mut item.kind {
186 Some((item.asset.coin, spend))
187 } else {
188 None
189 }
190 })
191 }))
192 .chain(self.dids.values_mut().filter_map(|did| {
193 did.lineage
194 .iter_mut()
195 .filter_map(|item| {
196 if let SpendKind::Conditions(spend) = &mut item.kind {
197 Some((item.asset.coin, spend))
198 } else {
199 None
200 }
201 })
202 .last()
203 }))
204 .chain(self.nfts.values_mut().filter_map(|nft| {
205 nft.lineage
206 .iter_mut()
207 .filter_map(|item| {
208 if let SpendKind::Conditions(spend) = &mut item.kind {
209 Some((item.asset.coin, spend))
210 } else {
211 None
212 }
213 })
214 .last()
215 }))
216 .chain(self.options.values_mut().filter_map(|option| {
217 option
218 .lineage
219 .iter_mut()
220 .filter_map(|item| {
221 if let SpendKind::Conditions(spend) = &mut item.kind {
222 Some((item.asset.coin, spend))
223 } else {
224 None
225 }
226 })
227 .last()
228 }))
229 }
230
231 fn emit_conditions(&mut self, ctx: &mut SpendContext) -> Result<(), DriverError> {
232 let mut conditions = self.conditions.required.clone().extend(
233 if self.conditions.disable_settlement_assertions {
234 vec![]
235 } else {
236 self.payment_assertions()
237 },
238 );
239
240 let required = !conditions.is_empty();
241
242 conditions = conditions.extend(self.conditions.optional.clone());
243
244 if self.outputs.reserved_fee > 0 {
245 conditions = conditions.reserve_fee(self.outputs.reserved_fee);
246 }
247
248 for (_, spend) in self.iter_conditions_spends() {
249 spend.add_conditions(mem::take(&mut conditions));
250 }
251
252 if conditions.is_empty() || !required {
253 return Ok(());
254 }
255
256 if let Some(index) = self
257 .xch
258 .intermediate_conditions_source(ctx, self.intermediate_puzzle_hash)?
259 {
260 match &mut self.xch.items[index].kind {
261 SpendKind::Conditions(spend) => {
262 spend.add_conditions(mem::take(&mut conditions));
263 }
264 SpendKind::Settlement(_) => {}
265 }
266 }
267
268 for cat in self.cats.values_mut() {
269 if let Some(index) =
270 cat.intermediate_conditions_source(ctx, self.intermediate_puzzle_hash)?
271 {
272 match &mut cat.items[index].kind {
273 SpendKind::Conditions(spend) => {
274 spend.add_conditions(mem::take(&mut conditions));
275 }
276 SpendKind::Settlement(_) => {}
277 }
278 }
279 }
280
281 for did in self.dids.values_mut() {
282 if let Some(mut item) =
283 did.intermediate_fungible_xch_spend(ctx, self.intermediate_puzzle_hash)?
284 {
285 match &mut item.kind {
286 SpendKind::Conditions(spend) => {
287 spend.add_conditions(mem::take(&mut conditions));
288 }
289 SpendKind::Settlement(_) => {}
290 }
291 self.xch.items.push(item);
292 }
293 }
294
295 for nft in self.nfts.values_mut() {
296 if let Some(mut item) =
297 nft.intermediate_fungible_xch_spend(ctx, self.intermediate_puzzle_hash)?
298 {
299 match &mut item.kind {
300 SpendKind::Conditions(spend) => {
301 spend.add_conditions(mem::take(&mut conditions));
302 }
303 SpendKind::Settlement(_) => {}
304 }
305 self.xch.items.push(item);
306 }
307 }
308
309 for option in self.options.values_mut() {
310 if let Some(mut item) =
311 option.intermediate_fungible_xch_spend(ctx, self.intermediate_puzzle_hash)?
312 {
313 match &mut item.kind {
314 SpendKind::Conditions(spend) => {
315 spend.add_conditions(mem::take(&mut conditions));
316 }
317 SpendKind::Settlement(_) => {}
318 }
319 self.xch.items.push(item);
320 }
321 }
322
323 if conditions.is_empty() {
324 Ok(())
325 } else {
326 Err(DriverError::CannotEmitConditions)
327 }
328 }
329
330 fn emit_relation(&mut self, relation: Relation) {
331 match relation {
332 Relation::None => {}
333 Relation::AssertConcurrent => {
334 let coin_ids: Vec<Bytes32> = self
335 .iter_conditions_spends()
336 .map(|(coin, _)| coin.coin_id())
337 .collect();
338
339 if coin_ids.len() <= 1 {
340 return;
341 }
342
343 self.iter_conditions_spends()
344 .enumerate()
345 .for_each(|(i, (_, spend))| {
346 spend.add_conditions(Conditions::new().assert_concurrent_spend(
347 if i == 0 {
348 coin_ids[coin_ids.len() - 1]
349 } else {
350 coin_ids[i - 1]
351 },
352 ));
353 });
354 }
355 }
356 }
357
358 pub fn p2_puzzle_hashes(&self) -> Vec<Bytes32> {
359 let mut p2_puzzle_hashes = vec![self.intermediate_puzzle_hash];
360
361 for item in &self.xch.items {
362 p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
363 }
364
365 for (_, cat) in &self.cats {
366 for item in &cat.items {
367 p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
368 }
369 }
370
371 for (_, did) in &self.dids {
372 for item in &did.lineage {
373 p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
374 }
375 }
376
377 for (_, nft) in &self.nfts {
378 for item in &nft.lineage {
379 p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
380 }
381 }
382
383 for (_, option) in &self.options {
384 for item in &option.lineage {
385 p2_puzzle_hashes.push(item.asset.p2_puzzle_hash());
386 }
387 }
388
389 p2_puzzle_hashes
390 }
391
392 pub fn non_settlement_coin_ids(&self) -> Vec<Bytes32> {
393 let mut coin_ids = Vec::new();
394
395 for item in &self.xch.items {
396 if item.kind.is_conditions() {
397 coin_ids.push(item.asset.coin_id());
398 }
399 }
400
401 for (_, cat) in &self.cats {
402 for item in &cat.items {
403 if item.kind.is_conditions() {
404 coin_ids.push(item.asset.coin_id());
405 }
406 }
407 }
408
409 for (_, did) in &self.dids {
410 for item in &did.lineage {
411 if item.kind.is_conditions() {
412 coin_ids.push(item.asset.coin_id());
413 }
414 }
415 }
416
417 for (_, nft) in &self.nfts {
418 for item in &nft.lineage {
419 if item.kind.is_conditions() {
420 coin_ids.push(item.asset.coin_id());
421 }
422 }
423 }
424
425 for (_, option) in &self.options {
426 for item in &option.lineage {
427 if item.kind.is_conditions() {
428 coin_ids.push(item.asset.coin_id());
429 }
430 }
431 }
432
433 coin_ids
434 }
435
436 pub fn prepare(
437 mut self,
438 ctx: &mut SpendContext,
439 deltas: &Deltas,
440 relation: Relation,
441 ) -> Result<Spends<Finished>, DriverError> {
442 self.create_change(ctx, deltas)?;
443 self.emit_conditions(ctx)?;
444 self.emit_relation(relation);
445
446 Ok(Spends {
447 xch: self.xch,
448 cats: self.cats,
449 dids: self.dids,
450 nfts: self.nfts,
451 options: self.options,
452 intermediate_puzzle_hash: self.intermediate_puzzle_hash,
453 change_puzzle_hash: self.change_puzzle_hash,
454 outputs: self.outputs,
455 conditions: self.conditions,
456 _state: Finished,
457 })
458 }
459
460 pub fn finish_with_keys(
461 self,
462 ctx: &mut SpendContext,
463 deltas: &Deltas,
464 relation: Relation,
465 synthetic_keys: &IndexMap<Bytes32, PublicKey>,
466 ) -> Result<Outputs, DriverError> {
467 let spends = self.prepare(ctx, deltas, relation)?;
468 let mut coin_spends = HashMap::new();
469
470 for (asset, kind) in spends.unspent() {
471 match kind {
472 SpendKind::Conditions(spend) => {
473 let Some(&synthetic_key) = synthetic_keys.get(&asset.p2_puzzle_hash()) else {
474 return Err(DriverError::MissingKey);
475 };
476 coin_spends.insert(
477 asset.coin().coin_id(),
478 StandardLayer::new(synthetic_key)
479 .spend_with_conditions(ctx, spend.finish())?,
480 );
481 }
482 SpendKind::Settlement(spend) => {
483 coin_spends.insert(
484 asset.coin().coin_id(),
485 SettlementLayer.construct_spend(
486 ctx,
487 SettlementPaymentsSolution::new(spend.finish()),
488 )?,
489 );
490 }
491 }
492 }
493
494 spends.spend(ctx, coin_spends)
495 }
496}
497
498impl Spends<Finished> {
499 pub fn unspent(&self) -> Vec<(SpendableAsset, SpendKind)> {
500 let mut result = Vec::new();
501
502 for item in &self.xch.items {
503 result.push((SpendableAsset::Xch(item.asset), item.kind.clone()));
504 }
505
506 for cat in self.cats.values() {
507 for item in &cat.items {
508 result.push((SpendableAsset::Cat(item.asset), item.kind.clone()));
509 }
510 }
511
512 for did in self.dids.values() {
513 for item in &did.lineage {
514 result.push((SpendableAsset::Did(item.asset), item.kind.clone()));
515 }
516 }
517
518 for nft in self.nfts.values() {
519 for item in &nft.lineage {
520 result.push((SpendableAsset::Nft(item.asset), item.kind.clone()));
521 }
522 }
523
524 for option in self.options.values() {
525 for item in &option.lineage {
526 result.push((SpendableAsset::Option(item.asset), item.kind.clone()));
527 }
528 }
529
530 result
531 }
532
533 pub fn spend(
534 self,
535 ctx: &mut SpendContext,
536 mut coin_spends: HashMap<Bytes32, Spend>,
537 ) -> Result<Outputs, DriverError> {
538 for item in self.xch.items {
539 let spend = coin_spends
540 .remove(&item.asset.coin_id())
541 .ok_or(DriverError::MissingSpend)?;
542 ctx.spend(item.asset, spend)?;
543 }
544
545 for cat in self.cats.into_values() {
546 let mut cat_spends = Vec::new();
547 for item in cat.items {
548 let spend = coin_spends
549 .remove(&item.asset.coin_id())
550 .ok_or(DriverError::MissingSpend)?;
551 cat_spends.push(CatSpend::new(item.asset, spend));
552 }
553 Cat::spend_all(ctx, &cat_spends)?;
554 }
555
556 for did in self.dids.into_values() {
557 for item in did.lineage {
558 let spend = coin_spends
559 .remove(&item.asset.coin_id())
560 .ok_or(DriverError::MissingSpend)?;
561 item.asset.spend(ctx, spend)?;
562 }
563 }
564
565 for nft in self.nfts.into_values() {
566 for item in nft.lineage {
567 let spend = coin_spends
568 .remove(&item.asset.coin_id())
569 .ok_or(DriverError::MissingSpend)?;
570 let _nft = item.asset.spend(ctx, spend)?;
571 }
572 }
573
574 for option in self.options.into_values() {
575 for item in option.lineage {
576 let spend = coin_spends
577 .remove(&item.asset.coin_id())
578 .ok_or(DriverError::MissingSpend)?;
579 let _option = item.asset.spend(ctx, spend)?;
580 }
581 }
582
583 Ok(self.outputs)
584 }
585}
586
587pub trait AddAsset {
588 fn add(self, spends: &mut Spends);
589}
590
591impl AddAsset for Coin {
592 fn add(self, spends: &mut Spends) {
593 spends.xch.items.push(FungibleSpend::new(self, false));
594 }
595}
596
597impl AddAsset for Cat {
598 fn add(self, spends: &mut Spends) {
599 spends
600 .cats
601 .entry(Id::Existing(self.info.asset_id))
602 .or_default()
603 .items
604 .push(FungibleSpend::new(self, false));
605 }
606}
607
608impl AddAsset for Did {
609 fn add(self, spends: &mut Spends) {
610 spends.dids.insert(
611 Id::Existing(self.info.launcher_id),
612 SingletonSpends::new(self, false),
613 );
614 }
615}
616
617impl AddAsset for Nft {
618 fn add(self, spends: &mut Spends) {
619 spends.nfts.insert(
620 Id::Existing(self.info.launcher_id),
621 SingletonSpends::new(self, false),
622 );
623 }
624}
625
626impl AddAsset for OptionContract {
627 fn add(self, spends: &mut Spends) {
628 spends.options.insert(
629 Id::Existing(self.info.launcher_id),
630 SingletonSpends::new(self, false),
631 );
632 }
633}