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_url::MintUrl;
20use crate::nuts::{
21 CurrencyUnit, Id, MeltQuoteState, MintQuoteState, SecretKey, SpendingConditions, State,
22};
23use crate::{Amount, Error};
24
25pub mod saga;
26
27pub use saga::{
28 IssueSagaState, MeltOperationData, MeltSagaState, MintOperationData, OperationData,
29 ReceiveOperationData, ReceiveSagaState, SendOperationData, SendSagaState, SwapOperationData,
30 SwapSagaState, WalletSaga, WalletSagaState,
31};
32
33#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
35pub struct WalletKey {
36 pub mint_url: MintUrl,
38 pub unit: CurrencyUnit,
40}
41
42impl fmt::Display for WalletKey {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "mint_url: {}, unit: {}", self.mint_url, self.unit,)
45 }
46}
47
48impl WalletKey {
49 pub fn new(mint_url: MintUrl, unit: CurrencyUnit) -> Self {
51 Self { mint_url, unit }
52 }
53}
54
55#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
57pub struct ProofInfo {
58 pub proof: Proof,
60 pub y: PublicKey,
62 pub mint_url: MintUrl,
64 pub state: State,
66 pub spending_condition: Option<SpendingConditions>,
68 pub unit: CurrencyUnit,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub used_by_operation: Option<Uuid>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub created_by_operation: Option<Uuid>,
76}
77
78impl ProofInfo {
79 pub fn new(
81 proof: Proof,
82 mint_url: MintUrl,
83 state: State,
84 unit: CurrencyUnit,
85 ) -> Result<Self, Error> {
86 let y = proof.y()?;
87
88 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
89
90 Ok(Self {
91 proof,
92 y,
93 mint_url,
94 state,
95 spending_condition,
96 unit,
97 used_by_operation: None,
98 created_by_operation: None,
99 })
100 }
101
102 pub fn new_with_operations(
104 proof: Proof,
105 mint_url: MintUrl,
106 state: State,
107 unit: CurrencyUnit,
108 used_by_operation: Option<Uuid>,
109 created_by_operation: Option<Uuid>,
110 ) -> Result<Self, Error> {
111 let y = proof.y()?;
112
113 let spending_condition: Option<SpendingConditions> = (&proof.secret).try_into().ok();
114
115 Ok(Self {
116 proof,
117 y,
118 mint_url,
119 state,
120 spending_condition,
121 unit,
122 used_by_operation,
123 created_by_operation,
124 })
125 }
126
127 pub fn matches_conditions(
129 &self,
130 mint_url: &Option<MintUrl>,
131 unit: &Option<CurrencyUnit>,
132 state: &Option<Vec<State>>,
133 spending_conditions: &Option<Vec<SpendingConditions>>,
134 ) -> bool {
135 if let Some(mint_url) = mint_url {
136 if mint_url.ne(&self.mint_url) {
137 return false;
138 }
139 }
140
141 if let Some(unit) = unit {
142 if unit.ne(&self.unit) {
143 return false;
144 }
145 }
146
147 if let Some(state) = state {
148 if !state.contains(&self.state) {
149 return false;
150 }
151 }
152
153 if let Some(spending_conditions) = spending_conditions {
154 match &self.spending_condition {
155 None => {
156 if !spending_conditions.is_empty() {
157 return false;
158 }
159 }
160 Some(s) => {
161 if !spending_conditions.contains(s) {
162 return false;
163 }
164 }
165 }
166 }
167
168 true
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174pub struct MintQuote {
175 pub id: String,
177 pub mint_url: MintUrl,
179 pub payment_method: PaymentMethod,
181 pub amount: Option<Amount>,
183 pub unit: CurrencyUnit,
185 pub request: String,
187 pub state: MintQuoteState,
189 pub expiry: u64,
191 pub secret_key: Option<SecretKey>,
193 #[serde(default)]
195 pub amount_issued: Amount,
196 #[serde(default)]
198 pub amount_paid: Amount,
199 #[serde(default)]
201 pub used_by_operation: Option<String>,
202 #[serde(default)]
204 pub version: u32,
205}
206
207#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
209pub struct MeltQuote {
210 pub id: String,
212 pub mint_url: Option<MintUrl>,
214 pub unit: CurrencyUnit,
216 pub amount: Amount,
218 pub request: String,
220 pub fee_reserve: Amount,
222 pub state: MeltQuoteState,
224 pub expiry: u64,
226 pub payment_preimage: Option<String>,
228 pub payment_method: PaymentMethod,
230 #[serde(default)]
232 pub used_by_operation: Option<String>,
233 #[serde(default)]
235 pub version: u32,
236}
237
238impl MintQuote {
239 #[allow(clippy::too_many_arguments)]
241 pub fn new(
242 id: String,
243 mint_url: MintUrl,
244 payment_method: PaymentMethod,
245 amount: Option<Amount>,
246 unit: CurrencyUnit,
247 request: String,
248 expiry: u64,
249 secret_key: Option<SecretKey>,
250 ) -> Self {
251 Self {
252 id,
253 mint_url,
254 payment_method,
255 amount,
256 unit,
257 request,
258 state: MintQuoteState::Unpaid,
259 expiry,
260 secret_key,
261 amount_issued: Amount::ZERO,
262 amount_paid: Amount::ZERO,
263 used_by_operation: None,
264 version: 0,
265 }
266 }
267
268 pub fn total_amount(&self) -> Amount {
270 self.amount_paid
271 }
272
273 pub fn is_expired(&self, current_time: u64) -> bool {
275 current_time > self.expiry
276 }
277
278 pub fn amount_mintable(&self) -> Amount {
280 if self.payment_method == PaymentMethod::BOLT11 {
281 if self.state == MintQuoteState::Paid {
283 self.amount.unwrap_or(Amount::ZERO)
284 } else {
285 Amount::ZERO
286 }
287 } else {
288 self.amount_paid
290 .checked_sub(self.amount_issued)
291 .unwrap_or(Amount::ZERO)
292 }
293 }
294}
295
296#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
298pub struct Restored {
299 pub spent: Amount,
301 pub unspent: Amount,
303 pub pending: Amount,
305}
306
307#[derive(Debug, Clone, Default)]
309pub struct SendOptions {
310 pub memo: Option<SendMemo>,
312 pub conditions: Option<SpendingConditions>,
314 pub amount_split_target: SplitTarget,
316 pub send_kind: SendKind,
318 pub include_fee: bool,
320 pub max_proofs: Option<usize>,
322 pub metadata: HashMap<String, String>,
324 pub use_p2bk: bool,
326}
327
328#[derive(Debug, Clone)]
330pub struct SendMemo {
331 pub memo: String,
333 pub include_memo: bool,
335}
336
337impl SendMemo {
338 pub fn for_token(memo: &str) -> Self {
340 Self {
341 memo: memo.to_string(),
342 include_memo: true,
343 }
344 }
345}
346
347#[derive(Debug, Clone, Default)]
349pub struct ReceiveOptions {
350 pub amount_split_target: SplitTarget,
352 pub p2pk_signing_keys: Vec<SecretKey>,
354 pub preimages: Vec<String>,
356 pub metadata: HashMap<String, String>,
358}
359
360#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
362pub enum SendKind {
363 #[default]
364 OnlineExact,
366 OnlineTolerance(Amount),
368 OfflineExact,
370 OfflineTolerance(Amount),
372}
373
374impl SendKind {
375 pub fn is_online(&self) -> bool {
377 matches!(self, Self::OnlineExact | Self::OnlineTolerance(_))
378 }
379
380 pub fn is_offline(&self) -> bool {
382 matches!(self, Self::OfflineExact | Self::OfflineTolerance(_))
383 }
384
385 pub fn is_exact(&self) -> bool {
387 matches!(self, Self::OnlineExact | Self::OfflineExact)
388 }
389
390 pub fn has_tolerance(&self) -> bool {
392 matches!(self, Self::OnlineTolerance(_) | Self::OfflineTolerance(_))
393 }
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
398pub struct Transaction {
399 pub mint_url: MintUrl,
401 pub direction: TransactionDirection,
403 pub amount: Amount,
405 pub fee: Amount,
407 pub unit: CurrencyUnit,
409 pub ys: Vec<PublicKey>,
411 pub timestamp: u64,
413 pub memo: Option<String>,
415 pub metadata: HashMap<String, String>,
417 pub quote_id: Option<String>,
419 pub payment_request: Option<String>,
421 pub payment_proof: Option<String>,
423 #[serde(default)]
425 pub payment_method: Option<PaymentMethod>,
426 #[serde(default)]
428 pub saga_id: Option<Uuid>,
429}
430
431impl Transaction {
432 pub fn id(&self) -> TransactionId {
434 TransactionId::new(self.ys.clone())
435 }
436
437 pub fn matches_conditions(
439 &self,
440 mint_url: &Option<MintUrl>,
441 direction: &Option<TransactionDirection>,
442 unit: &Option<CurrencyUnit>,
443 ) -> bool {
444 if let Some(mint_url) = mint_url {
445 if &self.mint_url != mint_url {
446 return false;
447 }
448 }
449 if let Some(direction) = direction {
450 if &self.direction != direction {
451 return false;
452 }
453 }
454 if let Some(unit) = unit {
455 if &self.unit != unit {
456 return false;
457 }
458 }
459 true
460 }
461}
462
463impl PartialOrd for Transaction {
464 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
465 Some(self.cmp(other))
466 }
467}
468
469impl Ord for Transaction {
470 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
471 self.timestamp
472 .cmp(&other.timestamp)
473 .reverse()
474 .then_with(|| self.id().cmp(&other.id()))
475 }
476}
477
478#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
480pub enum TransactionDirection {
481 Incoming,
483 Outgoing,
485}
486
487impl std::fmt::Display for TransactionDirection {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 match self {
490 TransactionDirection::Incoming => write!(f, "Incoming"),
491 TransactionDirection::Outgoing => write!(f, "Outgoing"),
492 }
493 }
494}
495
496impl FromStr for TransactionDirection {
497 type Err = Error;
498
499 fn from_str(value: &str) -> Result<Self, Self::Err> {
500 match value {
501 "Incoming" => Ok(Self::Incoming),
502 "Outgoing" => Ok(Self::Outgoing),
503 _ => Err(Error::InvalidTransactionDirection),
504 }
505 }
506}
507
508#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
510#[serde(transparent)]
511pub struct TransactionId([u8; 32]);
512
513impl TransactionId {
514 pub fn new(ys: Vec<PublicKey>) -> Self {
516 let mut ys = ys;
517 ys.sort();
518 let mut hasher = sha256::Hash::engine();
519 for y in ys {
520 hasher.input(&y.to_bytes());
521 }
522 let hash = sha256::Hash::from_engine(hasher);
523 Self(hash.to_byte_array())
524 }
525
526 pub fn from_proofs(proofs: Proofs) -> Result<Self, nut00::Error> {
528 let ys = proofs
529 .iter()
530 .map(|proof| proof.y())
531 .collect::<Result<Vec<PublicKey>, nut00::Error>>()?;
532 Ok(Self::new(ys))
533 }
534
535 pub fn from_bytes(bytes: [u8; 32]) -> Self {
537 Self(bytes)
538 }
539
540 pub fn from_hex(value: &str) -> Result<Self, Error> {
542 let bytes = hex::decode(value)?;
543 if bytes.len() != 32 {
544 return Err(Error::InvalidTransactionId);
545 }
546 let mut array = [0u8; 32];
547 array.copy_from_slice(&bytes);
548 Ok(Self(array))
549 }
550
551 pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
553 if slice.len() != 32 {
554 return Err(Error::InvalidTransactionId);
555 }
556 let mut array = [0u8; 32];
557 array.copy_from_slice(slice);
558 Ok(Self(array))
559 }
560
561 pub fn as_bytes(&self) -> &[u8; 32] {
563 &self.0
564 }
565
566 pub fn as_slice(&self) -> &[u8] {
568 &self.0
569 }
570}
571
572impl std::fmt::Display for TransactionId {
573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574 write!(f, "{}", hex::encode(self.0))
575 }
576}
577
578impl FromStr for TransactionId {
579 type Err = Error;
580
581 fn from_str(value: &str) -> Result<Self, Self::Err> {
582 Self::from_hex(value)
583 }
584}
585
586impl TryFrom<Proofs> for TransactionId {
587 type Error = nut00::Error;
588
589 fn try_from(proofs: Proofs) -> Result<Self, Self::Error> {
590 Self::from_proofs(proofs)
591 }
592}
593
594#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
596#[serde(rename_all = "snake_case")]
597pub enum OperationKind {
598 Send,
600 Receive,
602 Swap,
604 Mint,
606 Melt,
608}
609
610impl fmt::Display for OperationKind {
611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612 match self {
613 OperationKind::Send => write!(f, "send"),
614 OperationKind::Receive => write!(f, "receive"),
615 OperationKind::Swap => write!(f, "swap"),
616 OperationKind::Mint => write!(f, "mint"),
617 OperationKind::Melt => write!(f, "melt"),
618 }
619 }
620}
621
622impl FromStr for OperationKind {
623 type Err = Error;
624
625 fn from_str(s: &str) -> Result<Self, Self::Err> {
626 match s {
627 "send" => Ok(OperationKind::Send),
628 "receive" => Ok(OperationKind::Receive),
629 "swap" => Ok(OperationKind::Swap),
630 "mint" => Ok(OperationKind::Mint),
631 "melt" => Ok(OperationKind::Melt),
632 _ => Err(Error::InvalidOperationKind),
633 }
634 }
635}
636
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
639pub enum KeysetFilter {
640 Active,
642 All,
644}
645
646#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
655#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
656pub trait Wallet: Send + Sync {
657 type Error: std::error::Error + Send + Sync + 'static;
659 type Amount: Clone + Send + Sync;
661 type MintUrl: Clone + Send + Sync;
663 type CurrencyUnit: Clone + Send + Sync;
665 type MintInfo: Clone + Send + Sync;
667 type KeySetInfo: Clone + Send + Sync;
669 type MintQuote: Clone + Send + Sync;
671 type MeltQuote: Clone + Send + Sync;
673 type PaymentMethod: Clone + Send + Sync;
675 type MeltOptions: Clone + Send + Sync;
677 type OperationId: Clone + Send + Sync;
679 type PreparedSend<'a>: Send + Sync
681 where
682 Self: 'a;
683 type PreparedMelt<'a>: Send + Sync
685 where
686 Self: 'a;
687 type Subscription: Send + Sync;
689 type SubscribeParams: Clone + Send + Sync;
691
692 fn mint_url(&self) -> Self::MintUrl;
694
695 fn unit(&self) -> Self::CurrencyUnit;
697
698 async fn total_balance(&self) -> Result<Self::Amount, Self::Error>;
700
701 async fn total_pending_balance(&self) -> Result<Self::Amount, Self::Error>;
703
704 async fn total_reserved_balance(&self) -> Result<Self::Amount, Self::Error>;
706
707 async fn fetch_mint_info(&self) -> Result<Option<Self::MintInfo>, Self::Error>;
709
710 async fn load_mint_info(&self) -> Result<Self::MintInfo, Self::Error>;
712
713 async fn refresh_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error>;
715
716 async fn get_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error>;
718
719 async fn load_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Self::Error>;
721
722 async fn get_mint_keysets(
724 &self,
725 filter: KeysetFilter,
726 ) -> Result<Vec<Self::KeySetInfo>, Self::Error>;
727
728 async fn load_mint_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error> {
730 self.get_mint_keysets(KeysetFilter::Active).await
731 }
732
733 async fn fetch_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error>;
735
736 async fn get_keyset_fees_and_amounts(&self) -> Result<KeysetFeeAndAmounts, Self::Error>;
738
739 async fn get_keyset_count_fee(
741 &self,
742 keyset_id: &Id,
743 count: u64,
744 ) -> Result<Self::Amount, Self::Error>;
745
746 async fn get_keyset_fees_and_amounts_by_id(
748 &self,
749 keyset_id: Id,
750 ) -> Result<FeeAndAmounts, Self::Error>;
751
752 async fn mint_quote(
754 &self,
755 method: Self::PaymentMethod,
756 amount: Option<Self::Amount>,
757 description: Option<String>,
758 extra: Option<String>,
759 ) -> Result<Self::MintQuote, Self::Error>;
760
761 async fn melt_quote(
763 &self,
764 method: Self::PaymentMethod,
765 request: String,
766 options: Option<Self::MeltOptions>,
767 extra: Option<String>,
768 ) -> Result<Self::MeltQuote, Self::Error>;
769
770 async fn list_transactions(
772 &self,
773 direction: Option<TransactionDirection>,
774 ) -> Result<Vec<Transaction>, Self::Error>;
775
776 async fn get_transaction(&self, id: TransactionId) -> Result<Option<Transaction>, Self::Error>;
778
779 async fn get_proofs_for_transaction(&self, id: TransactionId) -> Result<Proofs, Self::Error>;
781
782 async fn revert_transaction(&self, id: TransactionId) -> Result<(), Self::Error>;
784
785 async fn check_all_pending_proofs(&self) -> Result<Self::Amount, Self::Error>;
787
788 async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<ProofState>, Self::Error>;
790
791 async fn get_keyset_fees_by_id(&self, keyset_id: Id) -> Result<u64, Self::Error>;
793
794 async fn calculate_fee(
796 &self,
797 proof_count: u64,
798 keyset_id: Id,
799 ) -> Result<Self::Amount, Self::Error>;
800
801 async fn receive(
803 &self,
804 encoded_token: &str,
805 options: ReceiveOptions,
806 ) -> Result<Self::Amount, Self::Error>;
807
808 async fn receive_proofs(
810 &self,
811 proofs: Proofs,
812 options: ReceiveOptions,
813 memo: Option<String>,
814 token: Option<String>,
815 ) -> Result<Self::Amount, Self::Error>;
816
817 async fn prepare_send(
819 &self,
820 amount: Self::Amount,
821 options: SendOptions,
822 ) -> Result<Self::PreparedSend<'_>, Self::Error>;
823
824 async fn get_pending_sends(&self) -> Result<Vec<Self::OperationId>, Self::Error>;
826
827 async fn revoke_send(
829 &self,
830 operation_id: Self::OperationId,
831 ) -> Result<Self::Amount, Self::Error>;
832
833 async fn check_send_status(&self, operation_id: Self::OperationId)
835 -> Result<bool, Self::Error>;
836
837 async fn mint(
839 &self,
840 quote_id: &str,
841 split_target: SplitTarget,
842 spending_conditions: Option<SpendingConditions>,
843 ) -> Result<Proofs, Self::Error>;
844
845 async fn check_mint_quote_status(&self, quote_id: &str)
847 -> Result<Self::MintQuote, Self::Error>;
848
849 async fn fetch_mint_quote(
851 &self,
852 quote_id: &str,
853 payment_method: Option<Self::PaymentMethod>,
854 ) -> Result<Self::MintQuote, Self::Error>;
855
856 async fn prepare_melt(
858 &self,
859 quote_id: &str,
860 metadata: HashMap<String, String>,
861 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
862
863 async fn prepare_melt_proofs(
865 &self,
866 quote_id: &str,
867 proofs: Proofs,
868 metadata: HashMap<String, String>,
869 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
870
871 async fn swap(
873 &self,
874 amount: Option<Self::Amount>,
875 split_target: SplitTarget,
876 input_proofs: Proofs,
877 spending_conditions: Option<SpendingConditions>,
878 include_fees: bool,
879 use_p2bk: bool,
880 ) -> Result<Option<Proofs>, Self::Error>;
881
882 async fn set_cat(&self, cat: String) -> Result<(), Self::Error>;
884
885 async fn set_refresh_token(&self, refresh_token: String) -> Result<(), Self::Error>;
887
888 async fn refresh_access_token(&self) -> Result<(), Self::Error>;
890
891 async fn mint_blind_auth(&self, amount: Self::Amount) -> Result<Proofs, Self::Error>;
893
894 async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, Self::Error>;
896
897 async fn restore(&self) -> Result<Restored, Self::Error>;
899
900 async fn verify_token_dleq(&self, token_str: &str) -> Result<(), Self::Error>;
902
903 async fn pay_request(
905 &self,
906 request: PaymentRequest,
907 custom_amount: Option<Self::Amount>,
908 ) -> Result<(), Self::Error>;
909
910 async fn subscribe_mint_quote_state(
915 &self,
916 quote_ids: Vec<String>,
917 method: Self::PaymentMethod,
918 ) -> Result<Self::Subscription, Self::Error>;
919
920 fn set_metadata_cache_ttl(&self, ttl_secs: Option<u64>);
926
927 async fn subscribe(
929 &self,
930 params: Self::SubscribeParams,
931 ) -> Result<Self::Subscription, Self::Error>;
932
933 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
935 async fn melt_bip353_quote(
936 &self,
937 bip353_address: &str,
938 amount_msat: Self::Amount,
939 network: bitcoin::Network,
940 ) -> Result<Self::MeltQuote, Self::Error>;
941
942 #[cfg(not(target_arch = "wasm32"))]
944 async fn melt_lightning_address_quote(
945 &self,
946 lightning_address: &str,
947 amount_msat: Self::Amount,
948 ) -> Result<Self::MeltQuote, Self::Error>;
949
950 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
956 async fn melt_human_readable_quote(
957 &self,
958 address: &str,
959 amount_msat: Self::Amount,
960 network: bitcoin::Network,
961 ) -> Result<Self::MeltQuote, Self::Error>;
962
963 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
965 async fn melt_human_readable(
966 &self,
967 address: &str,
968 amount_msat: Self::Amount,
969 network: bitcoin::Network,
970 ) -> Result<Self::MeltQuote, Self::Error> {
971 self.melt_human_readable_quote(address, amount_msat, network)
972 .await
973 }
974
975 async fn check_mint_quote(&self, quote_id: &str) -> Result<Self::MintQuote, Self::Error> {
977 self.check_mint_quote_status(quote_id).await
978 }
979
980 async fn mint_unified(
982 &self,
983 quote_id: &str,
984 split_target: SplitTarget,
985 spending_conditions: Option<SpendingConditions>,
986 ) -> Result<Proofs, Self::Error> {
987 self.mint(quote_id, split_target, spending_conditions).await
988 }
989
990 async fn get_proofs_by_states(&self, states: Vec<State>) -> Result<Proofs, Self::Error>;
996
997 async fn generate_public_key(&self) -> Result<PublicKey, Self::Error>;
1000
1001 async fn get_public_key(
1003 &self,
1004 pubkey: &PublicKey,
1005 ) -> Result<Option<P2PKSigningKey>, Self::Error>;
1006
1007 async fn get_public_keys(&self) -> Result<Vec<P2PKSigningKey>, Self::Error>;
1009
1010 async fn get_latest_public_key(&self) -> Result<Option<P2PKSigningKey>, Self::Error>;
1012
1013 async fn get_signing_key(&self, pubkey: &PublicKey) -> Result<Option<SecretKey>, Self::Error>;
1015}
1016
1017#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1019pub struct P2PKSigningKey {
1020 pub pubkey: PublicKey,
1022 pub derivation_path: DerivationPath,
1024 pub derivation_index: u32,
1026 pub created_time: u64,
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032 use super::*;
1033 use crate::nuts::Id;
1034 use crate::secret::Secret;
1035
1036 #[test]
1037 fn test_transaction_id_from_hex() {
1038 let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1c";
1039 let transaction_id = TransactionId::from_hex(hex_str).unwrap();
1040 assert_eq!(transaction_id.to_string(), hex_str);
1041 }
1042
1043 #[test]
1044 fn test_transaction_id_from_hex_empty_string() {
1045 let hex_str = "";
1046 let res = TransactionId::from_hex(hex_str);
1047 assert!(matches!(res, Err(Error::InvalidTransactionId)));
1048 }
1049
1050 #[test]
1051 fn test_transaction_id_from_hex_longer_string() {
1052 let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1ca1b2";
1053 let res = TransactionId::from_hex(hex_str);
1054 assert!(matches!(res, Err(Error::InvalidTransactionId)));
1055 }
1056
1057 #[test]
1058 fn test_matches_conditions() {
1059 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
1060 let proof = Proof::new(
1061 Amount::from(64),
1062 keyset_id,
1063 Secret::new("test_secret"),
1064 PublicKey::from_hex(
1065 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1066 )
1067 .unwrap(),
1068 );
1069
1070 let mint_url = MintUrl::from_str("https://example.com").unwrap();
1071 let proof_info =
1072 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
1073
1074 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
1076 assert!(!proof_info.matches_conditions(
1077 &Some(MintUrl::from_str("https://different.com").unwrap()),
1078 &None,
1079 &None,
1080 &None
1081 ));
1082
1083 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
1085 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
1086
1087 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
1089 assert!(proof_info.matches_conditions(
1090 &None,
1091 &None,
1092 &Some(vec![State::Unspent, State::Spent]),
1093 &None
1094 ));
1095 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
1096
1097 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
1099
1100 assert!(proof_info.matches_conditions(
1102 &Some(mint_url),
1103 &Some(CurrencyUnit::Sat),
1104 &Some(vec![State::Unspent]),
1105 &None
1106 ));
1107 }
1108
1109 #[test]
1110 fn test_matches_conditions_with_spending_conditions() {
1111 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, State::Unspent, CurrencyUnit::Sat).unwrap();
1129
1130 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
1132
1133 let dummy_condition = SpendingConditions::P2PKConditions {
1135 data: SecretKey::generate().public_key(),
1136 conditions: None,
1137 };
1138 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
1139 }
1140}