1use std::collections::HashMap;
4use std::fmt;
5use std::str::FromStr;
6
7use async_trait::async_trait;
8use bitcoin::bip32::DerivationPath;
9use bitcoin::hashes::{sha256, Hash, HashEngine};
10use cashu::amount::{FeeAndAmounts, KeysetFeeAndAmounts, SplitTarget};
11use cashu::nuts::nut07::ProofState;
12use cashu::nuts::nut18::PaymentRequest;
13use cashu::nuts::{AuthProof, Keys};
14use cashu::util::hex;
15use cashu::{nut00, PaymentMethod, Proof, Proofs, PublicKey};
16use serde::{Deserialize, Serialize};
17use uuid::Uuid;
18
19use crate::mint_quote::quote_state_from_amounts;
20use crate::mint_url::MintUrl;
21use crate::nuts::{
22 CurrencyUnit, Id, MeltQuoteState, MintQuoteState, SecretKey, SpendingConditions, State,
23};
24use crate::{Amount, Error};
25
26pub mod saga;
27
28pub use saga::{
29 IssueSagaState, MeltOperationData, MeltSagaState, MintOperationData, OperationData,
30 ReceiveOperationData, ReceiveSagaState, SendOperationData, SendSagaState, SwapOperationData,
31 SwapSagaState, WalletSaga, WalletSagaState,
32};
33
34#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
36pub struct WalletKey {
37 pub mint_url: MintUrl,
39 pub unit: CurrencyUnit,
41}
42
43impl fmt::Display for WalletKey {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "mint_url: {}, unit: {}", self.mint_url, self.unit,)
46 }
47}
48
49impl WalletKey {
50 pub fn new(mint_url: MintUrl, unit: CurrencyUnit) -> Self {
52 Self { mint_url, unit }
53 }
54}
55
56#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
58pub struct ProofInfo {
59 pub proof: Proof,
61 pub y: PublicKey,
63 pub mint_url: MintUrl,
65 pub state: State,
67 pub spending_condition: Option<SpendingConditions>,
69 pub unit: CurrencyUnit,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub used_by_operation: Option<Uuid>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub created_by_operation: Option<Uuid>,
77}
78
79impl ProofInfo {
80 pub fn new(
82 proof: Proof,
83 mint_url: MintUrl,
84 state: State,
85 unit: CurrencyUnit,
86 ) -> Result<Self, Error> {
87 let y = proof.y()?;
88
89 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
90
91 Ok(Self {
92 proof,
93 y,
94 mint_url,
95 state,
96 spending_condition,
97 unit,
98 used_by_operation: None,
99 created_by_operation: None,
100 })
101 }
102
103 pub fn new_with_operations(
105 proof: Proof,
106 mint_url: MintUrl,
107 state: State,
108 unit: CurrencyUnit,
109 used_by_operation: Option<Uuid>,
110 created_by_operation: Option<Uuid>,
111 ) -> Result<Self, Error> {
112 let y = proof.y()?;
113
114 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
115
116 Ok(Self {
117 proof,
118 y,
119 mint_url,
120 state,
121 spending_condition,
122 unit,
123 used_by_operation,
124 created_by_operation,
125 })
126 }
127
128 pub fn matches_conditions(
130 &self,
131 mint_url: &Option<MintUrl>,
132 unit: &Option<CurrencyUnit>,
133 state: &Option<Vec<State>>,
134 spending_conditions: &Option<Vec<SpendingConditions>>,
135 ) -> bool {
136 if let Some(mint_url) = mint_url {
137 if mint_url.ne(&self.mint_url) {
138 return false;
139 }
140 }
141
142 if let Some(unit) = unit {
143 if unit.ne(&self.unit) {
144 return false;
145 }
146 }
147
148 if let Some(state) = state {
149 if !state.contains(&self.state) {
150 return false;
151 }
152 }
153
154 if let Some(spending_conditions) = spending_conditions {
155 match &self.spending_condition {
156 None => {
157 if !spending_conditions.is_empty() {
158 return false;
159 }
160 }
161 Some(s) => {
162 if !spending_conditions.contains(s) {
163 return false;
164 }
165 }
166 }
167 }
168
169 true
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct MintQuote {
176 pub id: String,
178 pub mint_url: MintUrl,
180 pub payment_method: PaymentMethod,
182 pub amount: Option<Amount>,
187 pub unit: CurrencyUnit,
189 pub request: String,
191 pub state: MintQuoteState,
193 pub expiry: u64,
195 pub secret_key: Option<SecretKey>,
197 #[serde(default)]
199 pub amount_issued: Amount,
200 #[serde(default)]
202 pub amount_paid: Amount,
203 pub estimated_blocks: Option<u32>,
205 #[serde(default)]
207 pub used_by_operation: Option<String>,
208 #[serde(default)]
210 pub version: u32,
211}
212
213#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
215pub struct MeltQuote {
216 pub id: String,
218 pub mint_url: Option<MintUrl>,
220 pub unit: CurrencyUnit,
222 pub amount: Amount,
224 pub request: String,
226 pub fee_reserve: Amount,
228 pub state: MeltQuoteState,
230 pub expiry: u64,
232 #[serde(alias = "payment_preimage")]
234 pub payment_proof: Option<String>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
237 pub estimated_blocks: Option<u32>,
238 #[serde(default, skip_serializing_if = "Option::is_none")]
240 pub fee_index: Option<u32>,
241 pub payment_method: PaymentMethod,
243 #[serde(default)]
245 pub used_by_operation: Option<String>,
246 #[serde(default)]
248 pub version: u32,
249}
250
251impl MintQuote {
252 #[allow(clippy::too_many_arguments)]
254 pub fn new(
255 id: String,
256 mint_url: MintUrl,
257 payment_method: PaymentMethod,
258 amount: Option<Amount>,
259 unit: CurrencyUnit,
260 request: String,
261 expiry: u64,
262 secret_key: Option<SecretKey>,
263 ) -> Self {
264 Self {
265 id,
266 mint_url,
267 payment_method,
268 amount,
269 unit,
270 request,
271 state: MintQuoteState::Unpaid,
272 expiry,
273 secret_key,
274 amount_issued: Amount::ZERO,
275 amount_paid: Amount::ZERO,
276 estimated_blocks: None,
277 used_by_operation: None,
278 version: 0,
279 }
280 }
281
282 pub fn total_amount(&self) -> Amount {
284 self.amount_paid
285 }
286
287 pub fn state_from_amounts(&self) -> MintQuoteState {
289 quote_state_from_amounts(self.amount_paid, self.amount_issued)
290 }
291
292 pub fn update_state_from_amounts(&mut self) {
294 self.state = self.state_from_amounts();
295 }
296
297 pub fn is_expired(&self, current_time: u64) -> bool {
299 current_time > self.expiry
300 }
301
302 pub fn amount_mintable(&self) -> Amount {
304 if self.payment_method == PaymentMethod::BOLT11 {
305 if self.state == MintQuoteState::Paid {
307 self.amount.unwrap_or(Amount::ZERO)
308 } else {
309 Amount::ZERO
310 }
311 } else {
312 self.amount_paid
314 .checked_sub(self.amount_issued)
315 .unwrap_or(Amount::ZERO)
316 }
317 }
318}
319
320#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
322pub struct Restored {
323 pub spent: Amount,
325 pub unspent: Amount,
327 pub pending: Amount,
329}
330
331#[derive(Debug, Clone, Default)]
333pub struct SendOptions {
334 pub memo: Option<SendMemo>,
336 pub conditions: Option<SpendingConditions>,
338 pub amount_split_target: SplitTarget,
340 pub send_kind: SendKind,
342 pub include_fee: bool,
344 pub max_proofs: Option<usize>,
346 pub metadata: HashMap<String, String>,
348 pub use_p2bk: bool,
350 pub p2pk_signing_keys: Vec<SecretKey>,
352 pub p2pk_locked_proof_send_mode: P2PKLockedProofSendMode,
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
358pub enum P2PKLockedProofSendMode {
359 #[default]
361 Swap,
362 SignAndSend,
364}
365
366#[derive(Debug, Clone)]
368pub struct SendMemo {
369 pub memo: String,
371 pub include_memo: bool,
373}
374
375impl SendMemo {
376 pub fn for_token(memo: &str) -> Self {
378 Self {
379 memo: memo.to_string(),
380 include_memo: true,
381 }
382 }
383}
384
385#[derive(Debug, Clone, Default)]
387pub struct ReceiveOptions {
388 pub amount_split_target: SplitTarget,
390 pub p2pk_signing_keys: Vec<SecretKey>,
392 pub preimages: Vec<String>,
394 pub metadata: HashMap<String, String>,
396}
397
398#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
400pub enum SendKind {
401 #[default]
402 OnlineExact,
404 OnlineTolerance(Amount),
406 OfflineExact,
408 OfflineTolerance(Amount),
410}
411
412impl SendKind {
413 pub fn is_online(&self) -> bool {
415 matches!(self, Self::OnlineExact | Self::OnlineTolerance(_))
416 }
417
418 pub fn is_offline(&self) -> bool {
420 matches!(self, Self::OfflineExact | Self::OfflineTolerance(_))
421 }
422
423 pub fn is_exact(&self) -> bool {
425 matches!(self, Self::OnlineExact | Self::OfflineExact)
426 }
427
428 pub fn has_tolerance(&self) -> bool {
430 matches!(self, Self::OnlineTolerance(_) | Self::OfflineTolerance(_))
431 }
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
436pub struct Transaction {
437 pub mint_url: MintUrl,
439 pub direction: TransactionDirection,
441 pub amount: Amount,
443 pub fee: Amount,
445 pub unit: CurrencyUnit,
447 pub ys: Vec<PublicKey>,
449 pub timestamp: u64,
451 pub memo: Option<String>,
453 pub metadata: HashMap<String, String>,
455 pub quote_id: Option<String>,
457 pub payment_request: Option<String>,
459 #[serde(alias = "payment_preimage")]
461 pub payment_proof: Option<String>,
462 #[serde(default)]
464 pub payment_method: Option<PaymentMethod>,
465 #[serde(default)]
467 pub saga_id: Option<Uuid>,
468}
469
470impl Transaction {
471 pub fn id(&self) -> TransactionId {
473 TransactionId::new(self.ys.clone())
474 }
475
476 pub fn matches_conditions(
478 &self,
479 mint_url: &Option<MintUrl>,
480 direction: &Option<TransactionDirection>,
481 unit: &Option<CurrencyUnit>,
482 ) -> bool {
483 if let Some(mint_url) = mint_url {
484 if &self.mint_url != mint_url {
485 return false;
486 }
487 }
488 if let Some(direction) = direction {
489 if &self.direction != direction {
490 return false;
491 }
492 }
493 if let Some(unit) = unit {
494 if &self.unit != unit {
495 return false;
496 }
497 }
498 true
499 }
500}
501
502impl PartialOrd for Transaction {
503 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
504 Some(self.cmp(other))
505 }
506}
507
508impl Ord for Transaction {
509 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
510 self.timestamp
511 .cmp(&other.timestamp)
512 .reverse()
513 .then_with(|| self.id().cmp(&other.id()))
514 }
515}
516
517#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
519pub enum TransactionDirection {
520 Incoming,
522 Outgoing,
524}
525
526impl std::fmt::Display for TransactionDirection {
527 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528 match self {
529 TransactionDirection::Incoming => write!(f, "Incoming"),
530 TransactionDirection::Outgoing => write!(f, "Outgoing"),
531 }
532 }
533}
534
535impl FromStr for TransactionDirection {
536 type Err = Error;
537
538 fn from_str(value: &str) -> Result<Self, Self::Err> {
539 match value {
540 "Incoming" => Ok(Self::Incoming),
541 "Outgoing" => Ok(Self::Outgoing),
542 _ => Err(Error::InvalidTransactionDirection),
543 }
544 }
545}
546
547#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
549#[serde(transparent)]
550pub struct TransactionId([u8; 32]);
551
552impl TransactionId {
553 pub fn new(ys: Vec<PublicKey>) -> Self {
555 let mut ys = ys;
556 ys.sort();
557 let mut hasher = sha256::Hash::engine();
558 for y in ys {
559 hasher.input(&y.to_bytes());
560 }
561 let hash = sha256::Hash::from_engine(hasher);
562 Self(hash.to_byte_array())
563 }
564
565 pub fn from_proofs(proofs: Proofs) -> Result<Self, nut00::Error> {
567 let ys = proofs
568 .iter()
569 .map(|proof| proof.y())
570 .collect::<Result<Vec<PublicKey>, nut00::Error>>()?;
571 Ok(Self::new(ys))
572 }
573
574 pub fn from_bytes(bytes: [u8; 32]) -> Self {
576 Self(bytes)
577 }
578
579 pub fn from_hex(value: &str) -> Result<Self, Error> {
581 let bytes = hex::decode(value)?;
582 if bytes.len() != 32 {
583 return Err(Error::InvalidTransactionId);
584 }
585 let mut array = [0u8; 32];
586 array.copy_from_slice(&bytes);
587 Ok(Self(array))
588 }
589
590 pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
592 if slice.len() != 32 {
593 return Err(Error::InvalidTransactionId);
594 }
595 let mut array = [0u8; 32];
596 array.copy_from_slice(slice);
597 Ok(Self(array))
598 }
599
600 pub fn as_bytes(&self) -> &[u8; 32] {
602 &self.0
603 }
604
605 pub fn as_slice(&self) -> &[u8] {
607 &self.0
608 }
609}
610
611impl std::fmt::Display for TransactionId {
612 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
613 write!(f, "{}", hex::encode(self.0))
614 }
615}
616
617impl FromStr for TransactionId {
618 type Err = Error;
619
620 fn from_str(value: &str) -> Result<Self, Self::Err> {
621 Self::from_hex(value)
622 }
623}
624
625impl TryFrom<Proofs> for TransactionId {
626 type Error = nut00::Error;
627
628 fn try_from(proofs: Proofs) -> Result<Self, Self::Error> {
629 Self::from_proofs(proofs)
630 }
631}
632
633#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
635#[serde(rename_all = "snake_case")]
636pub enum OperationKind {
637 Send,
639 Receive,
641 Swap,
643 Mint,
645 Melt,
647}
648
649impl fmt::Display for OperationKind {
650 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651 match self {
652 OperationKind::Send => write!(f, "send"),
653 OperationKind::Receive => write!(f, "receive"),
654 OperationKind::Swap => write!(f, "swap"),
655 OperationKind::Mint => write!(f, "mint"),
656 OperationKind::Melt => write!(f, "melt"),
657 }
658 }
659}
660
661impl FromStr for OperationKind {
662 type Err = Error;
663
664 fn from_str(s: &str) -> Result<Self, Self::Err> {
665 match s {
666 "send" => Ok(OperationKind::Send),
667 "receive" => Ok(OperationKind::Receive),
668 "swap" => Ok(OperationKind::Swap),
669 "mint" => Ok(OperationKind::Mint),
670 "melt" => Ok(OperationKind::Melt),
671 _ => Err(Error::InvalidOperationKind),
672 }
673 }
674}
675
676#[derive(Debug, Clone, Copy, PartialEq, Eq)]
678pub enum KeysetFilter {
679 Active,
681 All,
683}
684
685#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
694#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
695pub trait Wallet: Send + Sync {
696 type Error: std::error::Error + Send + Sync + 'static;
698 type Amount: Clone + Send + Sync;
700 type MintUrl: Clone + Send + Sync;
702 type CurrencyUnit: Clone + Send + Sync;
704 type MintInfo: Clone + Send + Sync;
706 type KeySetInfo: Clone + Send + Sync;
708 type MintQuote: Clone + Send + Sync;
710 type MeltQuote: Clone + Send + Sync;
712 type PaymentMethod: Clone + Send + Sync;
714 type MeltOptions: Clone + Send + Sync;
716 type OperationId: Clone + Send + Sync;
718 type PreparedSend<'a>: Send + Sync
720 where
721 Self: 'a;
722 type PreparedMelt<'a>: Send + Sync
724 where
725 Self: 'a;
726 type Subscription: Send + Sync;
728 type SubscribeParams: Clone + Send + Sync;
730 type RecoveryReport: Clone + Send + Sync;
732
733 fn mint_url(&self) -> Self::MintUrl;
735
736 fn unit(&self) -> Self::CurrencyUnit;
738
739 async fn total_balance(&self) -> Result<Self::Amount, Self::Error>;
741
742 async fn total_pending_balance(&self) -> Result<Self::Amount, Self::Error>;
744
745 async fn total_reserved_balance(&self) -> Result<Self::Amount, Self::Error>;
747
748 async fn fetch_mint_info(&self) -> Result<Option<Self::MintInfo>, Self::Error>;
750
751 async fn load_mint_info(&self) -> Result<Self::MintInfo, Self::Error>;
753
754 async fn refresh_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error>;
756
757 async fn get_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error>;
759
760 async fn load_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Self::Error>;
762
763 async fn get_mint_keysets(
765 &self,
766 filter: KeysetFilter,
767 ) -> Result<Vec<Self::KeySetInfo>, Self::Error>;
768
769 async fn load_mint_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error> {
771 self.get_mint_keysets(KeysetFilter::Active).await
772 }
773
774 async fn fetch_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error>;
776
777 async fn get_keyset_fees_and_amounts(&self) -> Result<KeysetFeeAndAmounts, Self::Error>;
779
780 async fn get_keyset_count_fee(
782 &self,
783 keyset_id: &Id,
784 count: u64,
785 ) -> Result<Self::Amount, Self::Error>;
786
787 async fn get_keyset_fees_and_amounts_by_id(
789 &self,
790 keyset_id: Id,
791 ) -> Result<FeeAndAmounts, Self::Error>;
792
793 async fn mint_quote(
795 &self,
796 method: Self::PaymentMethod,
797 amount: Option<Self::Amount>,
798 description: Option<String>,
799 extra: Option<String>,
800 ) -> Result<Self::MintQuote, Self::Error>;
801
802 async fn melt_quote(
804 &self,
805 method: Self::PaymentMethod,
806 request: String,
807 options: Option<Self::MeltOptions>,
808 extra: Option<String>,
809 ) -> Result<Self::MeltQuote, Self::Error>;
810
811 async fn list_transactions(
813 &self,
814 direction: Option<TransactionDirection>,
815 ) -> Result<Vec<Transaction>, Self::Error>;
816
817 async fn get_transaction(&self, id: TransactionId) -> Result<Option<Transaction>, Self::Error>;
819
820 async fn get_proofs_for_transaction(&self, id: TransactionId) -> Result<Proofs, Self::Error>;
822
823 async fn revert_transaction(&self, id: TransactionId) -> Result<(), Self::Error>;
825
826 async fn check_all_pending_proofs(&self) -> Result<Self::Amount, Self::Error>;
828
829 async fn recover_incomplete_sagas(&self) -> Result<Self::RecoveryReport, Self::Error>;
831
832 async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<ProofState>, Self::Error>;
834
835 async fn get_keyset_fees_by_id(&self, keyset_id: Id) -> Result<u64, Self::Error>;
837
838 async fn calculate_fee(
840 &self,
841 proof_count: u64,
842 keyset_id: Id,
843 ) -> Result<Self::Amount, Self::Error>;
844
845 async fn receive(
847 &self,
848 encoded_token: &str,
849 options: ReceiveOptions,
850 ) -> Result<Self::Amount, Self::Error>;
851
852 async fn receive_proofs(
854 &self,
855 proofs: Proofs,
856 options: ReceiveOptions,
857 memo: Option<String>,
858 token: Option<String>,
859 ) -> Result<Self::Amount, Self::Error>;
860
861 async fn prepare_send(
863 &self,
864 amount: Self::Amount,
865 options: SendOptions,
866 ) -> Result<Self::PreparedSend<'_>, Self::Error>;
867
868 async fn get_pending_sends(&self) -> Result<Vec<Self::OperationId>, Self::Error>;
870
871 async fn revoke_send(
873 &self,
874 operation_id: Self::OperationId,
875 ) -> Result<Self::Amount, Self::Error>;
876
877 async fn check_send_status(&self, operation_id: Self::OperationId)
879 -> Result<bool, Self::Error>;
880
881 async fn mint(
883 &self,
884 quote_id: &str,
885 split_target: SplitTarget,
886 spending_conditions: Option<SpendingConditions>,
887 ) -> Result<Proofs, Self::Error>;
888
889 async fn check_mint_quote_status(&self, quote_id: &str)
891 -> Result<Self::MintQuote, Self::Error>;
892
893 async fn fetch_mint_quote(
895 &self,
896 quote_id: &str,
897 payment_method: Option<Self::PaymentMethod>,
898 ) -> Result<Self::MintQuote, Self::Error>;
899
900 async fn prepare_melt(
902 &self,
903 quote_id: &str,
904 metadata: HashMap<String, String>,
905 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
906
907 async fn prepare_melt_proofs(
909 &self,
910 quote_id: &str,
911 proofs: Proofs,
912 metadata: HashMap<String, String>,
913 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
914
915 async fn prepare_melt_token(
921 &self,
922 quote_id: &str,
923 encoded_token: &str,
924 metadata: HashMap<String, String>,
925 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
926
927 async fn swap(
929 &self,
930 amount: Option<Self::Amount>,
931 split_target: SplitTarget,
932 input_proofs: Proofs,
933 spending_conditions: Option<SpendingConditions>,
934 include_fees: bool,
935 use_p2bk: bool,
936 ) -> Result<Option<Proofs>, Self::Error>;
937
938 async fn set_cat(&self, cat: String) -> Result<(), Self::Error>;
940
941 async fn set_refresh_token(&self, refresh_token: String) -> Result<(), Self::Error>;
943
944 async fn refresh_access_token(&self) -> Result<(), Self::Error>;
946
947 async fn mint_blind_auth(&self, amount: Self::Amount) -> Result<Proofs, Self::Error>;
949
950 async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, Self::Error>;
952
953 async fn restore(&self) -> Result<Restored, Self::Error>;
955
956 async fn verify_token_dleq(&self, token_str: &str) -> Result<(), Self::Error>;
958
959 async fn pay_request(
961 &self,
962 request: PaymentRequest,
963 custom_amount: Option<Self::Amount>,
964 ) -> Result<(), Self::Error>;
965
966 async fn subscribe_mint_quote_state(
971 &self,
972 quote_ids: Vec<String>,
973 method: Self::PaymentMethod,
974 ) -> Result<Self::Subscription, Self::Error>;
975
976 fn set_metadata_cache_ttl(&self, ttl_secs: Option<u64>);
982
983 async fn subscribe(
985 &self,
986 params: Self::SubscribeParams,
987 ) -> Result<Self::Subscription, Self::Error>;
988
989 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
991 async fn melt_bip353_quote(
992 &self,
993 bip353_address: &str,
994 amount_msat: Self::Amount,
995 network: bitcoin::Network,
996 ) -> Result<Self::MeltQuote, Self::Error>;
997
998 #[cfg(not(target_arch = "wasm32"))]
1000 async fn melt_lightning_address_quote(
1001 &self,
1002 lightning_address: &str,
1003 amount_msat: Self::Amount,
1004 ) -> Result<Self::MeltQuote, Self::Error>;
1005
1006 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
1012 async fn melt_human_readable_quote(
1013 &self,
1014 address: &str,
1015 amount_msat: Self::Amount,
1016 network: bitcoin::Network,
1017 ) -> Result<Self::MeltQuote, Self::Error>;
1018
1019 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
1021 async fn melt_human_readable(
1022 &self,
1023 address: &str,
1024 amount_msat: Self::Amount,
1025 network: bitcoin::Network,
1026 ) -> Result<Self::MeltQuote, Self::Error> {
1027 self.melt_human_readable_quote(address, amount_msat, network)
1028 .await
1029 }
1030
1031 async fn check_mint_quote(&self, quote_id: &str) -> Result<Self::MintQuote, Self::Error> {
1033 self.check_mint_quote_status(quote_id).await
1034 }
1035
1036 async fn mint_unified(
1038 &self,
1039 quote_id: &str,
1040 split_target: SplitTarget,
1041 spending_conditions: Option<SpendingConditions>,
1042 ) -> Result<Proofs, Self::Error> {
1043 self.mint(quote_id, split_target, spending_conditions).await
1044 }
1045
1046 async fn get_proofs_by_states(&self, states: Vec<State>) -> Result<Proofs, Self::Error>;
1052
1053 async fn generate_public_key(&self) -> Result<PublicKey, Self::Error>;
1056
1057 async fn get_public_key(
1059 &self,
1060 pubkey: &PublicKey,
1061 ) -> Result<Option<P2PKSigningKey>, Self::Error>;
1062
1063 async fn get_public_keys(&self) -> Result<Vec<P2PKSigningKey>, Self::Error>;
1065
1066 async fn get_latest_public_key(&self) -> Result<Option<P2PKSigningKey>, Self::Error>;
1068
1069 async fn get_signing_key(&self, pubkey: &PublicKey) -> Result<Option<SecretKey>, Self::Error>;
1071}
1072
1073#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1075pub struct P2PKSigningKey {
1076 pub pubkey: PublicKey,
1078 pub derivation_path: DerivationPath,
1080 pub derivation_index: u32,
1082 pub created_time: u64,
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088 use super::*;
1089 use crate::nuts::Id;
1090 use crate::secret::Secret;
1091
1092 #[test]
1093 fn test_transaction_id_from_hex() {
1094 let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1c";
1095 let transaction_id = TransactionId::from_hex(hex_str).unwrap();
1096 assert_eq!(transaction_id.to_string(), hex_str);
1097 }
1098
1099 #[test]
1100 fn test_transaction_id_from_hex_empty_string() {
1101 let hex_str = "";
1102 let res = TransactionId::from_hex(hex_str);
1103 assert!(matches!(res, Err(Error::InvalidTransactionId)));
1104 }
1105
1106 #[test]
1107 fn test_transaction_id_from_hex_longer_string() {
1108 let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1ca1b2";
1109 let res = TransactionId::from_hex(hex_str);
1110 assert!(matches!(res, Err(Error::InvalidTransactionId)));
1111 }
1112
1113 #[test]
1114 fn test_matches_conditions() {
1115 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
1116 let proof = Proof::new(
1117 Amount::from(64),
1118 keyset_id,
1119 Secret::new("test_secret"),
1120 PublicKey::from_hex(
1121 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1122 )
1123 .unwrap(),
1124 );
1125
1126 let mint_url = MintUrl::from_str("https://example.com").unwrap();
1127 let proof_info =
1128 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
1129
1130 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
1132 assert!(!proof_info.matches_conditions(
1133 &Some(MintUrl::from_str("https://different.com").unwrap()),
1134 &None,
1135 &None,
1136 &None
1137 ));
1138
1139 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
1141 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
1142
1143 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
1145 assert!(proof_info.matches_conditions(
1146 &None,
1147 &None,
1148 &Some(vec![State::Unspent, State::Spent]),
1149 &None
1150 ));
1151 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
1152
1153 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
1155
1156 assert!(proof_info.matches_conditions(
1158 &Some(mint_url),
1159 &Some(CurrencyUnit::Sat),
1160 &Some(vec![State::Unspent]),
1161 &None
1162 ));
1163 }
1164
1165 #[test]
1166 fn test_matches_conditions_with_spending_conditions() {
1167 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
1172 let proof = Proof::new(
1173 Amount::from(64),
1174 keyset_id,
1175 Secret::new("test_secret"),
1176 PublicKey::from_hex(
1177 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1178 )
1179 .unwrap(),
1180 );
1181
1182 let mint_url = MintUrl::from_str("https://example.com").unwrap();
1183 let proof_info =
1184 ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
1185
1186 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
1188
1189 let dummy_condition = SpendingConditions::P2PKConditions {
1191 data: SecretKey::generate().public_key(),
1192 conditions: None,
1193 };
1194 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
1195 }
1196}