1use chik_protocol::{Bytes32, Coin, CoinSpend, SpendBundle};
2use chik_puzzle_types::{
3 offer::{NotarizedPayment, Payment, SettlementPaymentsSolution},
4 Memos,
5};
6use chik_sdk_types::{announcement_id, conditions::AssertPuzzleAnnouncement};
7use indexmap::IndexMap;
8use klvm_traits::ToKlvm;
9use klvm_utils::{tree_hash, ToTreeHash, TreeHash};
10use klvmr::{Allocator, NodePtr};
11
12use crate::{DriverError, Offer, ParsedOffer, Puzzle, SpendContext};
13
14#[derive(Debug, Clone)]
15pub struct OfferBuilder<T> {
16 data: T,
17}
18
19#[derive(Debug, Clone)]
20pub struct Make {
21 nonce: Bytes32,
22 requested_payments: IndexMap<Bytes32, (Puzzle, Vec<NotarizedPayment>)>,
23 announcements: Vec<AssertPuzzleAnnouncement>,
24}
25
26#[derive(Debug, Clone)]
27pub struct Partial {
28 requested_payments: IndexMap<Bytes32, (Puzzle, Vec<NotarizedPayment>)>,
29}
30
31#[derive(Debug, Clone)]
32pub struct Take {
33 parsed_offer: ParsedOffer,
34}
35
36impl OfferBuilder<Make> {
37 pub fn new(nonce: Bytes32) -> Self {
38 Self {
39 data: Make {
40 nonce,
41 requested_payments: IndexMap::new(),
42 announcements: Vec::new(),
43 },
44 }
45 }
46
47 pub fn request<P>(
50 self,
51 ctx: &mut SpendContext,
52 puzzle: &P,
53 payments: Vec<Payment>,
54 ) -> Result<Self, DriverError>
55 where
56 P: ToKlvm<Allocator>,
57 {
58 let nonce = self.data.nonce;
59 self.request_with_nonce(ctx, puzzle, nonce, payments)
60 }
61
62 pub fn request_with_nonce<P>(
64 mut self,
65 ctx: &mut SpendContext,
66 puzzle: &P,
67 nonce: Bytes32,
68 payments: Vec<Payment>,
69 ) -> Result<Self, DriverError>
70 where
71 P: ToKlvm<Allocator>,
72 {
73 let puzzle_ptr = ctx.alloc(puzzle)?;
74 let puzzle_hash = ctx.tree_hash(puzzle_ptr).into();
75 let puzzle = Puzzle::parse(ctx, puzzle_ptr);
76
77 let notarized_payment = NotarizedPayment::new(nonce, payments);
78 let assertion = payment_assertion(
79 puzzle_hash,
80 &tree_hash_notarized_payment(ctx, ¬arized_payment),
81 );
82
83 self.data
84 .requested_payments
85 .entry(puzzle_hash)
86 .or_insert_with(|| (puzzle, Vec::new()))
87 .1
88 .push(notarized_payment);
89
90 self.data.announcements.push(assertion);
91
92 Ok(self)
93 }
94
95 pub fn finish(self) -> (Vec<AssertPuzzleAnnouncement>, OfferBuilder<Partial>) {
98 let partial = OfferBuilder {
99 data: Partial {
100 requested_payments: self.data.requested_payments,
101 },
102 };
103 (self.data.announcements, partial)
104 }
105}
106
107impl OfferBuilder<Partial> {
108 pub fn bundle(
109 self,
110 ctx: &mut SpendContext,
111 partial_spend_bundle: SpendBundle,
112 ) -> Result<Offer, DriverError> {
113 let mut spend_bundle = partial_spend_bundle;
114
115 for (puzzle_hash, (puzzle, notarized_payments)) in self.data.requested_payments {
116 let puzzle_reveal = ctx.serialize(&puzzle.ptr())?;
117 let solution = ctx.serialize(&SettlementPaymentsSolution { notarized_payments })?;
118
119 spend_bundle.coin_spends.push(CoinSpend {
120 coin: Coin::new(Bytes32::default(), puzzle_hash, 0),
121 puzzle_reveal,
122 solution,
123 });
124 }
125
126 Ok(spend_bundle.into())
127 }
128
129 pub fn take(self, partial_spend_bundle: SpendBundle) -> OfferBuilder<Take> {
131 OfferBuilder {
132 data: Take {
133 parsed_offer: ParsedOffer {
134 coin_spends: partial_spend_bundle.coin_spends,
135 aggregated_signature: partial_spend_bundle.aggregated_signature,
136 requested_payments: self.data.requested_payments,
137 },
138 },
139 }
140 }
141}
142
143impl OfferBuilder<Take> {
144 pub fn from_parsed_offer(parsed_offer: ParsedOffer) -> Self {
145 Self {
146 data: Take { parsed_offer },
147 }
148 }
149
150 pub fn fulfill(&mut self) -> Option<(Puzzle, Vec<NotarizedPayment>)> {
151 Some(
152 self.data
153 .parsed_offer
154 .requested_payments
155 .shift_remove_index(0)?
156 .1,
157 )
158 }
159
160 pub fn bundle(self, other_spend_bundle: SpendBundle) -> SpendBundle {
163 assert_eq!(self.data.parsed_offer.requested_payments.len(), 0);
164
165 SpendBundle::aggregate(&[
166 SpendBundle::new(
167 self.data.parsed_offer.coin_spends,
168 self.data.parsed_offer.aggregated_signature,
169 ),
170 other_spend_bundle,
171 ])
172 }
173}
174
175pub fn payment_assertion(
176 puzzle_hash: Bytes32,
177 notarized_payment: &NotarizedPayment<TreeHash>,
178) -> AssertPuzzleAnnouncement {
179 AssertPuzzleAnnouncement::new(announcement_id(puzzle_hash, notarized_payment.tree_hash()))
180}
181
182pub fn tree_hash_notarized_payment(
183 allocator: &Allocator,
184 notarized_payment: &NotarizedPayment<NodePtr>,
185) -> NotarizedPayment<TreeHash> {
186 NotarizedPayment {
187 nonce: notarized_payment.nonce,
188 payments: notarized_payment
189 .payments
190 .iter()
191 .map(|payment| Payment {
192 puzzle_hash: payment.puzzle_hash,
193 amount: payment.amount,
194 memos: match payment.memos {
195 Memos::Some(memos) => Memos::Some(tree_hash(allocator, memos)),
196 Memos::None => Memos::None,
197 },
198 })
199 .collect(),
200 }
201}