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, 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}