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::SplitTarget;
11use cashu::nuts::nut07::ProofState;
12use cashu::nuts::nut18::PaymentRequest;
13use cashu::nuts::AuthProof;
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#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
646#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
647pub trait Wallet: Send + Sync {
648 type Error: std::error::Error + Send + Sync + 'static;
650 type Amount: Clone + Send + Sync;
652 type MintUrl: Clone + Send + Sync;
654 type CurrencyUnit: Clone + Send + Sync;
656 type MintInfo: Clone + Send + Sync;
658 type KeySetInfo: Clone + Send + Sync;
660 type MintQuote: Clone + Send + Sync;
662 type MeltQuote: Clone + Send + Sync;
664 type PaymentMethod: Clone + Send + Sync;
666 type MeltOptions: Clone + Send + Sync;
668 type OperationId: Clone + Send + Sync;
670 type PreparedSend<'a>: Send + Sync
672 where
673 Self: 'a;
674 type PreparedMelt<'a>: Send + Sync
676 where
677 Self: 'a;
678 type Subscription: Send + Sync;
680 type SubscribeParams: Clone + Send + Sync;
682
683 fn mint_url(&self) -> Self::MintUrl;
685
686 fn unit(&self) -> Self::CurrencyUnit;
688
689 async fn total_balance(&self) -> Result<Self::Amount, Self::Error>;
691
692 async fn total_pending_balance(&self) -> Result<Self::Amount, Self::Error>;
694
695 async fn total_reserved_balance(&self) -> Result<Self::Amount, Self::Error>;
697
698 async fn fetch_mint_info(&self) -> Result<Option<Self::MintInfo>, Self::Error>;
700
701 async fn load_mint_info(&self) -> Result<Self::MintInfo, Self::Error>;
703
704 async fn refresh_keysets(&self) -> Result<Vec<Self::KeySetInfo>, Self::Error>;
706
707 async fn get_active_keyset(&self) -> Result<Self::KeySetInfo, Self::Error>;
709
710 async fn mint_quote(
712 &self,
713 method: Self::PaymentMethod,
714 amount: Option<Self::Amount>,
715 description: Option<String>,
716 extra: Option<String>,
717 ) -> Result<Self::MintQuote, Self::Error>;
718
719 async fn melt_quote(
721 &self,
722 method: Self::PaymentMethod,
723 request: String,
724 options: Option<Self::MeltOptions>,
725 extra: Option<String>,
726 ) -> Result<Self::MeltQuote, Self::Error>;
727
728 async fn list_transactions(
730 &self,
731 direction: Option<TransactionDirection>,
732 ) -> Result<Vec<Transaction>, Self::Error>;
733
734 async fn get_transaction(&self, id: TransactionId) -> Result<Option<Transaction>, Self::Error>;
736
737 async fn get_proofs_for_transaction(&self, id: TransactionId) -> Result<Proofs, Self::Error>;
739
740 async fn revert_transaction(&self, id: TransactionId) -> Result<(), Self::Error>;
742
743 async fn check_all_pending_proofs(&self) -> Result<Self::Amount, Self::Error>;
745
746 async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<ProofState>, Self::Error>;
748
749 async fn get_keyset_fees_by_id(&self, keyset_id: Id) -> Result<u64, Self::Error>;
751
752 async fn calculate_fee(
754 &self,
755 proof_count: u64,
756 keyset_id: Id,
757 ) -> Result<Self::Amount, Self::Error>;
758
759 async fn receive(
761 &self,
762 encoded_token: &str,
763 options: ReceiveOptions,
764 ) -> Result<Self::Amount, Self::Error>;
765
766 async fn receive_proofs(
768 &self,
769 proofs: Proofs,
770 options: ReceiveOptions,
771 memo: Option<String>,
772 token: Option<String>,
773 ) -> Result<Self::Amount, Self::Error>;
774
775 async fn prepare_send(
777 &self,
778 amount: Self::Amount,
779 options: SendOptions,
780 ) -> Result<Self::PreparedSend<'_>, Self::Error>;
781
782 async fn get_pending_sends(&self) -> Result<Vec<Self::OperationId>, Self::Error>;
784
785 async fn revoke_send(
787 &self,
788 operation_id: Self::OperationId,
789 ) -> Result<Self::Amount, Self::Error>;
790
791 async fn check_send_status(&self, operation_id: Self::OperationId)
793 -> Result<bool, Self::Error>;
794
795 async fn mint(
797 &self,
798 quote_id: &str,
799 split_target: SplitTarget,
800 spending_conditions: Option<SpendingConditions>,
801 ) -> Result<Proofs, Self::Error>;
802
803 async fn check_mint_quote_status(&self, quote_id: &str)
805 -> Result<Self::MintQuote, Self::Error>;
806
807 async fn fetch_mint_quote(
809 &self,
810 quote_id: &str,
811 payment_method: Option<Self::PaymentMethod>,
812 ) -> Result<Self::MintQuote, Self::Error>;
813
814 async fn prepare_melt(
816 &self,
817 quote_id: &str,
818 metadata: HashMap<String, String>,
819 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
820
821 async fn prepare_melt_proofs(
823 &self,
824 quote_id: &str,
825 proofs: Proofs,
826 metadata: HashMap<String, String>,
827 ) -> Result<Self::PreparedMelt<'_>, Self::Error>;
828
829 async fn swap(
831 &self,
832 amount: Option<Self::Amount>,
833 split_target: SplitTarget,
834 input_proofs: Proofs,
835 spending_conditions: Option<SpendingConditions>,
836 include_fees: bool,
837 use_p2bk: bool,
838 ) -> Result<Option<Proofs>, Self::Error>;
839
840 async fn set_cat(&self, cat: String) -> Result<(), Self::Error>;
842
843 async fn set_refresh_token(&self, refresh_token: String) -> Result<(), Self::Error>;
845
846 async fn refresh_access_token(&self) -> Result<(), Self::Error>;
848
849 async fn mint_blind_auth(&self, amount: Self::Amount) -> Result<Proofs, Self::Error>;
851
852 async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, Self::Error>;
854
855 async fn restore(&self) -> Result<Restored, Self::Error>;
857
858 async fn verify_token_dleq(&self, token_str: &str) -> Result<(), Self::Error>;
860
861 async fn pay_request(
863 &self,
864 request: PaymentRequest,
865 custom_amount: Option<Self::Amount>,
866 ) -> Result<(), Self::Error>;
867
868 async fn subscribe_mint_quote_state(
873 &self,
874 quote_ids: Vec<String>,
875 method: Self::PaymentMethod,
876 ) -> Result<Self::Subscription, Self::Error>;
877
878 fn set_metadata_cache_ttl(&self, ttl_secs: Option<u64>);
884
885 async fn subscribe(
887 &self,
888 params: Self::SubscribeParams,
889 ) -> Result<Self::Subscription, Self::Error>;
890
891 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
893 async fn melt_bip353_quote(
894 &self,
895 bip353_address: &str,
896 amount_msat: Self::Amount,
897 network: bitcoin::Network,
898 ) -> Result<Self::MeltQuote, Self::Error>;
899
900 #[cfg(not(target_arch = "wasm32"))]
902 async fn melt_lightning_address_quote(
903 &self,
904 lightning_address: &str,
905 amount_msat: Self::Amount,
906 ) -> Result<Self::MeltQuote, Self::Error>;
907
908 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
914 async fn melt_human_readable_quote(
915 &self,
916 address: &str,
917 amount_msat: Self::Amount,
918 network: bitcoin::Network,
919 ) -> Result<Self::MeltQuote, Self::Error>;
920
921 #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
923 async fn melt_human_readable(
924 &self,
925 address: &str,
926 amount_msat: Self::Amount,
927 network: bitcoin::Network,
928 ) -> Result<Self::MeltQuote, Self::Error> {
929 self.melt_human_readable_quote(address, amount_msat, network)
930 .await
931 }
932
933 async fn check_mint_quote(&self, quote_id: &str) -> Result<Self::MintQuote, Self::Error> {
935 self.check_mint_quote_status(quote_id).await
936 }
937
938 async fn mint_unified(
940 &self,
941 quote_id: &str,
942 split_target: SplitTarget,
943 spending_conditions: Option<SpendingConditions>,
944 ) -> Result<Proofs, Self::Error> {
945 self.mint(quote_id, split_target, spending_conditions).await
946 }
947
948 async fn get_proofs_by_states(&self, states: Vec<State>) -> Result<Proofs, Self::Error>;
954
955 async fn generate_public_key(&self) -> Result<PublicKey, Self::Error>;
958
959 async fn get_public_key(
961 &self,
962 pubkey: &PublicKey,
963 ) -> Result<Option<P2PKSigningKey>, Self::Error>;
964
965 async fn get_public_keys(&self) -> Result<Vec<P2PKSigningKey>, Self::Error>;
967
968 async fn get_latest_public_key(&self) -> Result<Option<P2PKSigningKey>, Self::Error>;
970
971 async fn get_signing_key(&self, pubkey: &PublicKey) -> Result<Option<SecretKey>, Self::Error>;
973}
974
975#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
977pub struct P2PKSigningKey {
978 pub pubkey: PublicKey,
980 pub derivation_path: DerivationPath,
982 pub derivation_index: u32,
984 pub created_time: u64,
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991 use crate::nuts::Id;
992 use crate::secret::Secret;
993
994 #[test]
995 fn test_transaction_id_from_hex() {
996 let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1c";
997 let transaction_id = TransactionId::from_hex(hex_str).unwrap();
998 assert_eq!(transaction_id.to_string(), hex_str);
999 }
1000
1001 #[test]
1002 fn test_transaction_id_from_hex_empty_string() {
1003 let hex_str = "";
1004 let res = TransactionId::from_hex(hex_str);
1005 assert!(matches!(res, Err(Error::InvalidTransactionId)));
1006 }
1007
1008 #[test]
1009 fn test_transaction_id_from_hex_longer_string() {
1010 let hex_str = "a1b2c3d4e5f60718293a0b1c2d3e4f506172839a0b1c2d3e4f506172839a0b1ca1b2";
1011 let res = TransactionId::from_hex(hex_str);
1012 assert!(matches!(res, Err(Error::InvalidTransactionId)));
1013 }
1014
1015 #[test]
1016 fn test_matches_conditions() {
1017 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
1018 let proof = Proof::new(
1019 Amount::from(64),
1020 keyset_id,
1021 Secret::new("test_secret"),
1022 PublicKey::from_hex(
1023 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1024 )
1025 .unwrap(),
1026 );
1027
1028 let mint_url = MintUrl::from_str("https://example.com").unwrap();
1029 let proof_info =
1030 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
1031
1032 assert!(proof_info.matches_conditions(&Some(mint_url.clone()), &None, &None, &None));
1034 assert!(!proof_info.matches_conditions(
1035 &Some(MintUrl::from_str("https://different.com").unwrap()),
1036 &None,
1037 &None,
1038 &None
1039 ));
1040
1041 assert!(proof_info.matches_conditions(&None, &Some(CurrencyUnit::Sat), &None, &None));
1043 assert!(!proof_info.matches_conditions(&None, &Some(CurrencyUnit::Msat), &None, &None));
1044
1045 assert!(proof_info.matches_conditions(&None, &None, &Some(vec![State::Unspent]), &None));
1047 assert!(proof_info.matches_conditions(
1048 &None,
1049 &None,
1050 &Some(vec![State::Unspent, State::Spent]),
1051 &None
1052 ));
1053 assert!(!proof_info.matches_conditions(&None, &None, &Some(vec![State::Spent]), &None));
1054
1055 assert!(proof_info.matches_conditions(&None, &None, &None, &None));
1057
1058 assert!(proof_info.matches_conditions(
1060 &Some(mint_url),
1061 &Some(CurrencyUnit::Sat),
1062 &Some(vec![State::Unspent]),
1063 &None
1064 ));
1065 }
1066
1067 #[test]
1068 fn test_matches_conditions_with_spending_conditions() {
1069 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
1074 let proof = Proof::new(
1075 Amount::from(64),
1076 keyset_id,
1077 Secret::new("test_secret"),
1078 PublicKey::from_hex(
1079 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1080 )
1081 .unwrap(),
1082 );
1083
1084 let mint_url = MintUrl::from_str("https://example.com").unwrap();
1085 let proof_info =
1086 ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap();
1087
1088 assert!(proof_info.matches_conditions(&None, &None, &None, &Some(vec![])));
1090
1091 let dummy_condition = SpendingConditions::P2PKConditions {
1093 data: SecretKey::generate().public_key(),
1094 conditions: None,
1095 };
1096 assert!(!proof_info.matches_conditions(&None, &None, &None, &Some(vec![dummy_condition])));
1097 }
1098}