1use chia_protocol::Bytes32;
2use chia_puzzle_types::offer::{NotarizedPayment, SettlementPaymentsSolution};
3use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
4use chia_sdk_types::{
5 conditions::AssertPuzzleAnnouncement, payment_assertion, tree_hash_notarized_payment,
6};
7use clvm_traits::FromClvm;
8use clvmr::{Allocator, NodePtr};
9use indexmap::IndexMap;
10
11use crate::{
12 Action, AssetInfo, CatAssetInfo, CatInfo, DriverError, HashedPtr, Id, Layer, NftAssetInfo,
13 NftInfo, OfferAmounts, OptionAssetInfo, OptionInfo, Puzzle, SettlementLayer, SpendContext,
14};
15
16#[derive(Debug, Default, Clone)]
17pub struct RequestedPayments {
18 pub xch: Vec<NotarizedPayment>,
19 pub cats: IndexMap<Bytes32, Vec<NotarizedPayment>>,
20 pub nfts: IndexMap<Bytes32, Vec<NotarizedPayment>>,
21 pub options: IndexMap<Bytes32, Vec<NotarizedPayment>>,
22}
23
24impl RequestedPayments {
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn amounts(&self) -> OfferAmounts {
30 OfferAmounts {
31 xch: self
32 .xch
33 .iter()
34 .flat_map(|np| np.payments.iter().map(|p| p.amount))
35 .sum(),
36 cats: self
37 .cats
38 .iter()
39 .map(|(&launcher_id, nps)| {
40 (
41 launcher_id,
42 nps.iter()
43 .flat_map(|np| np.payments.iter().map(|p| p.amount))
44 .sum(),
45 )
46 })
47 .collect(),
48 }
49 }
50
51 pub fn assertions(
52 &self,
53 ctx: &mut SpendContext,
54 asset_info: &AssetInfo,
55 ) -> Result<Vec<AssertPuzzleAnnouncement>, DriverError> {
56 let mut assertions = Vec::new();
57
58 for notarized_payment in &self.xch {
59 assertions.push(payment_assertion(
60 SETTLEMENT_PAYMENT_HASH.into(),
61 tree_hash_notarized_payment(ctx, notarized_payment),
62 ));
63 }
64
65 for (&asset_id, notarized_payments) in &self.cats {
66 let default = CatAssetInfo::default();
67 let info = asset_info.cat(asset_id).unwrap_or(&default);
68
69 let puzzle_hash = CatInfo::new(
70 asset_id,
71 info.hidden_puzzle_hash,
72 SETTLEMENT_PAYMENT_HASH.into(),
73 )
74 .puzzle_hash()
75 .into();
76
77 for notarized_payment in notarized_payments {
78 assertions.push(payment_assertion(
79 puzzle_hash,
80 tree_hash_notarized_payment(ctx, notarized_payment),
81 ));
82 }
83 }
84
85 for (&launcher_id, notarized_payments) in &self.nfts {
86 let info = asset_info
87 .nft(launcher_id)
88 .ok_or(DriverError::MissingAssetInfo)?;
89
90 let puzzle_hash = NftInfo::new(
91 launcher_id,
92 info.metadata,
93 info.metadata_updater_puzzle_hash,
94 None,
95 info.royalty_puzzle_hash,
96 info.royalty_basis_points,
97 SETTLEMENT_PAYMENT_HASH.into(),
98 )
99 .puzzle_hash()
100 .into();
101
102 for notarized_payment in notarized_payments {
103 assertions.push(payment_assertion(
104 puzzle_hash,
105 tree_hash_notarized_payment(ctx, notarized_payment),
106 ));
107 }
108 }
109
110 for (&launcher_id, notarized_payments) in &self.options {
111 let info = asset_info
112 .option(launcher_id)
113 .ok_or(DriverError::MissingAssetInfo)?;
114
115 let puzzle_hash = OptionInfo::new(
116 launcher_id,
117 info.underlying_coin_id,
118 info.underlying_delegated_puzzle_hash,
119 SETTLEMENT_PAYMENT_HASH.into(),
120 )
121 .puzzle_hash()
122 .into();
123
124 for notarized_payment in notarized_payments {
125 assertions.push(payment_assertion(
126 puzzle_hash,
127 tree_hash_notarized_payment(ctx, notarized_payment),
128 ));
129 }
130 }
131
132 Ok(assertions)
133 }
134
135 pub fn actions(&self) -> Vec<Action> {
136 let mut actions = Vec::new();
137
138 for notarized_payment in &self.xch {
139 actions.push(Action::settle(Id::Xch, notarized_payment.clone()));
140 }
141
142 for (&asset_id, notarized_payments) in &self.cats {
143 for notarized_payment in notarized_payments {
144 actions.push(Action::settle(
145 Id::Existing(asset_id),
146 notarized_payment.clone(),
147 ));
148 }
149 }
150
151 for (&launcher_id, notarized_payments) in &self.nfts {
152 for notarized_payment in notarized_payments {
153 actions.push(Action::settle(
154 Id::Existing(launcher_id),
155 notarized_payment.clone(),
156 ));
157 }
158 }
159
160 for (&launcher_id, notarized_payments) in &self.options {
161 for notarized_payment in notarized_payments {
162 actions.push(Action::settle(
163 Id::Existing(launcher_id),
164 notarized_payment.clone(),
165 ));
166 }
167 }
168
169 actions
170 }
171
172 pub fn extend(&mut self, other: Self) -> Result<(), DriverError> {
173 for payment in other.xch {
174 self.xch.push(payment);
175 }
176
177 for (asset_id, payments) in other.cats {
178 self.cats.entry(asset_id).or_default().extend(payments);
179 }
180
181 for (launcher_id, payments) in other.nfts {
182 self.nfts.entry(launcher_id).or_default().extend(payments);
183 }
184
185 for (launcher_id, payments) in other.options {
186 self.options
187 .entry(launcher_id)
188 .or_default()
189 .extend(payments);
190 }
191
192 Ok(())
193 }
194
195 pub fn parse(
196 &mut self,
197 allocator: &Allocator,
198 asset_info: &mut AssetInfo,
199 puzzle: Puzzle,
200 solution: NodePtr,
201 ) -> Result<(), DriverError> {
202 let notarized_payments =
203 SettlementPaymentsSolution::from_clvm(allocator, solution)?.notarized_payments;
204
205 if SettlementLayer::parse_puzzle(allocator, puzzle)?.is_some() {
206 self.xch.extend(notarized_payments);
207 } else if let Some((cat, _)) = CatInfo::parse(allocator, puzzle)? {
208 self.cats
209 .entry(cat.asset_id)
210 .or_default()
211 .extend(notarized_payments);
212
213 let info = CatAssetInfo::new(cat.hidden_puzzle_hash);
214 asset_info.insert_cat(cat.asset_id, info)?;
215 } else if let Some((nft, _)) = NftInfo::<HashedPtr>::parse(allocator, puzzle)? {
216 self.nfts
217 .entry(nft.launcher_id)
218 .or_default()
219 .extend(notarized_payments);
220
221 let info = NftAssetInfo::new(
222 nft.metadata,
223 nft.metadata_updater_puzzle_hash,
224 nft.royalty_puzzle_hash,
225 nft.royalty_basis_points,
226 );
227 asset_info.insert_nft(nft.launcher_id, info)?;
228 } else if let Some((option, _)) = OptionInfo::parse(allocator, puzzle)? {
229 self.options
230 .entry(option.launcher_id)
231 .or_default()
232 .extend(notarized_payments);
233
234 let info = OptionAssetInfo::new(
235 option.underlying_coin_id,
236 option.underlying_delegated_puzzle_hash,
237 );
238 asset_info.insert_option(option.launcher_id, info)?;
239 }
240
241 Ok(())
242 }
243}