chia_sdk_driver/offers/
requested_payments.rs

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}