mint.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. //! Mint types
  2. use std::fmt;
  3. use std::str::FromStr;
  4. use bitcoin::bip32::DerivationPath;
  5. use cashu::quote_id::QuoteId;
  6. use cashu::util::unix_time;
  7. use cashu::{
  8. Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
  9. MintQuoteBolt12Response, PaymentMethod,
  10. };
  11. use lightning::offers::offer::Offer;
  12. use serde::{Deserialize, Serialize};
  13. use tracing::instrument;
  14. use uuid::Uuid;
  15. use crate::nuts::{MeltQuoteState, MintQuoteState};
  16. use crate::payment::PaymentIdentifier;
  17. use crate::{Amount, CurrencyUnit, Error, Id, KeySetInfo, PublicKey};
  18. /// Operation kind for saga persistence
  19. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
  20. #[serde(rename_all = "lowercase")]
  21. pub enum OperationKind {
  22. /// Swap operation
  23. Swap,
  24. /// Mint operation
  25. Mint,
  26. /// Melt operation
  27. Melt,
  28. }
  29. impl fmt::Display for OperationKind {
  30. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  31. match self {
  32. OperationKind::Swap => write!(f, "swap"),
  33. OperationKind::Mint => write!(f, "mint"),
  34. OperationKind::Melt => write!(f, "melt"),
  35. }
  36. }
  37. }
  38. impl FromStr for OperationKind {
  39. type Err = Error;
  40. fn from_str(value: &str) -> Result<Self, Self::Err> {
  41. let value = value.to_lowercase();
  42. match value.as_str() {
  43. "swap" => Ok(OperationKind::Swap),
  44. "mint" => Ok(OperationKind::Mint),
  45. "melt" => Ok(OperationKind::Melt),
  46. _ => Err(Error::Custom(format!("Invalid operation kind: {}", value))),
  47. }
  48. }
  49. }
  50. /// States specific to swap saga
  51. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  52. #[serde(rename_all = "snake_case")]
  53. pub enum SwapSagaState {
  54. /// Swap setup complete (proofs added, blinded messages added)
  55. SetupComplete,
  56. /// Outputs signed (signatures generated but not persisted)
  57. Signed,
  58. }
  59. impl fmt::Display for SwapSagaState {
  60. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  61. match self {
  62. SwapSagaState::SetupComplete => write!(f, "setup_complete"),
  63. SwapSagaState::Signed => write!(f, "signed"),
  64. }
  65. }
  66. }
  67. impl FromStr for SwapSagaState {
  68. type Err = Error;
  69. fn from_str(value: &str) -> Result<Self, Self::Err> {
  70. let value = value.to_lowercase();
  71. match value.as_str() {
  72. "setup_complete" => Ok(SwapSagaState::SetupComplete),
  73. "signed" => Ok(SwapSagaState::Signed),
  74. _ => Err(Error::Custom(format!("Invalid swap saga state: {}", value))),
  75. }
  76. }
  77. }
  78. /// Saga state for different operation types
  79. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  80. #[serde(tag = "type", rename_all = "snake_case")]
  81. pub enum SagaStateEnum {
  82. /// Swap saga states
  83. Swap(SwapSagaState),
  84. // Future: Mint saga states
  85. // Mint(MintSagaState),
  86. // Future: Melt saga states
  87. // Melt(MeltSagaState),
  88. }
  89. impl SagaStateEnum {
  90. /// Create from string given operation kind
  91. pub fn new(operation_kind: OperationKind, s: &str) -> Result<Self, Error> {
  92. match operation_kind {
  93. OperationKind::Swap => Ok(SagaStateEnum::Swap(SwapSagaState::from_str(s)?)),
  94. OperationKind::Mint => Err(Error::Custom("Mint saga not implemented yet".to_string())),
  95. OperationKind::Melt => Err(Error::Custom("Melt saga not implemented yet".to_string())),
  96. }
  97. }
  98. /// Get string representation of the state
  99. pub fn state(&self) -> &str {
  100. match self {
  101. SagaStateEnum::Swap(state) => match state {
  102. SwapSagaState::SetupComplete => "setup_complete",
  103. SwapSagaState::Signed => "signed",
  104. },
  105. }
  106. }
  107. }
  108. /// Persisted saga for recovery
  109. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  110. pub struct Saga {
  111. /// Operation ID (correlation key)
  112. pub operation_id: Uuid,
  113. /// Operation kind (swap, mint, melt)
  114. pub operation_kind: OperationKind,
  115. /// Current saga state (operation-specific)
  116. pub state: SagaStateEnum,
  117. /// Blinded secrets (B values) from output blinded messages
  118. pub blinded_secrets: Vec<PublicKey>,
  119. /// Y values (public keys) from input proofs
  120. pub input_ys: Vec<PublicKey>,
  121. /// Unix timestamp when saga was created
  122. pub created_at: u64,
  123. /// Unix timestamp when saga was last updated
  124. pub updated_at: u64,
  125. }
  126. impl Saga {
  127. /// Create new swap saga
  128. pub fn new_swap(
  129. operation_id: Uuid,
  130. state: SwapSagaState,
  131. blinded_secrets: Vec<PublicKey>,
  132. input_ys: Vec<PublicKey>,
  133. ) -> Self {
  134. let now = unix_time();
  135. Self {
  136. operation_id,
  137. operation_kind: OperationKind::Swap,
  138. state: SagaStateEnum::Swap(state),
  139. blinded_secrets,
  140. input_ys,
  141. created_at: now,
  142. updated_at: now,
  143. }
  144. }
  145. /// Update swap saga state
  146. pub fn update_swap_state(&mut self, new_state: SwapSagaState) {
  147. self.state = SagaStateEnum::Swap(new_state);
  148. self.updated_at = unix_time();
  149. }
  150. }
  151. /// Operation
  152. pub enum Operation {
  153. /// Mint
  154. Mint(Uuid),
  155. /// Melt
  156. Melt(Uuid),
  157. /// Swap
  158. Swap(Uuid),
  159. }
  160. impl Operation {
  161. /// Mint
  162. pub fn new_mint() -> Self {
  163. Self::Mint(Uuid::new_v4())
  164. }
  165. /// Melt
  166. pub fn new_melt() -> Self {
  167. Self::Melt(Uuid::new_v4())
  168. }
  169. /// Swap
  170. pub fn new_swap() -> Self {
  171. Self::Swap(Uuid::new_v4())
  172. }
  173. /// Operation id
  174. pub fn id(&self) -> &Uuid {
  175. match self {
  176. Operation::Mint(id) => id,
  177. Operation::Melt(id) => id,
  178. Operation::Swap(id) => id,
  179. }
  180. }
  181. /// Operation kind
  182. pub fn kind(&self) -> &str {
  183. match self {
  184. Operation::Mint(_) => "mint",
  185. Operation::Melt(_) => "melt",
  186. Operation::Swap(_) => "swap",
  187. }
  188. }
  189. /// From kind and i
  190. pub fn from_kind_and_id(kind: &str, id: &str) -> Result<Self, Error> {
  191. let uuid = Uuid::parse_str(id)?;
  192. match kind {
  193. "mint" => Ok(Self::Mint(uuid)),
  194. "melt" => Ok(Self::Melt(uuid)),
  195. "swap" => Ok(Self::Swap(uuid)),
  196. _ => Err(Error::Custom(format!("Invalid operation kind: {}", kind))),
  197. }
  198. }
  199. }
  200. /// Mint Quote Info
  201. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  202. pub struct MintQuote {
  203. /// Quote id
  204. pub id: QuoteId,
  205. /// Amount of quote
  206. pub amount: Option<Amount>,
  207. /// Unit of quote
  208. pub unit: CurrencyUnit,
  209. /// Quote payment request e.g. bolt11
  210. pub request: String,
  211. /// Expiration time of quote
  212. pub expiry: u64,
  213. /// Value used by ln backend to look up state of request
  214. pub request_lookup_id: PaymentIdentifier,
  215. /// Pubkey
  216. pub pubkey: Option<PublicKey>,
  217. /// Unix time quote was created
  218. #[serde(default)]
  219. pub created_time: u64,
  220. /// Amount paid
  221. #[serde(default)]
  222. amount_paid: Amount,
  223. /// Amount issued
  224. #[serde(default)]
  225. amount_issued: Amount,
  226. /// Payment of payment(s) that filled quote
  227. #[serde(default)]
  228. pub payments: Vec<IncomingPayment>,
  229. /// Payment Method
  230. #[serde(default)]
  231. pub payment_method: PaymentMethod,
  232. /// Payment of payment(s) that filled quote
  233. #[serde(default)]
  234. pub issuance: Vec<Issuance>,
  235. }
  236. impl MintQuote {
  237. /// Create new [`MintQuote`]
  238. #[allow(clippy::too_many_arguments)]
  239. pub fn new(
  240. id: Option<QuoteId>,
  241. request: String,
  242. unit: CurrencyUnit,
  243. amount: Option<Amount>,
  244. expiry: u64,
  245. request_lookup_id: PaymentIdentifier,
  246. pubkey: Option<PublicKey>,
  247. amount_paid: Amount,
  248. amount_issued: Amount,
  249. payment_method: PaymentMethod,
  250. created_time: u64,
  251. payments: Vec<IncomingPayment>,
  252. issuance: Vec<Issuance>,
  253. ) -> Self {
  254. let id = id.unwrap_or_else(QuoteId::new_uuid);
  255. Self {
  256. id,
  257. amount,
  258. unit,
  259. request,
  260. expiry,
  261. request_lookup_id,
  262. pubkey,
  263. created_time,
  264. amount_paid,
  265. amount_issued,
  266. payment_method,
  267. payments,
  268. issuance,
  269. }
  270. }
  271. /// Increment the amount paid on the mint quote by a given amount
  272. #[instrument(skip(self))]
  273. pub fn increment_amount_paid(
  274. &mut self,
  275. additional_amount: Amount,
  276. ) -> Result<Amount, crate::Error> {
  277. self.amount_paid = self
  278. .amount_paid
  279. .checked_add(additional_amount)
  280. .ok_or(crate::Error::AmountOverflow)?;
  281. Ok(self.amount_paid)
  282. }
  283. /// Amount paid
  284. #[instrument(skip(self))]
  285. pub fn amount_paid(&self) -> Amount {
  286. self.amount_paid
  287. }
  288. /// Increment the amount issued on the mint quote by a given amount
  289. #[instrument(skip(self))]
  290. pub fn increment_amount_issued(
  291. &mut self,
  292. additional_amount: Amount,
  293. ) -> Result<Amount, crate::Error> {
  294. self.amount_issued = self
  295. .amount_issued
  296. .checked_add(additional_amount)
  297. .ok_or(crate::Error::AmountOverflow)?;
  298. Ok(self.amount_issued)
  299. }
  300. /// Amount issued
  301. #[instrument(skip(self))]
  302. pub fn amount_issued(&self) -> Amount {
  303. self.amount_issued
  304. }
  305. /// Get state of mint quote
  306. #[instrument(skip(self))]
  307. pub fn state(&self) -> MintQuoteState {
  308. self.compute_quote_state()
  309. }
  310. /// Existing payment ids of a mint quote
  311. pub fn payment_ids(&self) -> Vec<&String> {
  312. self.payments.iter().map(|a| &a.payment_id).collect()
  313. }
  314. /// Amount mintable
  315. /// Returns the amount that is still available for minting.
  316. ///
  317. /// The value is computed as the difference between the total amount that
  318. /// has been paid for this issuance (`self.amount_paid`) and the amount
  319. /// that has already been issued (`self.amount_issued`). In other words,
  320. pub fn amount_mintable(&self) -> Amount {
  321. self.amount_paid - self.amount_issued
  322. }
  323. /// Add a payment ID to the list of payment IDs
  324. ///
  325. /// Returns an error if the payment ID is already in the list
  326. #[instrument(skip(self))]
  327. pub fn add_payment(
  328. &mut self,
  329. amount: Amount,
  330. payment_id: String,
  331. time: u64,
  332. ) -> Result<(), crate::Error> {
  333. let payment_ids = self.payment_ids();
  334. if payment_ids.contains(&&payment_id) {
  335. return Err(crate::Error::DuplicatePaymentId);
  336. }
  337. let payment = IncomingPayment::new(amount, payment_id, time);
  338. self.payments.push(payment);
  339. Ok(())
  340. }
  341. /// Compute quote state
  342. #[instrument(skip(self))]
  343. fn compute_quote_state(&self) -> MintQuoteState {
  344. if self.amount_paid == Amount::ZERO && self.amount_issued == Amount::ZERO {
  345. return MintQuoteState::Unpaid;
  346. }
  347. match self.amount_paid.cmp(&self.amount_issued) {
  348. std::cmp::Ordering::Less => {
  349. // self.amount_paid is less than other (amount issued)
  350. // Handle case where paid amount is insufficient
  351. tracing::error!("We should not have issued more then has been paid");
  352. MintQuoteState::Issued
  353. }
  354. std::cmp::Ordering::Equal => {
  355. // We do this extra check for backwards compatibility for quotes where amount paid/issed was not tracked
  356. // self.amount_paid equals other (amount issued)
  357. // Handle case where paid amount exactly matches
  358. MintQuoteState::Issued
  359. }
  360. std::cmp::Ordering::Greater => {
  361. // self.amount_paid is greater than other (amount issued)
  362. // Handle case where paid amount exceeds required amount
  363. MintQuoteState::Paid
  364. }
  365. }
  366. }
  367. }
  368. /// Mint Payments
  369. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  370. pub struct IncomingPayment {
  371. /// Amount
  372. pub amount: Amount,
  373. /// Pyament unix time
  374. pub time: u64,
  375. /// Payment id
  376. pub payment_id: String,
  377. }
  378. impl IncomingPayment {
  379. /// New [`IncomingPayment`]
  380. pub fn new(amount: Amount, payment_id: String, time: u64) -> Self {
  381. Self {
  382. payment_id,
  383. time,
  384. amount,
  385. }
  386. }
  387. }
  388. /// Informattion about issued quote
  389. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  390. pub struct Issuance {
  391. /// Amount
  392. pub amount: Amount,
  393. /// Time
  394. pub time: u64,
  395. }
  396. impl Issuance {
  397. /// Create new [`Issuance`]
  398. pub fn new(amount: Amount, time: u64) -> Self {
  399. Self { amount, time }
  400. }
  401. }
  402. /// Melt Quote Info
  403. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  404. pub struct MeltQuote {
  405. /// Quote id
  406. pub id: QuoteId,
  407. /// Quote unit
  408. pub unit: CurrencyUnit,
  409. /// Quote amount
  410. pub amount: Amount,
  411. /// Quote Payment request e.g. bolt11
  412. pub request: MeltPaymentRequest,
  413. /// Quote fee reserve
  414. pub fee_reserve: Amount,
  415. /// Quote state
  416. pub state: MeltQuoteState,
  417. /// Expiration time of quote
  418. pub expiry: u64,
  419. /// Payment preimage
  420. pub payment_preimage: Option<String>,
  421. /// Value used by ln backend to look up state of request
  422. pub request_lookup_id: Option<PaymentIdentifier>,
  423. /// Payment options
  424. ///
  425. /// Used for amountless invoices and MPP payments
  426. pub options: Option<MeltOptions>,
  427. /// Unix time quote was created
  428. #[serde(default)]
  429. pub created_time: u64,
  430. /// Unix time quote was paid
  431. pub paid_time: Option<u64>,
  432. /// Payment method
  433. #[serde(default)]
  434. pub payment_method: PaymentMethod,
  435. }
  436. impl MeltQuote {
  437. /// Create new [`MeltQuote`]
  438. #[allow(clippy::too_many_arguments)]
  439. pub fn new(
  440. request: MeltPaymentRequest,
  441. unit: CurrencyUnit,
  442. amount: Amount,
  443. fee_reserve: Amount,
  444. expiry: u64,
  445. request_lookup_id: Option<PaymentIdentifier>,
  446. options: Option<MeltOptions>,
  447. payment_method: PaymentMethod,
  448. ) -> Self {
  449. let id = Uuid::new_v4();
  450. Self {
  451. id: QuoteId::UUID(id),
  452. amount,
  453. unit,
  454. request,
  455. fee_reserve,
  456. state: MeltQuoteState::Unpaid,
  457. expiry,
  458. payment_preimage: None,
  459. request_lookup_id,
  460. options,
  461. created_time: unix_time(),
  462. paid_time: None,
  463. payment_method,
  464. }
  465. }
  466. }
  467. /// Mint Keyset Info
  468. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  469. pub struct MintKeySetInfo {
  470. /// Keyset [`Id`]
  471. pub id: Id,
  472. /// Keyset [`CurrencyUnit`]
  473. pub unit: CurrencyUnit,
  474. /// Keyset active or inactive
  475. /// Mint will only issue new signatures on active keysets
  476. pub active: bool,
  477. /// Starting unix time Keyset is valid from
  478. pub valid_from: u64,
  479. /// [`DerivationPath`] keyset
  480. pub derivation_path: DerivationPath,
  481. /// DerivationPath index of Keyset
  482. pub derivation_path_index: Option<u32>,
  483. /// Max order of keyset
  484. pub max_order: u8,
  485. /// Supported amounts
  486. pub amounts: Vec<u64>,
  487. /// Input Fee ppk
  488. #[serde(default = "default_fee")]
  489. pub input_fee_ppk: u64,
  490. /// Final expiry
  491. pub final_expiry: Option<u64>,
  492. }
  493. /// Default fee
  494. pub fn default_fee() -> u64 {
  495. 0
  496. }
  497. impl From<MintKeySetInfo> for KeySetInfo {
  498. fn from(keyset_info: MintKeySetInfo) -> Self {
  499. Self {
  500. id: keyset_info.id,
  501. unit: keyset_info.unit,
  502. active: keyset_info.active,
  503. input_fee_ppk: keyset_info.input_fee_ppk,
  504. final_expiry: keyset_info.final_expiry,
  505. }
  506. }
  507. }
  508. impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
  509. fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
  510. MintQuoteBolt11Response {
  511. quote: mint_quote.id.clone(),
  512. state: mint_quote.state(),
  513. request: mint_quote.request,
  514. expiry: Some(mint_quote.expiry),
  515. pubkey: mint_quote.pubkey,
  516. amount: mint_quote.amount,
  517. unit: Some(mint_quote.unit.clone()),
  518. }
  519. }
  520. }
  521. impl From<MintQuote> for MintQuoteBolt11Response<String> {
  522. fn from(quote: MintQuote) -> Self {
  523. let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
  524. quote.into()
  525. }
  526. }
  527. impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
  528. type Error = crate::Error;
  529. fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
  530. Ok(MintQuoteBolt12Response {
  531. quote: mint_quote.id.clone(),
  532. request: mint_quote.request,
  533. expiry: Some(mint_quote.expiry),
  534. amount_paid: mint_quote.amount_paid,
  535. amount_issued: mint_quote.amount_issued,
  536. pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
  537. amount: mint_quote.amount,
  538. unit: mint_quote.unit,
  539. })
  540. }
  541. }
  542. impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
  543. type Error = crate::Error;
  544. fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
  545. let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
  546. Ok(quote.into())
  547. }
  548. }
  549. impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  550. fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  551. MeltQuoteBolt11Response {
  552. quote: melt_quote.id.clone(),
  553. payment_preimage: None,
  554. change: None,
  555. state: melt_quote.state,
  556. paid: Some(melt_quote.state == MeltQuoteState::Paid),
  557. expiry: melt_quote.expiry,
  558. amount: melt_quote.amount,
  559. fee_reserve: melt_quote.fee_reserve,
  560. request: None,
  561. unit: Some(melt_quote.unit.clone()),
  562. }
  563. }
  564. }
  565. impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  566. fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  567. let paid = melt_quote.state == MeltQuoteState::Paid;
  568. MeltQuoteBolt11Response {
  569. quote: melt_quote.id.clone(),
  570. amount: melt_quote.amount,
  571. fee_reserve: melt_quote.fee_reserve,
  572. paid: Some(paid),
  573. state: melt_quote.state,
  574. expiry: melt_quote.expiry,
  575. payment_preimage: melt_quote.payment_preimage,
  576. change: None,
  577. request: Some(melt_quote.request.to_string()),
  578. unit: Some(melt_quote.unit.clone()),
  579. }
  580. }
  581. }
  582. /// Payment request
  583. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  584. pub enum MeltPaymentRequest {
  585. /// Bolt11 Payment
  586. Bolt11 {
  587. /// Bolt11 invoice
  588. bolt11: Bolt11Invoice,
  589. },
  590. /// Bolt12 Payment
  591. Bolt12 {
  592. /// Offer
  593. #[serde(with = "offer_serde")]
  594. offer: Box<Offer>,
  595. },
  596. }
  597. impl std::fmt::Display for MeltPaymentRequest {
  598. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  599. match self {
  600. MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
  601. MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
  602. }
  603. }
  604. }
  605. mod offer_serde {
  606. use std::str::FromStr;
  607. use serde::{self, Deserialize, Deserializer, Serializer};
  608. use super::Offer;
  609. pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
  610. where
  611. S: Serializer,
  612. {
  613. let s = offer.to_string();
  614. serializer.serialize_str(&s)
  615. }
  616. pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
  617. where
  618. D: Deserializer<'de>,
  619. {
  620. let s = String::deserialize(deserializer)?;
  621. Ok(Box::new(Offer::from_str(&s).map_err(|_| {
  622. serde::de::Error::custom("Invalid Bolt12 Offer")
  623. })?))
  624. }
  625. }