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
48 Ok(Self {
49 state,
50 preimage,
51 change: change_proofs,
52 amount,
53 fee_paid,
54 })
55 }
56
57 pub fn total_amount(&self) -> Amount {
59 self.amount + self.fee_paid
60 }
61}
62
63#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
65pub struct ProofInfo {
66 pub proof: Proof,
68 pub y: PublicKey,
70 pub mint_url: MintUrl,
72 pub state: State,
74 pub spending_condition: Option<SpendingConditions>,
76 pub unit: CurrencyUnit,
78}
79
80impl ProofInfo {
81 pub fn new(
83 proof: Proof,
84 mint_url: MintUrl,
85 state: State,
86 unit: CurrencyUnit,
87 ) -> Result<Self, Error> {
88 let y = proof.y()?;
89
90 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
91
92 Ok(Self {
93 proof,
94 y,
95 mint_url,
96 state,
97 spending_condition,
98 unit,
99 })
100 }
101
102 pub fn matches_conditions(
104 &self,
105 mint_url: &Option<MintUrl>,
106 unit: &Option<CurrencyUnit>,
107 state: &Option<Vec<State>>,
108 spending_conditions: &Option<Vec<SpendingConditions>>,
109 ) -> bool {
110 if let Some(mint_url) = mint_url {
111 if mint_url.ne(&self.mint_url) {
112 return false;
113 }
114 }
115
116 if let Some(unit) = unit {
117 if unit.ne(&self.unit) {
118 return false;
119 }
120 }
121
122 if let Some(state) = state {
123 if !state.contains(&self.state) {
124 return false;
125 }
126 }
127
128 if let Some(spending_conditions) = spending_conditions {
129 match &self.spending_condition {
130 None => {
131 if !spending_conditions.is_empty() {
132 return false;
133 }
134 }
135 Some(s) => {
136 if !spending_conditions.contains(s) {
137 return false;
138 }
139 }
140 }
141 }
142
143 true
144 }
145}
146
147#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
150pub struct PaymentProcessorKey {
151 pub unit: CurrencyUnit,
153 pub method: PaymentMethod,
155}
156
157impl PaymentProcessorKey {
158 pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
160 Self { unit, method }
161 }
162}
163
164#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
166pub struct QuoteTTL {
167 pub mint_ttl: u64,
169 pub melt_ttl: u64,
171}
172
173impl QuoteTTL {
174 pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
176 Self { mint_ttl, melt_ttl }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use std::str::FromStr;
183
184 use cashu::SecretKey;
185
186 use super::{Melted, ProofInfo};
187 use crate::mint_url::MintUrl;
188 use crate::nuts::{CurrencyUnit, Id, Proof, PublicKey, SpendingConditions, State};
189 use crate::secret::Secret;
190 use crate::Amount;
191
192 #[test]
193 fn test_melted() {
194 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
195 let proof = Proof::new(
196 Amount::from(64),
197 keyset_id,
198 Secret::generate(),
199 PublicKey::from_hex(
200 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
201 )
202 .unwrap(),
203 );
204 let melted = Melted::from_proofs(
205 super::MeltQuoteState::Paid,
206 Some("preimage".to_string()),
207 Amount::from(64),
208 vec![proof.clone()],
209 None,
210 )
211 .unwrap();
212 assert_eq!(melted.amount, Amount::from(64));
213 assert_eq!(melted.fee_paid, Amount::ZERO);
214 assert_eq!(melted.total_amount(), Amount::from(64));
215 }
216
217 #[test]
218 fn test_melted_with_change() {
219 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
220 let proof = Proof::new(
221 Amount::from(64),
222 keyset_id,
223 Secret::generate(),
224 PublicKey::from_hex(
225 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
226 )
227 .unwrap(),
228 );
229 let change_proof = Proof::new(
230 Amount::from(32),
231 keyset_id,
232 Secret::generate(),
233 PublicKey::from_hex(
234 "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
235 )
236 .unwrap(),
237 );
238 let melted = Melted::from_proofs(
239 super::MeltQuoteState::Paid,
240 Some("preimage".to_string()),
241 Amount::from(31),
242 vec![proof.clone()],
243 Some(vec![change_proof.clone()]),
244 )
245 .unwrap();
246 assert_eq!(melted.amount, Amount::from(31));
247 assert_eq!(melted.fee_paid, Amount::from(1));
248 assert_eq!(melted.total_amount(), Amount::from(32));
249 }
250
251 #[test]
252 fn test_matches_conditions() {
253 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
254 let proof = Proof::new(
255 Amount::from(64),
256 keyset_id,
257 Secret::new("test_secret"),
258 PublicKey::from_hex(
259 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
260 )
261 .unwrap(),
262 );
263
264 let mint_url = MintUrl::from_str("https://example.com").unwrap();
265 let proof_info =
266 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
267
268 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
270 assert!(!proof_info.matches_conditions(
271 &Some(MintUrl::from_str("https://different.com").unwrap()),
272 &None,
273 &None,
274 &None
275 ));
276
277 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
279 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
280
281 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
283 assert!(proof_info.matches_conditions(
284 &None,
285 &None,
286 &Some(vec![State::Unspent, State::Spent]),
287 &None
288 ));
289 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
290
291 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
293
294 assert!(proof_info.matches_conditions(
296 &Some(mint_url),
297 &Some(CurrencyUnit::Sat),
298 &Some(vec![State::Unspent]),
299 &None
300 ));
301 }
302
303 #[test]
304 fn test_matches_conditions_with_spending_conditions() {
305 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
310 let proof = Proof::new(
311 Amount::from(64),
312 keyset_id,
313 Secret::new("test_secret"),
314 PublicKey::from_hex(
315 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
316 )
317 .unwrap(),
318 );
319
320 let mint_url = MintUrl::from_str("https://example.com").unwrap();
321 let proof_info =
322 ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
323
324 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
326
327 let dummy_condition = SpendingConditions::P2PKConditions {
329 data: SecretKey::generate().public_key(),
330 conditions: None,
331 };
332 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
338pub struct FeeReserve {
339 pub min_fee_reserve: Amount,
341 pub percent_fee_reserve: f32,
343}