mod.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. //! Wallet Types
  2. pub mod traits;
  3. use std::collections::HashMap;
  4. use std::fmt;
  5. use std::str::FromStr;
  6. use bitcoin::hashes::{sha256, Hash, HashEngine};
  7. use cashu::util::hex;
  8. use cashu::{nut00, PaymentMethod, Proofs, PublicKey};
  9. use serde::{Deserialize, Serialize};
  10. use crate::mint_url::MintUrl;
  11. use crate::nuts::{CurrencyUnit, MeltQuoteState, MintQuoteState, SecretKey};
  12. use crate::{Amount, Error};
  13. /// Wallet Key
  14. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
  15. pub struct WalletKey {
  16. /// Mint Url
  17. pub mint_url: MintUrl,
  18. /// Currency Unit
  19. pub unit: CurrencyUnit,
  20. }
  21. impl fmt::Display for WalletKey {
  22. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  23. write!(f, "mint_url: {}, unit: {}", self.mint_url, self.unit,)
  24. }
  25. }
  26. impl WalletKey {
  27. /// Create new [`WalletKey`]
  28. pub fn new(mint_url: MintUrl, unit: CurrencyUnit) -> Self {
  29. Self { mint_url, unit }
  30. }
  31. }
  32. /// Mint Quote Info
  33. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  34. pub struct MintQuote {
  35. /// Quote id
  36. pub id: String,
  37. /// Mint Url
  38. pub mint_url: MintUrl,
  39. /// Payment method
  40. pub payment_method: PaymentMethod,
  41. /// Amount of quote
  42. pub amount: Option<Amount>,
  43. /// Unit of quote
  44. pub unit: CurrencyUnit,
  45. /// Quote payment request e.g. bolt11
  46. pub request: String,
  47. /// Quote state
  48. pub state: MintQuoteState,
  49. /// Expiration time of quote
  50. pub expiry: u64,
  51. /// Secretkey for signing mint quotes [NUT-20]
  52. pub secret_key: Option<SecretKey>,
  53. /// Amount minted
  54. #[serde(default)]
  55. pub amount_issued: Amount,
  56. /// Amount paid to the mint for the quote
  57. #[serde(default)]
  58. pub amount_paid: Amount,
  59. }
  60. /// Melt Quote Info
  61. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  62. pub struct MeltQuote {
  63. /// Quote id
  64. pub id: String,
  65. /// Quote unit
  66. pub unit: CurrencyUnit,
  67. /// Quote amount
  68. pub amount: Amount,
  69. /// Quote Payment request e.g. bolt11
  70. pub request: String,
  71. /// Quote fee reserve
  72. pub fee_reserve: Amount,
  73. /// Quote state
  74. pub state: MeltQuoteState,
  75. /// Expiration time of quote
  76. pub expiry: u64,
  77. /// Payment preimage
  78. pub payment_preimage: Option<String>,
  79. /// Payment method
  80. pub payment_method: PaymentMethod,
  81. }
  82. impl MintQuote {
  83. /// Create a new MintQuote
  84. #[allow(clippy::too_many_arguments)]
  85. pub fn new(
  86. id: String,
  87. mint_url: MintUrl,
  88. payment_method: PaymentMethod,
  89. amount: Option<Amount>,
  90. unit: CurrencyUnit,
  91. request: String,
  92. expiry: u64,
  93. secret_key: Option<SecretKey>,
  94. ) -> Self {
  95. Self {
  96. id,
  97. mint_url,
  98. payment_method,
  99. amount,
  100. unit,
  101. request,
  102. state: MintQuoteState::Unpaid,
  103. expiry,
  104. secret_key,
  105. amount_issued: Amount::ZERO,
  106. amount_paid: Amount::ZERO,
  107. }
  108. }
  109. /// Calculate the total amount including any fees
  110. pub fn total_amount(&self) -> Amount {
  111. self.amount_paid
  112. }
  113. /// Check if the quote has expired
  114. pub fn is_expired(&self, current_time: u64) -> bool {
  115. current_time > self.expiry
  116. }
  117. /// Amount that can be minted
  118. pub fn amount_mintable(&self) -> Amount {
  119. if self.amount_issued > self.amount_paid {
  120. return Amount::ZERO;
  121. }
  122. let difference = self.amount_paid - self.amount_issued;
  123. if difference == Amount::ZERO && self.state != MintQuoteState::Issued {
  124. if let Some(amount) = self.amount {
  125. return amount;
  126. }
  127. }
  128. difference
  129. }
  130. }
  131. /// Send Kind
  132. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
  133. pub enum SendKind {
  134. #[default]
  135. /// Allow online swap before send if wallet does not have exact amount
  136. OnlineExact,
  137. /// Prefer offline send if difference is less then tolerance
  138. OnlineTolerance(Amount),
  139. /// Wallet cannot do an online swap and selected proof must be exactly send amount
  140. OfflineExact,
  141. /// Wallet must remain offline but can over pay if below tolerance
  142. OfflineTolerance(Amount),
  143. }
  144. impl SendKind {
  145. /// Check if send kind is online
  146. pub fn is_online(&self) -> bool {
  147. matches!(self, Self::OnlineExact | Self::OnlineTolerance(_))
  148. }
  149. /// Check if send kind is offline
  150. pub fn is_offline(&self) -> bool {
  151. matches!(self, Self::OfflineExact | Self::OfflineTolerance(_))
  152. }
  153. /// Check if send kind is exact
  154. pub fn is_exact(&self) -> bool {
  155. matches!(self, Self::OnlineExact | Self::OfflineExact)
  156. }
  157. /// Check if send kind has tolerance
  158. pub fn has_tolerance(&self) -> bool {
  159. matches!(self, Self::OnlineTolerance(_) | Self::OfflineTolerance(_))
  160. }
  161. }
  162. /// Wallet Transaction
  163. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
  164. pub struct Transaction {
  165. /// Mint Url
  166. pub mint_url: MintUrl,
  167. /// Transaction direction
  168. pub direction: TransactionDirection,
  169. /// Amount
  170. pub amount: Amount,
  171. /// Fee
  172. pub fee: Amount,
  173. /// Currency Unit
  174. pub unit: CurrencyUnit,
  175. /// Proof Ys
  176. pub ys: Vec<PublicKey>,
  177. /// Unix timestamp
  178. pub timestamp: u64,
  179. /// Memo
  180. pub memo: Option<String>,
  181. /// User-defined metadata
  182. pub metadata: HashMap<String, String>,
  183. /// Quote ID if this is a mint or melt transaction
  184. pub quote_id: Option<String>,
  185. /// Payment request (e.g., BOLT11 invoice, BOLT12 offer)
  186. pub payment_request: Option<String>,
  187. /// Payment proof (e.g., preimage for Lightning melt transactions)
  188. pub payment_proof: Option<String>,
  189. /// Payment method (e.g., Bolt11, Bolt12) for mint/melt transactions
  190. #[serde(default)]
  191. pub payment_method: Option<PaymentMethod>,
  192. }
  193. impl Transaction {
  194. /// Transaction ID
  195. pub fn id(&self) -> TransactionId {
  196. TransactionId::new(self.ys.clone())
  197. }
  198. /// Check if transaction matches conditions
  199. pub fn matches_conditions(
  200. &self,
  201. mint_url: &Option<MintUrl>,
  202. direction: &Option<TransactionDirection>,
  203. unit: &Option<CurrencyUnit>,
  204. ) -> bool {
  205. if let Some(mint_url) = mint_url {
  206. if &self.mint_url != mint_url {
  207. return false;
  208. }
  209. }
  210. if let Some(direction) = direction {
  211. if &self.direction != direction {
  212. return false;
  213. }
  214. }
  215. if let Some(unit) = unit {
  216. if &self.unit != unit {
  217. return false;
  218. }
  219. }
  220. true
  221. }
  222. }
  223. impl PartialOrd for Transaction {
  224. fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
  225. Some(self.cmp(other))
  226. }
  227. }
  228. impl Ord for Transaction {
  229. fn cmp(&self, other: &Self) -> std::cmp::Ordering {
  230. self.timestamp
  231. .cmp(&other.timestamp)
  232. .reverse()
  233. .then_with(|| self.id().cmp(&other.id()))
  234. }
  235. }
  236. /// Transaction Direction
  237. #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
  238. pub enum TransactionDirection {
  239. /// Incoming transaction (i.e., receive or mint)
  240. Incoming,
  241. /// Outgoing transaction (i.e., send or melt)
  242. Outgoing,
  243. }
  244. impl std::fmt::Display for TransactionDirection {
  245. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  246. match self {
  247. TransactionDirection::Incoming => write!(f, "Incoming"),
  248. TransactionDirection::Outgoing => write!(f, "Outgoing"),
  249. }
  250. }
  251. }
  252. impl FromStr for TransactionDirection {
  253. type Err = Error;
  254. fn from_str(value: &str) -> Result<Self, Self::Err> {
  255. match value {
  256. "Incoming" => Ok(Self::Incoming),
  257. "Outgoing" => Ok(Self::Outgoing),
  258. _ => Err(Error::InvalidTransactionDirection),
  259. }
  260. }
  261. }
  262. /// Transaction ID
  263. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
  264. #[serde(transparent)]
  265. pub struct TransactionId([u8; 32]);
  266. impl TransactionId {
  267. /// Create new [`TransactionId`]
  268. pub fn new(ys: Vec<PublicKey>) -> Self {
  269. let mut ys = ys;
  270. ys.sort();
  271. let mut hasher = sha256::Hash::engine();
  272. for y in ys {
  273. hasher.input(&y.to_bytes());
  274. }
  275. let hash = sha256::Hash::from_engine(hasher);
  276. Self(hash.to_byte_array())
  277. }
  278. /// From proofs
  279. pub fn from_proofs(proofs: Proofs) -> Result<Self, nut00::Error> {
  280. let ys = proofs
  281. .iter()
  282. .map(|proof| proof.y())
  283. .collect::<Result<Vec<PublicKey>, nut00::Error>>()?;
  284. Ok(Self::new(ys))
  285. }
  286. /// From bytes
  287. pub fn from_bytes(bytes: [u8; 32]) -> Self {
  288. Self(bytes)
  289. }
  290. /// From hex string
  291. pub fn from_hex(value: &str) -> Result<Self, Error> {
  292. let bytes = hex::decode(value)?;
  293. if bytes.len() != 32 {
  294. return Err(Error::InvalidTransactionId);
  295. }
  296. let mut array = [0u8; 32];
  297. array.copy_from_slice(&bytes);
  298. Ok(Self(array))
  299. }
  300. /// From slice
  301. pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
  302. if slice.len() != 32 {
  303. return Err(Error::InvalidTransactionId);
  304. }
  305. let mut array = [0u8; 32];
  306. array.copy_from_slice(slice);
  307. Ok(Self(array))
  308. }
  309. /// Get inner value
  310. pub fn as_bytes(&self) -> &[u8; 32] {
  311. &self.0
  312. }
  313. /// Get inner value as slice
  314. pub fn as_slice(&self) -> &[u8] {
  315. &self.0
  316. }
  317. }
  318. impl std::fmt::Display for TransactionId {
  319. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  320. write!(f, "{}", hex::encode(self.0))
  321. }
  322. }
  323. impl FromStr for TransactionId {
  324. type Err = Error;
  325. fn from_str(value: &str) -> Result<Self, Self::Err> {
  326. Self::from_hex(value)
  327. }
  328. }
  329. impl TryFrom<Proofs> for TransactionId {
  330. type Error = nut00::Error;
  331. fn try_from(proofs: Proofs) -> Result<Self, Self::Error> {
  332. Self::from_proofs(proofs)
  333. }
  334. }
  335. #[cfg(test)]
  336. mod tests {
  337. use super::*;
  338. #[test]
  339. fn test_transaction_id_from_hex() {
  340. let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1c";
  341. let transaction_id = TransactionId::from_hex(hex_str).unwrap();
  342. assert_eq!(transaction_id.to_string(), hex_str);
  343. }
  344. #[test]
  345. fn test_transaction_id_from_hex_empty_string() {
  346. let hex_str = "";
  347. let res = TransactionId::from_hex(hex_str);
  348. assert!(matches!(res, Err(Error::InvalidTransactionId)));
  349. }
  350. #[test]
  351. fn test_transaction_id_from_hex_longer_string() {
  352. let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1ca1b2";
  353. let res = TransactionId::from_hex(hex_str);
  354. assert!(matches!(res, Err(Error::InvalidTransactionId)));
  355. }
  356. }