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