chia_sdk_driver/clear_signing/
children.rs1use std::collections::VecDeque;
2
3use chia_protocol::{Bytes32, Coin};
4use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
5use chia_sdk_types::{Condition, conditions::CreateCoin};
6
7use crate::{
8 BURN_PUZZLE_HASH, Cat, DriverError, Facts, Nft, ParsedAsset, ParsedMemos, RevealedCoinSpend,
9 RevealedP2Puzzle, Reveals, SpendContext, parse_memos,
10};
11
12#[derive(Debug, Clone)]
13pub struct ParsedChild {
14 pub asset: ParsedAsset,
15 pub memos: ParsedMemos,
16 pub transfer_type: TransferType,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum TransferType {
21 Sent,
22 Burned,
23 Offered,
24 OfferPreSplit(OfferPreSplitInfo),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct OfferPreSplitInfo {
29 pub launcher_id: Bytes32,
30 pub nonce: usize,
31 pub fixed_conditions: Vec<Condition>,
32 pub settlement_amount: u64,
33}
34
35pub fn parse_children(
36 reveals: &mut Reveals,
37 facts: &mut Facts,
38 ctx: &mut SpendContext,
39 asset: &ParsedAsset,
40 spend: RevealedCoinSpend,
41 conditions: &[Condition],
42 is_claw_back: bool,
43) -> Result<Vec<ParsedChild>, DriverError> {
44 let mut children = Vec::new();
46
47 let mut cats = if matches!(asset, ParsedAsset::Cat(_)) {
49 if let Some(cats) = Cat::parse_children(ctx, spend.coin, spend.puzzle, spend.solution)? {
50 VecDeque::from(cats)
51 } else {
52 return Err(DriverError::MissingChild);
53 }
54 } else {
55 VecDeque::new()
56 };
57
58 for condition in conditions {
59 match condition {
60 Condition::AssertPuzzleAnnouncement(condition) => {
61 facts.assert_puzzle_announcement(condition.announcement_id);
62 }
63 Condition::AssertConcurrentSpend(condition) => {
64 facts.assert_spend(condition.coin_id);
65 }
66 Condition::AssertBeforeSecondsAbsolute(condition) if !is_claw_back => {
70 facts.update_actual_expiration_time(condition.seconds);
71 }
72 Condition::ReserveFee(condition) => {
73 facts.add_reserved_fees(condition.amount);
74 }
75 Condition::CreateCoin(condition) => {
76 match &asset {
77 ParsedAsset::Xch(_) | ParsedAsset::Bulletin(_) => {
80 let memos = parse_memos(reveals, ctx, *condition, false);
81 let transfer_type =
82 calculate_transfer_type(reveals, &memos, condition.amount);
83
84 children.push(ParsedChild {
85 asset: ParsedAsset::Xch(Coin::new(
86 spend.coin.coin_id(),
87 condition.puzzle_hash,
88 condition.amount,
89 )),
90 memos,
91 transfer_type,
92 });
93 }
94 ParsedAsset::Nft(_) => {
97 if condition.amount % 2 == 1 {
98 let Some(nft) =
99 Nft::parse_child(ctx, spend.coin, spend.puzzle, spend.solution)?
100 else {
101 return Err(DriverError::MissingChild);
102 };
103
104 let memos = parse_memos(reveals, ctx, *condition, true);
105 let transfer_type =
106 calculate_transfer_type(reveals, &memos, condition.amount);
107
108 children.push(ParsedChild {
109 asset: ParsedAsset::Nft(nft),
110 memos,
111 transfer_type,
112 });
113 } else {
114 let memos = parse_memos(reveals, ctx, *condition, false);
115 let transfer_type =
116 calculate_transfer_type(reveals, &memos, condition.amount);
117
118 children.push(ParsedChild {
119 asset: ParsedAsset::Xch(Coin::new(
120 spend.coin.coin_id(),
121 condition.puzzle_hash,
122 condition.amount,
123 )),
124 memos,
125 transfer_type,
126 });
127 }
128 }
129 ParsedAsset::Cat(parent) => {
131 let cat = cats.pop_front().ok_or(DriverError::MissingChild)?;
132
133 if cat.info.hidden_puzzle_hash != parent.info.hidden_puzzle_hash {
136 return Err(DriverError::RevocableChild);
137 }
138
139 let memos = parse_memos(
140 reveals,
141 ctx,
142 CreateCoin::new(
143 cat.info.p2_puzzle_hash,
144 condition.amount,
145 condition.memos,
146 ),
147 true,
148 );
149 let transfer_type =
150 calculate_transfer_type(reveals, &memos, condition.amount);
151
152 children.push(ParsedChild {
153 asset: ParsedAsset::Cat(cat),
154 memos,
155 transfer_type,
156 });
157 }
158 }
159 }
160 _ => {}
161 }
162 }
163
164 if !cats.is_empty() {
165 return Err(DriverError::MissingChild);
166 }
167
168 Ok(children)
169}
170
171fn calculate_transfer_type(
172 reveals: &Reveals,
173 memos: &ParsedMemos,
174 input_amount: u64,
175) -> TransferType {
176 if memos.p2_puzzle_hash == BURN_PUZZLE_HASH {
177 TransferType::Burned
178 } else if memos.p2_puzzle_hash == SETTLEMENT_PAYMENT_HASH.into() {
179 TransferType::Offered
180 } else if memos.clawback.is_none()
181 && let Some(RevealedP2Puzzle::P2ConditionsOrSingleton(reveal)) =
182 reveals.p2_puzzle(memos.p2_puzzle_hash.into())
183 && let Some(fixed_conditions) = &memos.fixed_conditions
184 {
185 let mut reserved_fee = 0;
186
187 for condition in fixed_conditions {
188 if let Condition::ReserveFee(condition) = condition {
189 reserved_fee += condition.amount;
190 }
191 }
192
193 TransferType::OfferPreSplit(OfferPreSplitInfo {
194 launcher_id: reveal.launcher_id,
195 nonce: reveal.nonce,
196 fixed_conditions: fixed_conditions.clone(),
197 settlement_amount: input_amount.saturating_sub(reserved_fee),
198 })
199 } else {
200 TransferType::Sent
201 }
202}