common.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. //! Types
  2. use serde::{Deserialize, Serialize};
  3. use crate::error::Error;
  4. use crate::mint_url::MintUrl;
  5. use crate::nuts::nut00::ProofsMethods;
  6. use crate::nuts::{
  7. CurrencyUnit, MeltQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SpendingConditions,
  8. State,
  9. };
  10. use crate::Amount;
  11. /// Melt response with proofs
  12. #[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
  13. pub struct Melted {
  14. /// State of quote
  15. pub state: MeltQuoteState,
  16. /// Preimage of melt payment
  17. pub preimage: Option<String>,
  18. /// Melt change
  19. pub change: Option<Proofs>,
  20. /// Melt amount
  21. pub amount: Amount,
  22. /// Fee paid
  23. pub fee_paid: Amount,
  24. }
  25. impl Melted {
  26. /// Create new [`Melted`]
  27. pub fn from_proofs(
  28. state: MeltQuoteState,
  29. preimage: Option<String>,
  30. quote_amount: Amount,
  31. proofs: Proofs,
  32. change_proofs: Option<Proofs>,
  33. ) -> Result<Self, Error> {
  34. let proofs_amount = proofs.total_amount()?;
  35. let change_amount = match &change_proofs {
  36. Some(change_proofs) => change_proofs.total_amount()?,
  37. None => Amount::ZERO,
  38. };
  39. tracing::info!(
  40. "Proofs amount: {} Amount: {} Change: {}",
  41. proofs_amount,
  42. quote_amount,
  43. change_amount
  44. );
  45. let fee_paid = proofs_amount
  46. .checked_sub(
  47. quote_amount
  48. .checked_add(change_amount)
  49. .ok_or(Error::AmountOverflow)?,
  50. )
  51. .ok_or(Error::AmountOverflow)?;
  52. Ok(Self {
  53. state,
  54. preimage,
  55. change: change_proofs,
  56. amount: quote_amount,
  57. fee_paid,
  58. })
  59. }
  60. /// Total amount melted
  61. pub fn total_amount(&self) -> Amount {
  62. self.amount
  63. .checked_add(self.fee_paid)
  64. .expect("We check when calc fee paid")
  65. }
  66. }
  67. /// Prooinfo
  68. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  69. pub struct ProofInfo {
  70. /// Proof
  71. pub proof: Proof,
  72. /// y
  73. pub y: PublicKey,
  74. /// Mint Url
  75. pub mint_url: MintUrl,
  76. /// Proof State
  77. pub state: State,
  78. /// Proof Spending Conditions
  79. pub spending_condition: Option<SpendingConditions>,
  80. /// Unit
  81. pub unit: CurrencyUnit,
  82. }
  83. impl ProofInfo {
  84. /// Create new [`ProofInfo`]
  85. pub fn new(
  86. proof: Proof,
  87. mint_url: MintUrl,
  88. state: State,
  89. unit: CurrencyUnit,
  90. ) -> Result<Self, Error> {
  91. let y = proof.y()?;
  92. let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
  93. Ok(Self {
  94. proof,
  95. y,
  96. mint_url,
  97. state,
  98. spending_condition,
  99. unit,
  100. })
  101. }
  102. /// Check if [`Proof`] matches conditions
  103. 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. if let Some(unit) = unit {
  116. if unit.ne(&self.unit) {
  117. return false;
  118. }
  119. }
  120. if let Some(state) = state {
  121. if !state.contains(&self.state) {
  122. return false;
  123. }
  124. }
  125. if let Some(spending_conditions) = spending_conditions {
  126. match &self.spending_condition {
  127. None => {
  128. if !spending_conditions.is_empty() {
  129. return false;
  130. }
  131. }
  132. Some(s) => {
  133. if !spending_conditions.contains(s) {
  134. return false;
  135. }
  136. }
  137. }
  138. }
  139. true
  140. }
  141. }
  142. /// Key used in hashmap of ln backends to identify what unit and payment method
  143. /// it is for
  144. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  145. pub struct PaymentProcessorKey {
  146. /// Unit of Payment backend
  147. pub unit: CurrencyUnit,
  148. /// Method of payment backend
  149. pub method: PaymentMethod,
  150. }
  151. impl PaymentProcessorKey {
  152. /// Create new [`PaymentProcessorKey`]
  153. pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
  154. Self { unit, method }
  155. }
  156. }
  157. /// Seconds quotes are valid
  158. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
  159. pub struct QuoteTTL {
  160. /// Seconds mint quote is valid
  161. pub mint_ttl: u64,
  162. /// Seconds melt quote is valid
  163. pub melt_ttl: u64,
  164. }
  165. impl QuoteTTL {
  166. /// Create new [`QuoteTTL`]
  167. pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
  168. Self { mint_ttl, melt_ttl }
  169. }
  170. }
  171. impl Default for QuoteTTL {
  172. fn default() -> Self {
  173. Self {
  174. mint_ttl: 60 * 60, // 1 hour
  175. melt_ttl: 60, // 1 minute
  176. }
  177. }
  178. }
  179. #[cfg(test)]
  180. mod tests {
  181. use std::str::FromStr;
  182. use cashu::SecretKey;
  183. use super::{Melted, ProofInfo};
  184. use crate::mint_url::MintUrl;
  185. use crate::nuts::{CurrencyUnit, Id, Proof, PublicKey, SpendingConditions, State};
  186. use crate::secret::Secret;
  187. use crate::Amount;
  188. #[test]
  189. fn test_melted() {
  190. let keyset_id = Id::from_str("00deadbeef123456").unwrap();
  191. let proof = Proof::new(
  192. Amount::from(64),
  193. keyset_id,
  194. Secret::generate(),
  195. PublicKey::from_hex(
  196. "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  197. )
  198. .unwrap(),
  199. );
  200. let melted = Melted::from_proofs(
  201. super::MeltQuoteState::Paid,
  202. Some("preimage".to_string()),
  203. Amount::from(64),
  204. vec![proof.clone()],
  205. None,
  206. )
  207. .unwrap();
  208. assert_eq!(melted.amount, Amount::from(64));
  209. assert_eq!(melted.fee_paid, Amount::ZERO);
  210. assert_eq!(melted.total_amount(), Amount::from(64));
  211. }
  212. #[test]
  213. fn test_melted_with_change() {
  214. let keyset_id = Id::from_str("00deadbeef123456").unwrap();
  215. let proof = Proof::new(
  216. Amount::from(64),
  217. keyset_id,
  218. Secret::generate(),
  219. PublicKey::from_hex(
  220. "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  221. )
  222. .unwrap(),
  223. );
  224. let change_proof = Proof::new(
  225. Amount::from(32),
  226. keyset_id,
  227. Secret::generate(),
  228. PublicKey::from_hex(
  229. "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  230. )
  231. .unwrap(),
  232. );
  233. let melted = Melted::from_proofs(
  234. super::MeltQuoteState::Paid,
  235. Some("preimage".to_string()),
  236. Amount::from(31),
  237. vec![proof.clone()],
  238. Some(vec![change_proof.clone()]),
  239. )
  240. .unwrap();
  241. assert_eq!(melted.amount, Amount::from(31));
  242. assert_eq!(melted.fee_paid, Amount::from(1));
  243. assert_eq!(melted.total_amount(), Amount::from(32));
  244. }
  245. #[test]
  246. fn test_matches_conditions() {
  247. let keyset_id = Id::from_str("00deadbeef123456").unwrap();
  248. let proof = Proof::new(
  249. Amount::from(64),
  250. keyset_id,
  251. Secret::new("test_secret"),
  252. PublicKey::from_hex(
  253. "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  254. )
  255. .unwrap(),
  256. );
  257. let mint_url = MintUrl::from_str("https://example.com").unwrap();
  258. let proof_info =
  259. ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
  260. // Test matching mint_url
  261. assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
  262. assert!(!proof_info.matches_conditions(
  263. &Some(MintUrl::from_str("https://different.com").unwrap()),
  264. &None,
  265. &None,
  266. &None
  267. ));
  268. // Test matching unit
  269. assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
  270. assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
  271. // Test matching state
  272. assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
  273. assert!(proof_info.matches_conditions(
  274. &None,
  275. &None,
  276. &Some(vec![State::Unspent, State::Spent]),
  277. &None
  278. ));
  279. assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
  280. // Test with no conditions (should match)
  281. assert!(proof_info.matches_conditions(&None, &None, &None, &None));
  282. // Test with multiple conditions
  283. assert!(proof_info.matches_conditions(
  284. &Some(mint_url),
  285. &Some(CurrencyUnit::Sat),
  286. &Some(vec![State::Unspent]),
  287. &None
  288. ));
  289. }
  290. #[test]
  291. fn test_matches_conditions_with_spending_conditions() {
  292. // This test would need to be expanded with actual SpendingConditions
  293. // implementation, but we can test the basic case where no spending
  294. // conditions are present
  295. let keyset_id = Id::from_str("00deadbeef123456").unwrap();
  296. let proof = Proof::new(
  297. Amount::from(64),
  298. keyset_id,
  299. Secret::new("test_secret"),
  300. PublicKey::from_hex(
  301. "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  302. )
  303. .unwrap(),
  304. );
  305. let mint_url = MintUrl::from_str("https://example.com").unwrap();
  306. let proof_info =
  307. ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
  308. // Test with empty spending conditions (should match when proof has none)
  309. assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
  310. // Test with non-empty spending conditions (should not match when proof has none)
  311. let dummy_condition = SpendingConditions::P2PKConditions {
  312. data: SecretKey::generate().public_key(),
  313. conditions: None,
  314. };
  315. assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
  316. }
  317. }
  318. /// Mint Fee Reserve
  319. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
  320. pub struct FeeReserve {
  321. /// Absolute expected min fee
  322. pub min_fee_reserve: Amount,
  323. /// Percentage expected fee
  324. pub percent_fee_reserve: f32,
  325. }