common.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. //! Types
  2. use serde::{Deserialize, Serialize};
  3. use crate::error::Error;
  4. use crate::nuts::nut00::ProofsMethods;
  5. use crate::nuts::{CurrencyUnit, MeltQuoteState, PaymentMethod, Proofs};
  6. // Re-export ProofInfo from wallet module for backwards compatibility
  7. #[cfg(feature = "wallet")]
  8. pub use crate::wallet::ProofInfo;
  9. use crate::Amount;
  10. /// Result of a finalized melt operation
  11. #[derive(Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
  12. pub struct FinalizedMelt {
  13. /// Quote ID
  14. quote_id: String,
  15. /// State of quote
  16. state: MeltQuoteState,
  17. /// Payment proof (e.g., Lightning preimage)
  18. payment_proof: Option<String>,
  19. /// Melt change
  20. change: Option<Proofs>,
  21. /// Melt amount
  22. amount: Amount,
  23. /// Fee paid
  24. fee_paid: Amount,
  25. }
  26. impl FinalizedMelt {
  27. /// Create new [`FinalizedMelt`]
  28. pub fn new(
  29. quote_id: String,
  30. state: MeltQuoteState,
  31. payment_proof: Option<String>,
  32. amount: Amount,
  33. fee_paid: Amount,
  34. change: Option<Proofs>,
  35. ) -> Self {
  36. Self {
  37. quote_id,
  38. state,
  39. payment_proof,
  40. change,
  41. amount,
  42. fee_paid,
  43. }
  44. }
  45. /// Create new [`FinalizedMelt`] calculating fee from proofs
  46. pub fn from_proofs(
  47. quote_id: String,
  48. state: MeltQuoteState,
  49. payment_proof: Option<String>,
  50. quote_amount: Amount,
  51. proofs: Proofs,
  52. change_proofs: Option<Proofs>,
  53. ) -> Result<Self, Error> {
  54. let proofs_amount = proofs.total_amount()?;
  55. let change_amount = match &change_proofs {
  56. Some(change_proofs) => change_proofs.total_amount()?,
  57. None => Amount::ZERO,
  58. };
  59. tracing::info!(
  60. "Proofs amount: {} Amount: {} Change: {}",
  61. proofs_amount,
  62. quote_amount,
  63. change_amount
  64. );
  65. let fee_paid = proofs_amount
  66. .checked_sub(
  67. quote_amount
  68. .checked_add(change_amount)
  69. .ok_or(Error::AmountOverflow)?,
  70. )
  71. .ok_or(Error::AmountOverflow)?;
  72. Ok(Self {
  73. quote_id,
  74. state,
  75. payment_proof,
  76. change: change_proofs,
  77. amount: quote_amount,
  78. fee_paid,
  79. })
  80. }
  81. /// Get the quote ID
  82. #[inline]
  83. pub fn quote_id(&self) -> &str {
  84. &self.quote_id
  85. }
  86. /// Get the state of the melt
  87. #[inline]
  88. pub fn state(&self) -> MeltQuoteState {
  89. self.state
  90. }
  91. /// Get the payment proof (e.g., Lightning preimage)
  92. #[inline]
  93. pub fn payment_proof(&self) -> Option<&str> {
  94. self.payment_proof.as_deref()
  95. }
  96. /// Get the change proofs
  97. #[inline]
  98. pub fn change(&self) -> Option<&Proofs> {
  99. self.change.as_ref()
  100. }
  101. /// Consume self and return the change proofs
  102. #[inline]
  103. pub fn into_change(self) -> Option<Proofs> {
  104. self.change
  105. }
  106. /// Get the amount melted
  107. #[inline]
  108. pub fn amount(&self) -> Amount {
  109. self.amount
  110. }
  111. /// Get the fee paid
  112. #[inline]
  113. pub fn fee_paid(&self) -> Amount {
  114. self.fee_paid
  115. }
  116. /// Total amount melted (amount + fee)
  117. ///
  118. /// # Panics
  119. ///
  120. /// Panics if the sum of `amount` and `fee_paid` overflows. This should not
  121. /// happen as the fee is validated when calculated.
  122. #[inline]
  123. pub fn total_amount(&self) -> Amount {
  124. self.amount
  125. .checked_add(self.fee_paid)
  126. .expect("We check when calc fee paid")
  127. }
  128. }
  129. impl std::fmt::Debug for FinalizedMelt {
  130. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  131. f.debug_struct("FinalizedMelt")
  132. .field("quote_id", &self.quote_id)
  133. .field("state", &self.state)
  134. .field("amount", &self.amount)
  135. .field("fee_paid", &self.fee_paid)
  136. .finish()
  137. }
  138. }
  139. /// Key used in hashmap of ln backends to identify what unit and payment method
  140. /// it is for
  141. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  142. pub struct PaymentProcessorKey {
  143. /// Unit of Payment backend
  144. pub unit: CurrencyUnit,
  145. /// Method of payment backend
  146. pub method: PaymentMethod,
  147. }
  148. impl PaymentProcessorKey {
  149. /// Create new [`PaymentProcessorKey`]
  150. pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
  151. Self { unit, method }
  152. }
  153. }
  154. /// Seconds quotes are valid
  155. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
  156. pub struct QuoteTTL {
  157. /// Seconds mint quote is valid
  158. pub mint_ttl: u64,
  159. /// Seconds melt quote is valid
  160. pub melt_ttl: u64,
  161. }
  162. impl QuoteTTL {
  163. /// Create new [`QuoteTTL`]
  164. pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
  165. Self { mint_ttl, melt_ttl }
  166. }
  167. }
  168. impl Default for QuoteTTL {
  169. fn default() -> Self {
  170. Self {
  171. mint_ttl: 60 * 60, // 1 hour
  172. melt_ttl: 60, // 1 minute
  173. }
  174. }
  175. }
  176. /// Mint Fee Reserve
  177. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
  178. pub struct FeeReserve {
  179. /// Absolute expected min fee
  180. pub min_fee_reserve: Amount,
  181. /// Percentage expected fee
  182. pub percent_fee_reserve: f32,
  183. }
  184. /// CDK Version
  185. #[derive(Debug, Clone, Hash, PartialEq, Eq)]
  186. pub struct IssuerVersion {
  187. /// Implementation name (e.g., "cdk", "nutshell")
  188. pub implementation: String,
  189. /// Major version
  190. pub major: u16,
  191. /// Minor version
  192. pub minor: u16,
  193. /// Patch version
  194. pub patch: u16,
  195. }
  196. impl IssuerVersion {
  197. /// Create new [`IssuerVersion`]
  198. pub fn new(implementation: String, major: u16, minor: u16, patch: u16) -> Self {
  199. Self {
  200. implementation,
  201. major,
  202. minor,
  203. patch,
  204. }
  205. }
  206. }
  207. impl std::fmt::Display for IssuerVersion {
  208. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  209. write!(
  210. f,
  211. "{}/{}.{}.{}",
  212. self.implementation, self.major, self.minor, self.patch
  213. )
  214. }
  215. }
  216. impl PartialOrd for IssuerVersion {
  217. fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
  218. if self.implementation != other.implementation {
  219. return None;
  220. }
  221. match self.major.cmp(&other.major) {
  222. std::cmp::Ordering::Equal => match self.minor.cmp(&other.minor) {
  223. std::cmp::Ordering::Equal => Some(self.patch.cmp(&other.patch)),
  224. other => Some(other),
  225. },
  226. other => Some(other),
  227. }
  228. }
  229. }
  230. impl std::str::FromStr for IssuerVersion {
  231. type Err = Error;
  232. fn from_str(s: &str) -> Result<Self, Self::Err> {
  233. let (implementation, version_str) = s
  234. .split_once('/')
  235. .ok_or(Error::Custom(format!("Invalid version string: {}", s)))?;
  236. let implementation = implementation.to_string();
  237. let parts: Vec<&str> = version_str.splitn(3, '.').collect();
  238. if parts.len() != 3 {
  239. return Err(Error::Custom(format!("Invalid version string: {}", s)));
  240. }
  241. let major = parts[0]
  242. .parse()
  243. .map_err(|_| Error::Custom(format!("Invalid major version: {}", parts[0])))?;
  244. let minor = parts[1]
  245. .parse()
  246. .map_err(|_| Error::Custom(format!("Invalid minor version: {}", parts[1])))?;
  247. // Handle patch version with optional suffixes like -rc1
  248. let patch_str = parts[2];
  249. let patch_end = patch_str
  250. .find(|c: char| !c.is_numeric())
  251. .unwrap_or(patch_str.len());
  252. let patch = patch_str[..patch_end]
  253. .parse()
  254. .map_err(|_| Error::Custom(format!("Invalid patch version: {}", parts[2])))?;
  255. Ok(Self {
  256. implementation,
  257. major,
  258. minor,
  259. patch,
  260. })
  261. }
  262. }
  263. impl Serialize for IssuerVersion {
  264. fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  265. where
  266. S: serde::Serializer,
  267. {
  268. serializer.serialize_str(&self.to_string())
  269. }
  270. }
  271. impl<'de> Deserialize<'de> for IssuerVersion {
  272. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  273. where
  274. D: serde::Deserializer<'de>,
  275. {
  276. let s = String::deserialize(deserializer)?;
  277. std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
  278. }
  279. }
  280. #[cfg(test)]
  281. mod tests {
  282. use std::str::FromStr;
  283. use super::FinalizedMelt;
  284. use crate::nuts::{Id, Proof, PublicKey};
  285. use crate::secret::Secret;
  286. use crate::Amount;
  287. #[test]
  288. fn test_finalized_melt() {
  289. let keyset_id = Id::from_str("00deadbeef123456").unwrap();
  290. let proof = Proof::new(
  291. Amount::from(64),
  292. keyset_id,
  293. Secret::generate(),
  294. PublicKey::from_hex(
  295. "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  296. )
  297. .unwrap(),
  298. );
  299. let finalized = FinalizedMelt::from_proofs(
  300. "test_quote_id".to_string(),
  301. super::MeltQuoteState::Paid,
  302. Some("preimage".to_string()),
  303. Amount::from(64),
  304. vec![proof.clone()],
  305. None,
  306. )
  307. .unwrap();
  308. assert_eq!(finalized.quote_id(), "test_quote_id");
  309. assert_eq!(finalized.amount(), Amount::from(64));
  310. assert_eq!(finalized.fee_paid(), Amount::ZERO);
  311. assert_eq!(finalized.total_amount(), Amount::from(64));
  312. }
  313. #[test]
  314. fn test_finalized_melt_with_change() {
  315. let keyset_id = Id::from_str("00deadbeef123456").unwrap();
  316. let proof = Proof::new(
  317. Amount::from(64),
  318. keyset_id,
  319. Secret::generate(),
  320. PublicKey::from_hex(
  321. "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  322. )
  323. .unwrap(),
  324. );
  325. let change_proof = Proof::new(
  326. Amount::from(32),
  327. keyset_id,
  328. Secret::generate(),
  329. PublicKey::from_hex(
  330. "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
  331. )
  332. .unwrap(),
  333. );
  334. let finalized = FinalizedMelt::from_proofs(
  335. "test_quote_id".to_string(),
  336. super::MeltQuoteState::Paid,
  337. Some("preimage".to_string()),
  338. Amount::from(31),
  339. vec![proof.clone()],
  340. Some(vec![change_proof.clone()]),
  341. )
  342. .unwrap();
  343. assert_eq!(finalized.quote_id(), "test_quote_id");
  344. assert_eq!(finalized.amount(), Amount::from(31));
  345. assert_eq!(finalized.fee_paid(), Amount::from(1));
  346. assert_eq!(finalized.total_amount(), Amount::from(32));
  347. }
  348. use super::IssuerVersion;
  349. #[test]
  350. fn test_version_parsing() {
  351. // Test explicit cdk format
  352. let v = IssuerVersion::from_str("cdk/1.2.3").unwrap();
  353. assert_eq!(v.implementation, "cdk");
  354. assert_eq!(v.major, 1);
  355. assert_eq!(v.minor, 2);
  356. assert_eq!(v.patch, 3);
  357. assert_eq!(v.to_string(), "cdk/1.2.3");
  358. // Test nutshell format
  359. let v = IssuerVersion::from_str("nutshell/0.16.0").unwrap();
  360. assert_eq!(v.implementation, "nutshell");
  361. assert_eq!(v.major, 0);
  362. assert_eq!(v.minor, 16);
  363. assert_eq!(v.patch, 0);
  364. assert_eq!(v.to_string(), "nutshell/0.16.0");
  365. }
  366. #[test]
  367. fn test_version_ordering() {
  368. let v1 = IssuerVersion::from_str("cdk/0.1.0").unwrap();
  369. let v2 = IssuerVersion::from_str("cdk/0.1.1").unwrap();
  370. let v3 = IssuerVersion::from_str("cdk/0.2.0").unwrap();
  371. let v4 = IssuerVersion::from_str("cdk/1.0.0").unwrap();
  372. assert!(v1 < v2);
  373. assert!(v2 < v3);
  374. assert!(v3 < v4);
  375. assert!(v1 < v4);
  376. // Test mixed implementations
  377. let v_nutshell = IssuerVersion::from_str("nutshell/0.1.0").unwrap();
  378. assert_eq!(v1.partial_cmp(&v_nutshell), None);
  379. assert!(!(v1 < v_nutshell));
  380. assert!(!(v1 > v_nutshell));
  381. assert!(!(v1 == v_nutshell));
  382. }
  383. #[test]
  384. fn test_version_serialization() {
  385. let v = IssuerVersion::from_str("cdk/0.14.2").unwrap();
  386. let json = serde_json::to_string(&v).unwrap();
  387. assert_eq!(json, "\"cdk/0.14.2\"");
  388. let v_deserialized: IssuerVersion = serde_json::from_str(&json).unwrap();
  389. assert_eq!(v, v_deserialized);
  390. }
  391. #[test]
  392. fn test_cdk_version_parsing_with_suffix() {
  393. let version_str = "cdk/0.15.0-rc1";
  394. let version = IssuerVersion::from_str(version_str).unwrap();
  395. assert_eq!(version.implementation, "cdk");
  396. assert_eq!(version.major, 0);
  397. assert_eq!(version.minor, 15);
  398. assert_eq!(version.patch, 0);
  399. }
  400. #[test]
  401. fn test_cdk_version_parsing_standard() {
  402. let version_str = "cdk/0.15.0";
  403. let version = IssuerVersion::from_str(version_str).unwrap();
  404. assert_eq!(version.implementation, "cdk");
  405. assert_eq!(version.major, 0);
  406. assert_eq!(version.minor, 15);
  407. assert_eq!(version.patch, 0);
  408. }
  409. #[test]
  410. fn test_cdk_version_parsing_complex_suffix() {
  411. let version_str = "cdk/0.15.0-beta.1+build123";
  412. let version = IssuerVersion::from_str(version_str).unwrap();
  413. assert_eq!(version.implementation, "cdk");
  414. assert_eq!(version.major, 0);
  415. assert_eq!(version.minor, 15);
  416. assert_eq!(version.patch, 0);
  417. }
  418. #[test]
  419. fn test_cdk_version_parsing_invalid() {
  420. // Missing prefix
  421. let version_str = "0.15.0";
  422. assert!(IssuerVersion::from_str(version_str).is_err());
  423. // Invalid version format
  424. let version_str = "cdk/0.15";
  425. assert!(IssuerVersion::from_str(version_str).is_err());
  426. let version_str = "cdk/0.15.a";
  427. assert!(IssuerVersion::from_str(version_str).is_err());
  428. }
  429. #[test]
  430. fn test_cdk_version_parsing_with_implementation() {
  431. let version_str = "nutshell/0.16.2";
  432. let version = IssuerVersion::from_str(version_str).unwrap();
  433. assert_eq!(version.implementation, "nutshell");
  434. assert_eq!(version.major, 0);
  435. assert_eq!(version.minor, 16);
  436. assert_eq!(version.patch, 2);
  437. }
  438. #[test]
  439. fn test_cdk_version_comparison_different_implementations() {
  440. let v1 = IssuerVersion::from_str("cdk/0.15.0").unwrap();
  441. let v2 = IssuerVersion::from_str("nutshell/0.15.0").unwrap();
  442. assert_eq!(v1.partial_cmp(&v2), None);
  443. }
  444. }