1use std::str::FromStr;
4use std::sync::Arc;
5
6use bip39::Mnemonic;
7use cdk::wallet::{Wallet as CdkWallet, WalletBuilder as CdkWalletBuilder};
8
9use crate::error::FfiError;
10use crate::token::Token;
11#[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
12use crate::types::bip321::BitcoinNetwork;
13use crate::types::payment_request::PaymentRequest;
14use crate::types::*;
15
16#[derive(uniffi::Object)]
19pub struct Wallet {
20 inner: Arc<CdkWallet>,
21}
22
23impl Wallet {
24 pub(crate) fn from_inner(inner: Arc<CdkWallet>) -> Self {
26 Self { inner }
27 }
28
29 pub(crate) fn inner(&self) -> &Arc<CdkWallet> {
31 &self.inner
32 }
33}
34
35#[uniffi::export(async_runtime = "tokio")]
36impl Wallet {
37 #[uniffi::constructor]
44 pub fn new(
45 mint_url: String,
46 unit: CurrencyUnit,
47 mnemonic: String,
48 store: crate::database::WalletStore,
49 config: WalletConfig,
50 ) -> Result<Self, FfiError> {
51 let db = crate::database::resolve_wallet_store(store)?;
52 let localstore = crate::database::create_cdk_database_from_ffi(db);
53
54 let m = Mnemonic::parse(&mnemonic)
55 .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
56 let seed = m.to_seed_normalized("");
57
58 let wallet = CdkWalletBuilder::new()
59 .mint_url(mint_url.parse().map_err(|e: cdk::mint_url::Error| {
60 FfiError::internal(format!("Invalid URL: {}", e))
61 })?)
62 .unit(unit.into())
63 .localstore(localstore)
64 .seed(seed)
65 .target_proof_count(config.target_proof_count.unwrap_or(3) as usize)
66 .build()
67 .map_err(FfiError::from)?;
68
69 Ok(Self {
70 inner: Arc::new(wallet),
71 })
72 }
73
74 pub fn mint_url(&self) -> MintUrl {
76 self.inner.mint_url.clone().into()
77 }
78
79 pub fn unit(&self) -> CurrencyUnit {
81 self.inner.unit.clone().into()
82 }
83
84 pub fn set_metadata_cache_ttl(&self, ttl_secs: Option<u64>) {
103 let ttl = ttl_secs.map(std::time::Duration::from_secs);
104 self.inner.set_metadata_cache_ttl(ttl);
105 }
106
107 pub async fn total_balance(&self) -> Result<Amount, FfiError> {
109 let balance = self.inner.total_balance().await?;
110 Ok(balance.into())
111 }
112
113 pub async fn total_pending_balance(&self) -> Result<Amount, FfiError> {
115 let balance = self.inner.total_pending_balance().await?;
116 Ok(balance.into())
117 }
118
119 pub async fn total_reserved_balance(&self) -> Result<Amount, FfiError> {
121 let balance = self.inner.total_reserved_balance().await?;
122 Ok(balance.into())
123 }
124
125 pub async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, FfiError> {
127 let info = self.inner.fetch_mint_info().await?;
128 Ok(info.map(Into::into))
129 }
130
131 pub async fn load_mint_info(&self) -> Result<MintInfo, FfiError> {
135 let info = self.inner.load_mint_info().await?;
136 Ok(info.into())
137 }
138
139 pub async fn receive(
141 &self,
142 token: std::sync::Arc<Token>,
143 options: ReceiveOptions,
144 ) -> Result<Amount, FfiError> {
145 let amount = self
146 .inner
147 .receive(&token.to_string(), options.try_into()?)
148 .await?;
149 Ok(amount.into())
150 }
151
152 pub async fn restore(&self) -> Result<Restored, FfiError> {
154 let restored = self.inner.restore().await?;
155 Ok(restored.into())
156 }
157
158 pub async fn restore_with_opts(&self, opts: NUT13Options) -> Result<Restored, FfiError> {
160 let restored = self.inner.restore_with_opts(opts.try_into()?).await?;
161 Ok(restored.into())
162 }
163
164 pub async fn verify_token_dleq(&self, token: std::sync::Arc<Token>) -> Result<(), FfiError> {
166 let cdk_token = token.inner.clone();
167 self.inner.verify_token_dleq(&cdk_token).await?;
168 Ok(())
169 }
170
171 pub async fn receive_proofs(
173 &self,
174 proofs: Proofs,
175 options: ReceiveOptions,
176 memo: Option<String>,
177 token: Option<String>,
178 ) -> Result<Amount, FfiError> {
179 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
180 proofs.into_iter().map(|p| p.try_into()).collect();
181 let cdk_proofs = cdk_proofs?;
182
183 let amount = self
184 .inner
185 .receive_proofs(cdk_proofs, options.try_into()?, memo, token)
186 .await?;
187 Ok(amount.into())
188 }
189
190 pub async fn get_pending_sends(&self) -> Result<Vec<String>, FfiError> {
192 let sends = self.inner.get_pending_sends().await?;
193 Ok(sends.into_iter().map(|id| id.to_string()).collect())
194 }
195
196 pub async fn revoke_send(&self, operation_id: String) -> Result<Amount, FfiError> {
198 let uuid = uuid::Uuid::parse_str(&operation_id)
199 .map_err(|e| FfiError::internal(format!("Invalid operation ID: {}", e)))?;
200 let amount = self.inner.revoke_send(uuid).await?;
201 Ok(amount.into())
202 }
203
204 pub async fn check_send_status(&self, operation_id: String) -> Result<bool, FfiError> {
206 let uuid = uuid::Uuid::parse_str(&operation_id)
207 .map_err(|e| FfiError::internal(format!("Invalid operation ID: {}", e)))?;
208 let claimed = self.inner.check_send_status(uuid).await?;
209 Ok(claimed)
210 }
211
212 pub async fn prepare_send(
214 &self,
215 amount: Amount,
216 options: SendOptions,
217 ) -> Result<std::sync::Arc<PreparedSend>, FfiError> {
218 let prepared = self
219 .inner
220 .prepare_send(amount.into(), options.try_into()?)
221 .await?;
222 Ok(std::sync::Arc::new(PreparedSend::new(
223 self.inner.clone(),
224 &prepared,
225 )))
226 }
227
228 pub async fn mint_quote(
230 &self,
231 payment_method: PaymentMethod,
232 amount: Option<Amount>,
233 description: Option<String>,
234 extra: Option<String>,
235 ) -> Result<MintQuote, FfiError> {
236 let quote = self
237 .inner
238 .mint_quote(
239 payment_method.into(),
240 amount.map(Into::into),
241 description,
242 extra,
243 )
244 .await?;
245 Ok(quote.into())
246 }
247
248 pub async fn check_mint_quote(&self, quote_id: String) -> Result<MintQuote, FfiError> {
259 self.check_mint_quote_status(quote_id).await
260 }
261
262 pub async fn check_mint_quote_status(&self, quote_id: String) -> Result<MintQuote, FfiError> {
273 let quote = self.inner.check_mint_quote_status("e_id).await?;
274 Ok(quote.into())
275 }
276
277 pub async fn fetch_mint_quote(
287 &self,
288 quote_id: String,
289 payment_method: Option<PaymentMethod>,
290 ) -> Result<MintQuote, FfiError> {
291 let method = payment_method.map(Into::into);
292 let quote = self.inner.fetch_mint_quote("e_id, method).await?;
293 Ok(quote.into())
294 }
295
296 pub async fn mint(
298 &self,
299 quote_id: String,
300 amount_split_target: SplitTarget,
301 spending_conditions: Option<SpendingConditions>,
302 ) -> Result<Proofs, FfiError> {
303 let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
305
306 let proofs = self
307 .inner
308 .mint("e_id, amount_split_target.into(), conditions)
309 .await?;
310 Ok(proofs.into_iter().map(|p| p.into()).collect())
311 }
312
313 pub async fn prepare_melt(&self, quote_id: String) -> Result<PreparedMelt, FfiError> {
317 let prepared = self
318 .inner
319 .prepare_melt("e_id, std::collections::HashMap::new())
320 .await?;
321 Ok(PreparedMelt::new(Arc::clone(&self.inner), &prepared))
322 }
323
324 pub async fn prepare_melt_proofs(
339 &self,
340 quote_id: String,
341 proofs: Proofs,
342 ) -> Result<PreparedMelt, FfiError> {
343 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
344 proofs.into_iter().map(|p| p.try_into()).collect();
345 let cdk_proofs = cdk_proofs?;
346
347 let prepared = self
348 .inner
349 .prepare_melt_proofs("e_id, cdk_proofs, std::collections::HashMap::new())
350 .await?;
351 Ok(PreparedMelt::new(Arc::clone(&self.inner), &prepared))
352 }
353
354 pub async fn prepare_melt_token(
368 &self,
369 quote_id: String,
370 encoded_token: String,
371 ) -> Result<PreparedMelt, FfiError> {
372 let prepared = self
373 .inner
374 .prepare_melt_token("e_id, &encoded_token, std::collections::HashMap::new())
375 .await?;
376 Ok(PreparedMelt::new(Arc::clone(&self.inner), &prepared))
377 }
378
379 pub async fn mint_unified(
380 &self,
381 quote_id: String,
382 amount_split_target: SplitTarget,
383 spending_conditions: Option<SpendingConditions>,
384 ) -> Result<Proofs, FfiError> {
385 let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
386
387 let proofs = self
388 .inner
389 .mint("e_id, amount_split_target.into(), conditions)
390 .await?;
391
392 Ok(proofs.into_iter().map(|p| p.into()).collect())
393 }
394 pub async fn melt_quote(
406 &self,
407 method: PaymentMethod,
408 request: String,
409 options: Option<MeltOptions>,
410 extra: Option<String>,
411 ) -> Result<MeltQuote, FfiError> {
412 let cdk_options = options.map(Into::into);
413 let quote = self
414 .inner
415 .melt_quote::<cdk::nuts::PaymentMethod, _>(method.into(), request, cdk_options, extra)
416 .await?;
417 Ok(quote.into())
418 }
419
420 pub async fn quote_onchain_melt_options(
426 &self,
427 address: String,
428 amount: Amount,
429 max_fee_amount: Option<Amount>,
430 ) -> Result<Vec<MeltQuote>, FfiError> {
431 let quotes = self
432 .inner
433 .quote_onchain_melt_options(&address, amount.into(), max_fee_amount.map(Into::into))
434 .await?;
435
436 Ok(quotes.into_iter().map(Into::into).collect())
437 }
438
439 pub async fn select_onchain_melt_quote(&self, quote: MeltQuote) -> Result<MeltQuote, FfiError> {
441 let quote = self
442 .inner
443 .select_onchain_melt_quote(quote.try_into()?)
444 .await?;
445 Ok(quote.into())
446 }
447
448 pub async fn check_melt_quote_status(&self, quote_id: String) -> Result<MeltQuote, FfiError> {
450 let quote = self.inner.check_melt_quote_status("e_id).await?;
451 Ok(quote.into())
452 }
453
454 pub async fn finalize_pending_melts(&self) -> Result<Vec<FinalizedMelt>, FfiError> {
456 let finalized = self.inner.finalize_pending_melts().await?;
457 Ok(finalized.into_iter().map(Into::into).collect())
458 }
459
460 pub async fn swap(
462 &self,
463 amount: Option<Amount>,
464 amount_split_target: SplitTarget,
465 input_proofs: Proofs,
466 spending_conditions: Option<SpendingConditions>,
467 include_fees: bool,
468 ) -> Result<Option<Proofs>, FfiError> {
469 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
470 input_proofs.into_iter().map(|p| p.try_into()).collect();
471 let cdk_proofs = cdk_proofs?;
472
473 let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
475
476 let result = self
477 .inner
478 .swap(
479 amount.map(Into::into),
480 amount_split_target.into(),
481 cdk_proofs,
482 conditions,
483 include_fees,
484 false,
485 )
486 .await?;
487
488 Ok(result.map(|proofs| proofs.into_iter().map(|p| p.into()).collect()))
489 }
490
491 pub async fn get_proofs_by_states(&self, states: Vec<ProofState>) -> Result<Proofs, FfiError> {
493 let mut all_proofs = Vec::new();
494
495 for state in states {
496 let proofs = match state {
497 ProofState::Unspent => self.inner.get_unspent_proofs().await?,
498 ProofState::Pending => self.inner.get_pending_proofs().await?,
499 ProofState::Reserved => self.inner.get_reserved_proofs().await?,
500 ProofState::PendingSpent => self.inner.get_pending_spent_proofs().await?,
501 ProofState::Spent => {
502 continue;
505 }
506 };
507
508 for proof in proofs {
509 all_proofs.push(proof.into());
510 }
511 }
512
513 Ok(all_proofs)
514 }
515
516 pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<bool>, FfiError> {
518 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
519 proofs.into_iter().map(|p| p.try_into()).collect();
520 let cdk_proofs = cdk_proofs?;
521
522 let proof_states = self.inner.check_proofs_spent(cdk_proofs).await?;
523 let spent_bools = proof_states
525 .into_iter()
526 .map(|proof_state| {
527 matches!(
528 proof_state.state,
529 cdk::nuts::State::Spent | cdk::nuts::State::PendingSpent
530 )
531 })
532 .collect();
533 Ok(spent_bools)
534 }
535
536 pub async fn list_transactions(
538 &self,
539 direction: Option<TransactionDirection>,
540 ) -> Result<Vec<Transaction>, FfiError> {
541 let cdk_direction = direction.map(Into::into);
542 let transactions = self.inner.list_transactions(cdk_direction).await?;
543 Ok(transactions.into_iter().map(Into::into).collect())
544 }
545
546 pub async fn get_transaction(
548 &self,
549 id: TransactionId,
550 ) -> Result<Option<Transaction>, FfiError> {
551 let cdk_id = id.try_into()?;
552 let transaction = self.inner.get_transaction(cdk_id).await?;
553 Ok(transaction.map(Into::into))
554 }
555
556 pub async fn get_proofs_for_transaction(
561 &self,
562 id: TransactionId,
563 ) -> Result<Vec<Proof>, FfiError> {
564 let cdk_id = id.try_into()?;
565 let proofs = self.inner.get_proofs_for_transaction(cdk_id).await?;
566 Ok(proofs.into_iter().map(Into::into).collect())
567 }
568
569 pub async fn revert_transaction(&self, id: TransactionId) -> Result<(), FfiError> {
571 let cdk_id = id.try_into()?;
572 self.inner.revert_transaction(cdk_id).await?;
573 Ok(())
574 }
575
576 pub async fn subscribe(
578 &self,
579 params: SubscribeParams,
580 ) -> Result<std::sync::Arc<ActiveSubscription>, FfiError> {
581 let cdk_params: cdk::nuts::nut17::Params<Arc<String>> = params.clone().into();
582 let sub_id = cdk_params.id.to_string();
583 let active_sub = self.inner.subscribe(cdk_params).await?;
584 Ok(std::sync::Arc::new(ActiveSubscription::new(
585 active_sub, sub_id,
586 )))
587 }
588
589 pub async fn subscribe_mint_quote_state(
603 &self,
604 quote_ids: Vec<String>,
605 payment_method: PaymentMethod,
606 ) -> Result<std::sync::Arc<ActiveSubscription>, FfiError> {
607 let cdk_method: cdk_common::PaymentMethod = payment_method.into();
608 let active_sub = self
609 .inner
610 .subscribe_mint_quote_state(quote_ids, cdk_method)
611 .await?;
612 let sub_id = uuid::Uuid::new_v4().to_string();
613 Ok(std::sync::Arc::new(ActiveSubscription::new(
614 active_sub, sub_id,
615 )))
616 }
617
618 pub async fn refresh_keysets(&self) -> Result<Vec<KeySetInfo>, FfiError> {
620 let keysets = self.inner.refresh_keysets().await?;
621 Ok(keysets.into_iter().map(Into::into).collect())
622 }
623
624 pub async fn get_active_keyset(&self) -> Result<KeySetInfo, FfiError> {
626 let keyset = self.inner.get_active_keyset().await?;
627 Ok(keyset.into())
628 }
629
630 pub async fn get_keyset_fees_by_id(&self, keyset_id: String) -> Result<u64, FfiError> {
632 let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
633 Ok(self.inner.get_keyset_fees_by_id(id).await?)
634 }
635
636 pub async fn load_keyset_keys(&self, keyset_id: String) -> Result<Keys, FfiError> {
638 let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
639 let keys = self.inner.load_keyset_keys(id).await?;
640 Ok(keys.into())
641 }
642
643 pub async fn get_mint_keysets(
645 &self,
646 filter: KeysetFilter,
647 ) -> Result<Vec<KeySetInfo>, FfiError> {
648 let keysets = self.inner.get_mint_keysets(filter.into()).await?;
649 Ok(keysets.into_iter().map(Into::into).collect())
650 }
651
652 pub async fn load_mint_keysets(&self) -> Result<Vec<KeySetInfo>, FfiError> {
654 let keysets = self.inner.load_mint_keysets().await?;
655 Ok(keysets.into_iter().map(Into::into).collect())
656 }
657
658 pub async fn fetch_active_keyset(&self) -> Result<KeySetInfo, FfiError> {
660 let keyset = self.inner.fetch_active_keyset().await?;
661 Ok(keyset.into())
662 }
663
664 pub async fn get_keyset_fees_and_amounts(
666 &self,
667 ) -> Result<std::collections::HashMap<String, FeeAndAmounts>, FfiError> {
668 let fees = self.inner.get_keyset_fees_and_amounts().await?;
669 Ok(fees
670 .into_iter()
671 .map(|(id, fa)| (id.to_string(), fa.into()))
672 .collect())
673 }
674
675 pub async fn get_keyset_fees_and_amounts_by_id(
677 &self,
678 keyset_id: String,
679 ) -> Result<FeeAndAmounts, FfiError> {
680 let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
681 let fa = self.inner.get_keyset_fees_and_amounts_by_id(id).await?;
682 Ok(fa.into())
683 }
684
685 pub async fn get_keyset_count_fee(
687 &self,
688 keyset_id: String,
689 count: u64,
690 ) -> Result<Amount, FfiError> {
691 let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
692 let fee = self.inner.get_keyset_count_fee(&id, count).await?;
693 Ok(fee.into())
694 }
695
696 pub async fn check_all_pending_proofs(&self) -> Result<Amount, FfiError> {
701 let amount = self.inner.check_all_pending_proofs().await?;
702 Ok(amount.into())
703 }
704
705 pub async fn recover_incomplete_sagas(&self) -> Result<RecoveryReport, FfiError> {
710 let report = self.inner.recover_incomplete_sagas().await?;
711 Ok(report.into())
712 }
713
714 pub async fn calculate_fee(
716 &self,
717 proof_count: u32,
718 keyset_id: String,
719 ) -> Result<Amount, FfiError> {
720 let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
721 let fee = self.inner.calculate_fee(proof_count as u64, id).await?;
722 Ok(fee.into())
723 }
724
725 pub async fn pay_request(
735 &self,
736 payment_request: std::sync::Arc<PaymentRequest>,
737 custom_amount: Option<Amount>,
738 ) -> Result<(), FfiError> {
739 self.inner
740 .pay_request(
741 payment_request.inner().clone(),
742 custom_amount.map(Into::into),
743 )
744 .await?;
745 Ok(())
746 }
747}
748
749#[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
751#[uniffi::export(async_runtime = "tokio")]
752impl Wallet {
753 pub async fn melt_bip353_quote(
761 &self,
762 bip353_address: String,
763 amount_msat: Amount,
764 network: BitcoinNetwork,
765 ) -> Result<MeltQuote, FfiError> {
766 let cdk_amount: cdk::Amount = amount_msat.into();
767 let quote = self
768 .inner
769 .melt_bip353_quote(&bip353_address, cdk_amount, network.into())
770 .await?;
771 Ok(quote.into())
772 }
773
774 pub async fn melt_lightning_address_quote(
779 &self,
780 lightning_address: String,
781 amount_msat: Amount,
782 ) -> Result<MeltQuote, FfiError> {
783 let cdk_amount: cdk::Amount = amount_msat.into();
784 let quote = self
785 .inner
786 .melt_lightning_address_quote(&lightning_address, cdk_amount)
787 .await?;
788 Ok(quote.into())
789 }
790
791 pub async fn melt_human_readable(
804 &self,
805 address: String,
806 amount_msat: Amount,
807 network: BitcoinNetwork,
808 ) -> Result<MeltQuote, FfiError> {
809 self.melt_human_readable_quote(address, amount_msat, network)
810 .await
811 }
812
813 pub async fn melt_human_readable_quote(
819 &self,
820 address: String,
821 amount_msat: Amount,
822 network: BitcoinNetwork,
823 ) -> Result<MeltQuote, FfiError> {
824 let cdk_amount: cdk::Amount = amount_msat.into();
825 let quote = self
826 .inner
827 .melt_human_readable_quote(&address, cdk_amount, network.into())
828 .await?;
829 Ok(quote.into())
830 }
831}
832
833#[uniffi::export(async_runtime = "tokio")]
835impl Wallet {
836 pub async fn set_cat(&self, cat: String) -> Result<(), FfiError> {
838 self.inner.set_cat(cat).await?;
839 Ok(())
840 }
841
842 pub async fn set_refresh_token(&self, refresh_token: String) -> Result<(), FfiError> {
844 self.inner.set_refresh_token(refresh_token).await?;
845 Ok(())
846 }
847
848 pub async fn refresh_access_token(&self) -> Result<(), FfiError> {
850 self.inner.refresh_access_token().await?;
851 Ok(())
852 }
853
854 pub async fn mint_blind_auth(&self, amount: Amount) -> Result<Proofs, FfiError> {
856 let proofs = self.inner.mint_blind_auth(amount.into()).await?;
857 Ok(proofs.into_iter().map(|p| p.into()).collect())
858 }
859
860 pub async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, FfiError> {
862 let auth_proofs = self.inner.get_unspent_auth_proofs().await?;
863 Ok(auth_proofs.into_iter().map(Into::into).collect())
864 }
865}
866
867#[derive(Debug, Clone, uniffi::Record)]
869pub struct WalletConfig {
870 pub target_proof_count: Option<u32>,
871}
872
873#[uniffi::export]
875pub fn generate_mnemonic() -> Result<String, FfiError> {
876 let mnemonic = Mnemonic::generate(12)
877 .map_err(|e| FfiError::internal(format!("Failed to generate mnemonic: {}", e)))?;
878 Ok(mnemonic.to_string())
879}
880
881#[uniffi::export]
883pub fn mnemonic_to_entropy(mnemonic: String) -> Result<Vec<u8>, FfiError> {
884 let m = Mnemonic::parse(&mnemonic)
885 .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
886 Ok(m.to_entropy())
887}