1use serde::{Deserialize, Serialize};
4
5use crate::error::Error;
6use crate::mint_url::MintUrl;
7use crate::nuts::nut00::ProofsMethods;
8use crate::nuts::{
9 CurrencyUnit, MeltQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SpendingConditions,
10 State,
11};
12use crate::Amount;
13
14#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
16pub struct Melted {
17 pub state: MeltQuoteState,
19 pub preimage: Option<String>,
21 pub change: Option<Proofs>,
23 pub amount: Amount,
25 pub fee_paid: Amount,
27}
28
29impl Melted {
30 pub fn from_proofs(
32 state: MeltQuoteState,
33 preimage: Option<String>,
34 amount: Amount,
35 proofs: Proofs,
36 change_proofs: Option<Proofs>,
37 ) -> Result<Self, Error> {
38 let proofs_amount = proofs.total_amount()?;
39 let change_amount = match &change_proofs {
40 Some(change_proofs) => change_proofs.total_amount()?,
41 None => Amount::ZERO,
42 };
43
44 let fee_paid = proofs_amount
45 .checked_sub(amount + change_amount)
46 .ok_or(Error::AmountOverflow)
47 .unwrap();
48
49 Ok(Self {
50 state,
51 preimage,
52 change: change_proofs,
53 amount,
54 fee_paid,
55 })
56 }
57
58 pub fn total_amount(&self) -> Amount {
60 self.amount + self.fee_paid
61 }
62}
63
64#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
66pub struct ProofInfo {
67 pub proof: Proof,
69 pub y: PublicKey,
71 pub mint_url: MintUrl,
73 pub state: State,
75 pub spending_condition: Option<SpendingConditions>,
77 pub unit: CurrencyUnit,
79}
80
81impl ProofInfo {
82 pub fn new(
84 proof: Proof,
85 mint_url: MintUrl,
86 state: State,
87 unit: CurrencyUnit,
88 ) -> Result<Self, Error> {
89 let y = proof.y()?;
90
91 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
92
93 Ok(Self {
94 proof,
95 y,
96 mint_url,
97 state,
98 spending_condition,
99 unit,
100 })
101 }
102
103 pub fn matches_conditions(
105 &self,
106 mint_url: &Option<MintUrl>,
107 unit: &Option<CurrencyUnit>,
108 state: &Option<Vec<State>>,
109 spending_conditions: &Option<Vec<SpendingConditions>>,
110 ) -> bool {
111 if let Some(mint_url) = mint_url {
112 if mint_url.ne(&self.mint_url) {
113 return false;
114 }
115 }
116
117 if let Some(unit) = unit {
118 if unit.ne(&self.unit) {
119 return false;
120 }
121 }
122
123 if let Some(state) = state {
124 if !state.contains(&self.state) {
125 return false;
126 }
127 }
128
129 if let Some(spending_conditions) = spending_conditions {
130 match &self.spending_condition {
131 None => {
132 if !spending_conditions.is_empty() {
133 return false;
134 }
135 }
136 Some(s) => {
137 if !spending_conditions.contains(s) {
138 return false;
139 }
140 }
141 }
142 }
143
144 true
145 }
146}
147
148#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
151pub struct PaymentProcessorKey {
152 pub unit: CurrencyUnit,
154 pub method: PaymentMethod,
156}
157
158impl PaymentProcessorKey {
159 pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
161 Self { unit, method }
162 }
163}
164
165#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
167pub struct QuoteTTL {
168 pub mint_ttl: u64,
170 pub melt_ttl: u64,
172}
173
174impl QuoteTTL {
175 pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
177 Self { mint_ttl, melt_ttl }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use std::str::FromStr;
184
185 use cashu::SecretKey;
186
187 use super::{Melted, ProofInfo};
188 use crate::mint_url::MintUrl;
189 use crate::nuts::{CurrencyUnit, Id, Proof, PublicKey, SpendingConditions, State};
190 use crate::secret::Secret;
191 use crate::Amount;
192
193 #[test]
194 fn test_melted() {
195 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
196 let proof = Proof::new(
197 Amount::from(64),
198 keyset_id,
199 Secret::generate(),
200 PublicKey::from_hex(
201 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
202 )
203 .unwrap(),
204 );
205 let melted = Melted::from_proofs(
206 super::MeltQuoteState::Paid,
207 Some("preimage".to_string()),
208 Amount::from(64),
209 vec![proof.clone()],
210 None,
211 )
212 .unwrap();
213 assert_eq!(melted.amount, Amount::from(64));
214 assert_eq!(melted.fee_paid, Amount::ZERO);
215 assert_eq!(melted.total_amount(), Amount::from(64));
216 }
217
218 #[test]
219 fn test_melted_with_change() {
220 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
221 let proof = Proof::new(
222 Amount::from(64),
223 keyset_id,
224 Secret::generate(),
225 PublicKey::from_hex(
226 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
227 )
228 .unwrap(),
229 );
230 let change_proof = Proof::new(
231 Amount::from(32),
232 keyset_id,
233 Secret::generate(),
234 PublicKey::from_hex(
235 "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
236 )
237 .unwrap(),
238 );
239 let melted = Melted::from_proofs(
240 super::MeltQuoteState::Paid,
241 Some("preimage".to_string()),
242 Amount::from(31),
243 vec![proof.clone()],
244 Some(vec![change_proof.clone()]),
245 )
246 .unwrap();
247 assert_eq!(melted.amount, Amount::from(31));
248 assert_eq!(melted.fee_paid, Amount::from(1));
249 assert_eq!(melted.total_amount(), Amount::from(32));
250 }
251
252 #[test]
253 fn test_matches_conditions() {
254 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
255 let proof = Proof::new(
256 Amount::from(64),
257 keyset_id,
258 Secret::new("test_secret"),
259 PublicKey::from_hex(
260 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
261 )
262 .unwrap(),
263 );
264
265 let mint_url = MintUrl::from_str("https://example.com").unwrap();
266 let proof_info =
267 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
268
269 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
271 assert!(!proof_info.matches_conditions(
272 &Some(MintUrl::from_str("https://different.com").unwrap()),
273 &None,
274 &None,
275 &None
276 ));
277
278 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
280 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
281
282 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
284 assert!(proof_info.matches_conditions(
285 &None,
286 &None,
287 &Some(vec![State::Unspent, State::Spent]),
288 &None
289 ));
290 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
291
292 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
294
295 assert!(proof_info.matches_conditions(
297 &Some(mint_url),
298 &Some(CurrencyUnit::Sat),
299 &Some(vec![State::Unspent]),
300 &None
301 ));
302 }
303
304 #[test]
305 fn test_matches_conditions_with_spending_conditions() {
306 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
311 let proof = Proof::new(
312 Amount::from(64),
313 keyset_id,
314 Secret::new("test_secret"),
315 PublicKey::from_hex(
316 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
317 )
318 .unwrap(),
319 );
320
321 let mint_url = MintUrl::from_str("https://example.com").unwrap();
322 let proof_info =
323 ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
324
325 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
327
328 let dummy_condition = SpendingConditions::P2PKConditions {
330 data: SecretKey::generate().public_key(),
331 conditions: None,
332 };
333 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
339pub struct FeeReserve {
340 pub min_fee_reserve: Amount,
342 pub percent_fee_reserve: f32,
344}