1use chia_protocol::{Bytes32, Coin};
2use chia_puzzle_types::{
3 Memos,
4 cat::{CatArgs, GenesisByCoinIdTailArgs},
5 offer::NotarizedPayment,
6};
7use chia_puzzles::{SETTLEMENT_PAYMENT_HASH, SINGLETON_LAUNCHER_HASH};
8use chia_sdk_types::conditions::{AssertPuzzleAnnouncement, CreateCoin};
9
10use crate::{
11 Asset, Cat, Delta, DriverError, Launcher, OptionLauncher, OptionLauncherInfo, OptionType,
12 Output, OutputSet, SpendContext, SpendKind,
13};
14
15#[derive(Debug, Clone)]
16pub struct FungibleSpends<A> {
17 pub items: Vec<FungibleSpend<A>>,
18 pub payment_assertions: Vec<AssertPuzzleAnnouncement>,
19}
20
21impl<A> FungibleSpends<A>
22where
23 A: FungibleAsset,
24{
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn selected_amount(&self) -> u64 {
30 self.items
31 .iter()
32 .filter(|item| !item.ephemeral)
33 .map(|item| item.asset.amount())
34 .sum()
35 }
36
37 pub fn output_source(
38 &mut self,
39 ctx: &mut SpendContext,
40 output: &Output,
41 ) -> Result<usize, DriverError> {
42 if let Some(index) = self
43 .items
44 .iter()
45 .position(|item| item.kind.is_allowed(output, &item.asset.constraints()))
46 {
47 return Ok(index);
48 }
49
50 self.intermediate_source(ctx)
51 }
52
53 pub fn notarized_payment_source(
54 &mut self,
55 notarized_payment: &NotarizedPayment,
56 ) -> Result<usize, DriverError> {
57 if let Some(index) = self.items.iter().position(|item| {
58 item.kind.is_settlement()
59 && notarized_payment.payments.iter().all(|payment| {
60 item.kind.is_allowed(
61 &Output::new(payment.puzzle_hash, payment.amount),
62 &item.asset.constraints(),
63 )
64 })
65 }) {
66 return Ok(index);
67 }
68
69 self.intermediate_settlement_source()?
70 .ok_or(DriverError::NoSourceForOutput)
71 }
72
73 pub fn run_tail_source(&mut self, ctx: &mut SpendContext) -> Result<usize, DriverError> {
74 if let Some(index) = self
75 .items
76 .iter()
77 .position(|item| item.kind.can_run_cat_tail())
78 {
79 return Ok(index);
80 }
81
82 self.intermediate_source(ctx)
83 }
84
85 pub fn cat_issuance_source(
86 &mut self,
87 ctx: &mut SpendContext,
88 asset_id: Option<Bytes32>,
89 amount: u64,
90 ) -> Result<usize, DriverError> {
91 if let Some(index) = self.items.iter().position(|item| {
92 item.kind.is_allowed(
93 &Output::new(
94 CatArgs::curry_tree_hash(
95 asset_id.unwrap_or_else(|| {
96 GenesisByCoinIdTailArgs::curry_tree_hash(item.asset.coin_id()).into()
97 }),
98 item.asset.p2_puzzle_hash().into(),
99 )
100 .into(),
101 amount,
102 ),
103 &item.asset.constraints(),
104 )
105 }) {
106 return Ok(index);
107 }
108
109 self.intermediate_source(ctx)
110 }
111
112 pub fn intermediate_source(&mut self, ctx: &mut SpendContext) -> Result<usize, DriverError> {
113 let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
114 item.kind
115 .find_amount(item.asset.p2_puzzle_hash(), &item.asset.constraints())
116 .map(|amount| (index, amount))
117 }) else {
118 return Err(DriverError::NoSourceForOutput);
119 };
120
121 let source = &mut self.items[index];
122
123 source.kind.create_intermediate_coin(CreateCoin::new(
124 source.asset.p2_puzzle_hash(),
125 amount,
126 source
127 .asset
128 .child_memos(ctx, source.asset.p2_puzzle_hash())?,
129 ));
130
131 let child = FungibleSpend::new(
132 source
133 .asset
134 .make_child(source.asset.p2_puzzle_hash(), amount),
135 true,
136 );
137
138 self.items.push(child);
139
140 Ok(self.items.len() - 1)
141 }
142
143 pub fn intermediate_settlement_source(&mut self) -> Result<Option<usize>, DriverError> {
144 let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
145 item.kind
146 .find_amount(SETTLEMENT_PAYMENT_HASH.into(), &item.asset.constraints())
147 .map(|amount| (index, amount))
148 }) else {
149 return Ok(None);
150 };
151
152 let source = &mut self.items[index];
153
154 source.kind.create_intermediate_coin(CreateCoin::new(
155 SETTLEMENT_PAYMENT_HASH.into(),
156 amount,
157 Memos::None,
158 ));
159
160 let child = FungibleSpend::new(
161 source
162 .asset
163 .make_child(SETTLEMENT_PAYMENT_HASH.into(), amount),
164 true,
165 );
166
167 self.items.push(child);
168
169 Ok(Some(self.items.len() - 1))
170 }
171
172 pub fn intermediate_conditions_source(
173 &mut self,
174 ctx: &mut SpendContext,
175 intermediate_puzzle_hash: Bytes32,
176 ) -> Result<Option<usize>, DriverError> {
177 let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
178 item.kind
179 .find_amount(intermediate_puzzle_hash, &item.asset.constraints())
180 .map(|amount| (index, amount))
181 }) else {
182 return Ok(None);
183 };
184
185 let source = &mut self.items[index];
186
187 let hint = ctx.hint(intermediate_puzzle_hash)?;
188
189 source.kind.create_intermediate_coin(CreateCoin::new(
190 intermediate_puzzle_hash,
191 amount,
192 hint,
193 ));
194
195 let child = FungibleSpend::new(
196 source.asset.make_child(intermediate_puzzle_hash, amount),
197 true,
198 );
199
200 self.items.push(child);
201
202 Ok(Some(self.items.len() - 1))
203 }
204
205 pub fn launcher_source(&mut self) -> Result<(usize, u64), DriverError> {
206 let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
207 item.kind
208 .find_amount(SINGLETON_LAUNCHER_HASH.into(), &item.asset.constraints())
209 .map(|amount| (index, amount))
210 }) else {
211 return Err(DriverError::NoSourceForOutput);
212 };
213
214 Ok((index, amount))
215 }
216
217 pub fn create_launcher(
218 &mut self,
219 singleton_amount: u64,
220 ) -> Result<(usize, Launcher), DriverError> {
221 let (index, launcher_amount) = self.launcher_source()?;
222
223 let (create_coin, launcher) =
224 Launcher::create_early(self.items[index].asset.coin_id(), launcher_amount);
225
226 self.items[index].kind.create_intermediate_coin(create_coin);
227
228 Ok((index, launcher.with_singleton_amount(singleton_amount)))
229 }
230
231 pub fn create_option_launcher(
232 &mut self,
233 ctx: &mut SpendContext,
234 singleton_amount: u64,
235 creator_puzzle_hash: Bytes32,
236 seconds: u64,
237 underlying_amount: u64,
238 strike_type: OptionType,
239 ) -> Result<(usize, OptionLauncher), DriverError> {
240 let (index, launcher_amount) = self.launcher_source()?;
241
242 let source = &mut self.items[index];
243
244 let (create_coin, launcher) = OptionLauncher::create_early(
245 ctx,
246 source.asset.coin_id(),
247 launcher_amount,
248 OptionLauncherInfo::new(
249 creator_puzzle_hash,
250 source.asset.p2_puzzle_hash(),
251 seconds,
252 underlying_amount,
253 strike_type,
254 ),
255 singleton_amount,
256 )?;
257
258 source.kind.create_intermediate_coin(create_coin);
259
260 Ok((index, launcher))
261 }
262
263 pub fn create_change(
264 &mut self,
265 ctx: &mut SpendContext,
266 delta: &Delta,
267 change_puzzle_hash: Bytes32,
268 ) -> Result<Option<A>, DriverError> {
269 let change = (self.selected_amount() + delta.input).saturating_sub(delta.output);
270
271 if change == 0 {
272 return Ok(None);
273 }
274
275 let output = Output::new(change_puzzle_hash, change);
276 let source = self.output_source(ctx, &output)?;
277 let item = &mut self.items[source];
278
279 let parent_puzzle_hash = item.asset.full_puzzle_hash();
280 let create_coin = CreateCoin::new(
281 change_puzzle_hash,
282 change,
283 item.asset.child_memos(ctx, change_puzzle_hash)?,
284 );
285 item.kind.create_coin_with_assertion(
286 ctx,
287 parent_puzzle_hash,
288 &mut self.payment_assertions,
289 create_coin,
290 );
291
292 Ok(Some(item.asset.make_child(change_puzzle_hash, change)))
293 }
294}
295
296impl<A> Default for FungibleSpends<A> {
297 fn default() -> Self {
298 Self {
299 items: Vec::new(),
300 payment_assertions: Vec::new(),
301 }
302 }
303}
304
305#[derive(Debug, Clone)]
306pub struct FungibleSpend<T> {
307 pub asset: T,
308 pub kind: SpendKind,
309 pub ephemeral: bool,
310}
311
312impl<T> FungibleSpend<T>
313where
314 T: FungibleAsset,
315{
316 pub fn new(asset: T, ephemeral: bool) -> Self {
317 let kind = if asset.p2_puzzle_hash() == SETTLEMENT_PAYMENT_HASH.into() {
318 SpendKind::settlement()
319 } else {
320 SpendKind::conditions()
321 };
322
323 Self {
324 asset,
325 kind,
326 ephemeral,
327 }
328 }
329}
330
331pub trait FungibleAsset: Clone + Asset {
332 #[must_use]
333 fn make_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self;
334 fn child_memos(
335 &self,
336 ctx: &mut SpendContext,
337 p2_puzzle_hash: Bytes32,
338 ) -> Result<Memos, DriverError>;
339}
340
341impl FungibleAsset for Coin {
342 fn make_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
343 Coin::new(self.coin_id(), p2_puzzle_hash, amount)
344 }
345
346 fn child_memos(
347 &self,
348 _ctx: &mut SpendContext,
349 _p2_puzzle_hash: Bytes32,
350 ) -> Result<Memos, DriverError> {
351 Ok(Memos::None)
352 }
353}
354
355impl FungibleAsset for Cat {
356 fn make_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
357 self.child(p2_puzzle_hash, amount)
358 }
359
360 fn child_memos(
361 &self,
362 ctx: &mut SpendContext,
363 p2_puzzle_hash: Bytes32,
364 ) -> Result<Memos, DriverError> {
365 ctx.hint(p2_puzzle_hash)
366 }
367}