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