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