common.rs 10 KB

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