wallet.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. //! FFI Wallet bindings
  2. use std::str::FromStr;
  3. use std::sync::Arc;
  4. use bip39::Mnemonic;
  5. use cdk::wallet::{Wallet as CdkWallet, WalletBuilder as CdkWalletBuilder};
  6. use crate::error::FfiError;
  7. use crate::token::Token;
  8. use crate::types::payment_request::PaymentRequest;
  9. use crate::types::*;
  10. /// FFI-compatible Wallet
  11. #[derive(uniffi::Object)]
  12. pub struct Wallet {
  13. inner: Arc<CdkWallet>,
  14. }
  15. impl Wallet {
  16. /// Create a Wallet from an existing CDK wallet (internal use only)
  17. pub(crate) fn from_inner(inner: Arc<CdkWallet>) -> Self {
  18. Self { inner }
  19. }
  20. }
  21. #[uniffi::export(async_runtime = "tokio")]
  22. impl Wallet {
  23. /// Create a new Wallet from mnemonic using WalletDatabaseFfi trait
  24. #[uniffi::constructor]
  25. pub fn new(
  26. mint_url: String,
  27. unit: CurrencyUnit,
  28. mnemonic: String,
  29. db: Arc<dyn crate::database::WalletDatabase>,
  30. config: WalletConfig,
  31. ) -> Result<Self, FfiError> {
  32. // Parse mnemonic and generate seed without passphrase
  33. let m = Mnemonic::parse(&mnemonic)
  34. .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
  35. let seed = m.to_seed_normalized("");
  36. tracing::info!("creating ffi wallet");
  37. // Convert the FFI database trait to a CDK database implementation
  38. let localstore = crate::database::create_cdk_database_from_ffi(db);
  39. let wallet = CdkWalletBuilder::new()
  40. .mint_url(mint_url.parse().map_err(|e: cdk::mint_url::Error| {
  41. FfiError::internal(format!("Invalid URL: {}", e))
  42. })?)
  43. .unit(unit.into())
  44. .localstore(localstore)
  45. .seed(seed)
  46. .target_proof_count(config.target_proof_count.unwrap_or(3) as usize)
  47. .build()
  48. .map_err(FfiError::from)?;
  49. Ok(Self {
  50. inner: Arc::new(wallet),
  51. })
  52. }
  53. /// Get the mint URL
  54. pub fn mint_url(&self) -> MintUrl {
  55. self.inner.mint_url.clone().into()
  56. }
  57. /// Get the currency unit
  58. pub fn unit(&self) -> CurrencyUnit {
  59. self.inner.unit.clone().into()
  60. }
  61. /// Set metadata cache TTL (time-to-live) in seconds
  62. ///
  63. /// Controls how long cached mint metadata (keysets, keys, mint info) is considered fresh
  64. /// before requiring a refresh from the mint server.
  65. ///
  66. /// # Arguments
  67. ///
  68. /// * `ttl_secs` - Optional TTL in seconds. If None, cache never expires and is always used.
  69. ///
  70. /// # Example
  71. ///
  72. /// ```ignore
  73. /// // Cache expires after 5 minutes
  74. /// wallet.set_metadata_cache_ttl(Some(300));
  75. ///
  76. /// // Cache never expires (default)
  77. /// wallet.set_metadata_cache_ttl(None);
  78. /// ```
  79. pub fn set_metadata_cache_ttl(&self, ttl_secs: Option<u64>) {
  80. let ttl = ttl_secs.map(std::time::Duration::from_secs);
  81. self.inner.set_metadata_cache_ttl(ttl);
  82. }
  83. /// Get total balance
  84. pub async fn total_balance(&self) -> Result<Amount, FfiError> {
  85. let balance = self.inner.total_balance().await?;
  86. Ok(balance.into())
  87. }
  88. /// Get total pending balance
  89. pub async fn total_pending_balance(&self) -> Result<Amount, FfiError> {
  90. let balance = self.inner.total_pending_balance().await?;
  91. Ok(balance.into())
  92. }
  93. /// Get total reserved balance
  94. pub async fn total_reserved_balance(&self) -> Result<Amount, FfiError> {
  95. let balance = self.inner.total_reserved_balance().await?;
  96. Ok(balance.into())
  97. }
  98. /// Get mint info from mint
  99. pub async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, FfiError> {
  100. let info = self.inner.fetch_mint_info().await?;
  101. Ok(info.map(Into::into))
  102. }
  103. /// Load mint info
  104. ///
  105. /// This will get mint info from cache if it is fresh
  106. pub async fn load_mint_info(&self) -> Result<MintInfo, FfiError> {
  107. let info = self.inner.load_mint_info().await?;
  108. Ok(info.into())
  109. }
  110. /// Receive tokens
  111. pub async fn receive(
  112. &self,
  113. token: std::sync::Arc<Token>,
  114. options: ReceiveOptions,
  115. ) -> Result<Amount, FfiError> {
  116. let amount = self
  117. .inner
  118. .receive(&token.to_string(), options.into())
  119. .await?;
  120. Ok(amount.into())
  121. }
  122. /// Restore wallet from seed
  123. pub async fn restore(&self) -> Result<Restored, FfiError> {
  124. let restored = self.inner.restore().await?;
  125. Ok(restored.into())
  126. }
  127. /// Verify token DLEQ proofs
  128. pub async fn verify_token_dleq(&self, token: std::sync::Arc<Token>) -> Result<(), FfiError> {
  129. let cdk_token = token.inner.clone();
  130. self.inner.verify_token_dleq(&cdk_token).await?;
  131. Ok(())
  132. }
  133. /// Receive proofs directly
  134. pub async fn receive_proofs(
  135. &self,
  136. proofs: Proofs,
  137. options: ReceiveOptions,
  138. memo: Option<String>,
  139. ) -> Result<Amount, FfiError> {
  140. let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
  141. proofs.into_iter().map(|p| p.try_into()).collect();
  142. let cdk_proofs = cdk_proofs?;
  143. let amount = self
  144. .inner
  145. .receive_proofs(cdk_proofs, options.into(), memo)
  146. .await?;
  147. Ok(amount.into())
  148. }
  149. /// Prepare a send operation
  150. pub async fn prepare_send(
  151. &self,
  152. amount: Amount,
  153. options: SendOptions,
  154. ) -> Result<std::sync::Arc<PreparedSend>, FfiError> {
  155. let prepared = self
  156. .inner
  157. .prepare_send(amount.into(), options.into())
  158. .await?;
  159. Ok(std::sync::Arc::new(prepared.into()))
  160. }
  161. /// Get a mint quote
  162. pub async fn mint_quote(
  163. &self,
  164. amount: Amount,
  165. description: Option<String>,
  166. ) -> Result<MintQuote, FfiError> {
  167. let quote = self.inner.mint_quote(amount.into(), description).await?;
  168. Ok(quote.into())
  169. }
  170. /// Check a specific mint quote status
  171. pub async fn check_mint_quote(
  172. &self,
  173. quote_id: String,
  174. ) -> Result<MintQuoteBolt11Response, FfiError> {
  175. let quote = self.inner.mint_quote_state(&quote_id).await?;
  176. Ok(quote.into())
  177. }
  178. /// Mint tokens
  179. pub async fn mint(
  180. &self,
  181. quote_id: String,
  182. amount_split_target: SplitTarget,
  183. spending_conditions: Option<SpendingConditions>,
  184. ) -> Result<Proofs, FfiError> {
  185. // Convert spending conditions if provided
  186. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  187. let proofs = self
  188. .inner
  189. .mint(&quote_id, amount_split_target.into(), conditions)
  190. .await?;
  191. Ok(proofs.into_iter().map(|p| p.into()).collect())
  192. }
  193. /// Get a melt quote
  194. pub async fn melt_quote(
  195. &self,
  196. request: String,
  197. options: Option<MeltOptions>,
  198. ) -> Result<MeltQuote, FfiError> {
  199. let cdk_options = options.map(Into::into);
  200. let quote = self.inner.melt_quote(request, cdk_options).await?;
  201. Ok(quote.into())
  202. }
  203. /// Melt tokens
  204. pub async fn melt(&self, quote_id: String) -> Result<Melted, FfiError> {
  205. let melted = self.inner.melt(&quote_id).await?;
  206. Ok(melted.into())
  207. }
  208. /// Melt specific proofs
  209. ///
  210. /// This method allows melting proofs that may not be in the wallet's database,
  211. /// similar to how `receive_proofs` handles external proofs. The proofs will be
  212. /// added to the database and used for the melt operation.
  213. ///
  214. /// # Arguments
  215. ///
  216. /// * `quote_id` - The melt quote ID (obtained from `melt_quote`)
  217. /// * `proofs` - The proofs to melt (can be external proofs not in the wallet's database)
  218. ///
  219. /// # Returns
  220. ///
  221. /// A `Melted` result containing the payment details and any change proofs
  222. pub async fn melt_proofs(&self, quote_id: String, proofs: Proofs) -> Result<Melted, FfiError> {
  223. let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
  224. proofs.into_iter().map(|p| p.try_into()).collect();
  225. let cdk_proofs = cdk_proofs?;
  226. let melted = self.inner.melt_proofs(&quote_id, cdk_proofs).await?;
  227. Ok(melted.into())
  228. }
  229. /// Get a quote for a bolt12 mint
  230. pub async fn mint_bolt12_quote(
  231. &self,
  232. amount: Option<Amount>,
  233. description: Option<String>,
  234. ) -> Result<MintQuote, FfiError> {
  235. let quote = self
  236. .inner
  237. .mint_bolt12_quote(amount.map(Into::into), description)
  238. .await?;
  239. Ok(quote.into())
  240. }
  241. /// Get a mint quote using a unified interface for any payment method
  242. ///
  243. /// This method supports bolt11, bolt12, and custom payment methods.
  244. /// For custom methods, you can pass extra JSON data that will be forwarded
  245. /// to the payment processor.
  246. ///
  247. /// # Arguments
  248. /// * `amount` - Optional amount to mint (required for bolt11)
  249. /// * `method` - Payment method to use (bolt11, bolt12, or custom)
  250. /// * `description` - Optional description for the quote
  251. /// * `extra` - Optional JSON string with extra payment-method-specific fields (for custom methods)
  252. pub async fn mint_quote_unified(
  253. &self,
  254. amount: Option<Amount>,
  255. method: PaymentMethod,
  256. description: Option<String>,
  257. extra: Option<String>,
  258. ) -> Result<MintQuote, FfiError> {
  259. let quote = self
  260. .inner
  261. .mint_quote_unified(amount.map(Into::into), method.into(), description, extra)
  262. .await?;
  263. Ok(quote.into())
  264. }
  265. /// Mint tokens using bolt12
  266. pub async fn mint_bolt12(
  267. &self,
  268. quote_id: String,
  269. amount: Option<Amount>,
  270. amount_split_target: SplitTarget,
  271. spending_conditions: Option<SpendingConditions>,
  272. ) -> Result<Proofs, FfiError> {
  273. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  274. let proofs = self
  275. .inner
  276. .mint_bolt12(
  277. &quote_id,
  278. amount.map(Into::into),
  279. amount_split_target.into(),
  280. conditions,
  281. )
  282. .await?;
  283. Ok(proofs.into_iter().map(|p| p.into()).collect())
  284. }
  285. pub async fn mint_unified(
  286. &self,
  287. quote_id: String,
  288. amount: Option<Amount>,
  289. amount_split_target: SplitTarget,
  290. spending_conditions: Option<SpendingConditions>,
  291. ) -> Result<Proofs, FfiError> {
  292. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  293. let proofs = self
  294. .inner
  295. .mint_unified(
  296. &quote_id,
  297. amount.map(Into::into),
  298. amount_split_target.into(),
  299. conditions,
  300. )
  301. .await?;
  302. Ok(proofs.into_iter().map(|p| p.into()).collect())
  303. }
  304. /// Get a quote for a bolt12 melt
  305. pub async fn melt_bolt12_quote(
  306. &self,
  307. request: String,
  308. options: Option<MeltOptions>,
  309. ) -> Result<MeltQuote, FfiError> {
  310. let cdk_options = options.map(Into::into);
  311. let quote = self.inner.melt_bolt12_quote(request, cdk_options).await?;
  312. Ok(quote.into())
  313. }
  314. /// Get a melt quote using a unified interface for any payment method
  315. ///
  316. /// This method supports bolt11, bolt12, and custom payment methods.
  317. /// For custom methods, you can pass extra JSON data that will be forwarded
  318. /// to the payment processor.
  319. ///
  320. /// # Arguments
  321. /// * `method` - Payment method to use (bolt11, bolt12, or custom)
  322. /// * `request` - Payment request string (invoice, offer, or custom format)
  323. /// * `options` - Optional melt options (MPP, amountless, etc.)
  324. /// * `extra` - Optional JSON string with extra payment-method-specific fields (for custom methods)
  325. pub async fn melt_quote_unified(
  326. &self,
  327. method: PaymentMethod,
  328. request: String,
  329. options: Option<MeltOptions>,
  330. extra: Option<String>,
  331. ) -> Result<MeltQuote, FfiError> {
  332. // Parse the extra JSON string into a serde_json::Value
  333. let extra_value = extra
  334. .map(|s| serde_json::from_str(&s))
  335. .transpose()
  336. .map_err(|e| FfiError::internal(format!("Invalid extra JSON: {}", e)))?;
  337. let cdk_options = options.map(Into::into);
  338. let quote = self
  339. .inner
  340. .melt_quote_unified(method.into(), request, cdk_options, extra_value)
  341. .await?;
  342. Ok(quote.into())
  343. }
  344. /// Swap proofs
  345. pub async fn swap(
  346. &self,
  347. amount: Option<Amount>,
  348. amount_split_target: SplitTarget,
  349. input_proofs: Proofs,
  350. spending_conditions: Option<SpendingConditions>,
  351. include_fees: bool,
  352. ) -> Result<Option<Proofs>, FfiError> {
  353. let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
  354. input_proofs.into_iter().map(|p| p.try_into()).collect();
  355. let cdk_proofs = cdk_proofs?;
  356. // Convert spending conditions if provided
  357. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  358. let result = self
  359. .inner
  360. .swap(
  361. amount.map(Into::into),
  362. amount_split_target.into(),
  363. cdk_proofs,
  364. conditions,
  365. include_fees,
  366. )
  367. .await?;
  368. Ok(result.map(|proofs| proofs.into_iter().map(|p| p.into()).collect()))
  369. }
  370. /// Get proofs by states
  371. pub async fn get_proofs_by_states(&self, states: Vec<ProofState>) -> Result<Proofs, FfiError> {
  372. let mut all_proofs = Vec::new();
  373. for state in states {
  374. let proofs = match state {
  375. ProofState::Unspent => self.inner.get_unspent_proofs().await?,
  376. ProofState::Pending => self.inner.get_pending_proofs().await?,
  377. ProofState::Reserved => self.inner.get_reserved_proofs().await?,
  378. ProofState::PendingSpent => self.inner.get_pending_spent_proofs().await?,
  379. ProofState::Spent => {
  380. // CDK doesn't have a method to get spent proofs directly
  381. // They are removed from the database when spent
  382. continue;
  383. }
  384. };
  385. for proof in proofs {
  386. all_proofs.push(proof.into());
  387. }
  388. }
  389. Ok(all_proofs)
  390. }
  391. /// Check if proofs are spent
  392. pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<bool>, FfiError> {
  393. let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
  394. proofs.into_iter().map(|p| p.try_into()).collect();
  395. let cdk_proofs = cdk_proofs?;
  396. let proof_states = self.inner.check_proofs_spent(cdk_proofs).await?;
  397. // Convert ProofState to bool (spent = true, unspent = false)
  398. let spent_bools = proof_states
  399. .into_iter()
  400. .map(|proof_state| {
  401. matches!(
  402. proof_state.state,
  403. cdk::nuts::State::Spent | cdk::nuts::State::PendingSpent
  404. )
  405. })
  406. .collect();
  407. Ok(spent_bools)
  408. }
  409. /// List transactions
  410. pub async fn list_transactions(
  411. &self,
  412. direction: Option<TransactionDirection>,
  413. ) -> Result<Vec<Transaction>, FfiError> {
  414. let cdk_direction = direction.map(Into::into);
  415. let transactions = self.inner.list_transactions(cdk_direction).await?;
  416. Ok(transactions.into_iter().map(Into::into).collect())
  417. }
  418. /// Get transaction by ID
  419. pub async fn get_transaction(
  420. &self,
  421. id: TransactionId,
  422. ) -> Result<Option<Transaction>, FfiError> {
  423. let cdk_id = id.try_into()?;
  424. let transaction = self.inner.get_transaction(cdk_id).await?;
  425. Ok(transaction.map(Into::into))
  426. }
  427. /// Get proofs for a transaction by transaction ID
  428. ///
  429. /// This retrieves all proofs associated with a transaction by looking up
  430. /// the transaction's Y values and fetching the corresponding proofs.
  431. pub async fn get_proofs_for_transaction(
  432. &self,
  433. id: TransactionId,
  434. ) -> Result<Vec<Proof>, FfiError> {
  435. let cdk_id = id.try_into()?;
  436. let proofs = self.inner.get_proofs_for_transaction(cdk_id).await?;
  437. Ok(proofs.into_iter().map(Into::into).collect())
  438. }
  439. /// Revert a transaction
  440. pub async fn revert_transaction(&self, id: TransactionId) -> Result<(), FfiError> {
  441. let cdk_id = id.try_into()?;
  442. self.inner.revert_transaction(cdk_id).await?;
  443. Ok(())
  444. }
  445. /// Subscribe to wallet events
  446. pub async fn subscribe(
  447. &self,
  448. params: SubscribeParams,
  449. ) -> Result<std::sync::Arc<ActiveSubscription>, FfiError> {
  450. let cdk_params: cdk::nuts::nut17::Params<Arc<String>> = params.clone().into();
  451. let sub_id = cdk_params.id.to_string();
  452. let active_sub = self.inner.subscribe(cdk_params).await?;
  453. Ok(std::sync::Arc::new(ActiveSubscription::new(
  454. active_sub, sub_id,
  455. )))
  456. }
  457. /// Refresh keysets from the mint
  458. pub async fn refresh_keysets(&self) -> Result<Vec<KeySetInfo>, FfiError> {
  459. let keysets = self.inner.refresh_keysets().await?;
  460. Ok(keysets.into_iter().map(Into::into).collect())
  461. }
  462. /// Get the active keyset for the wallet's unit
  463. pub async fn get_active_keyset(&self) -> Result<KeySetInfo, FfiError> {
  464. let keyset = self.inner.get_active_keyset().await?;
  465. Ok(keyset.into())
  466. }
  467. /// Get fees for a specific keyset ID
  468. pub async fn get_keyset_fees_by_id(&self, keyset_id: String) -> Result<u64, FfiError> {
  469. let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
  470. Ok(self
  471. .inner
  472. .get_keyset_fees_and_amounts_by_id(id)
  473. .await?
  474. .fee())
  475. }
  476. /// Reclaim unspent proofs (mark them as unspent in the database)
  477. pub async fn reclaim_unspent(&self, proofs: Proofs) -> Result<(), FfiError> {
  478. let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
  479. proofs.iter().map(|p| p.clone().try_into()).collect();
  480. let cdk_proofs = cdk_proofs?;
  481. self.inner.reclaim_unspent(cdk_proofs).await?;
  482. Ok(())
  483. }
  484. /// Check all pending proofs and return the total amount reclaimed
  485. pub async fn check_all_pending_proofs(&self) -> Result<Amount, FfiError> {
  486. let amount = self.inner.check_all_pending_proofs().await?;
  487. Ok(amount.into())
  488. }
  489. /// Calculate fee for a given number of proofs with the specified keyset
  490. pub async fn calculate_fee(
  491. &self,
  492. proof_count: u32,
  493. keyset_id: String,
  494. ) -> Result<Amount, FfiError> {
  495. let id = cdk::nuts::Id::from_str(&keyset_id).map_err(FfiError::internal)?;
  496. let fee = self
  497. .inner
  498. .get_keyset_count_fee(&id, proof_count as u64)
  499. .await?;
  500. Ok(fee.into())
  501. }
  502. /// Pay a NUT-18 payment request
  503. ///
  504. /// This method prepares and sends a payment for the given payment request.
  505. /// It will use the Nostr or HTTP transport specified in the request.
  506. ///
  507. /// # Arguments
  508. ///
  509. /// * `payment_request` - The NUT-18 payment request to pay
  510. /// * `custom_amount` - Optional amount to pay (required if request has no amount)
  511. pub async fn pay_request(
  512. &self,
  513. payment_request: std::sync::Arc<PaymentRequest>,
  514. custom_amount: Option<Amount>,
  515. ) -> Result<(), FfiError> {
  516. self.inner
  517. .pay_request(
  518. payment_request.inner().clone(),
  519. custom_amount.map(Into::into),
  520. )
  521. .await?;
  522. Ok(())
  523. }
  524. }
  525. /// BIP353 methods for Wallet
  526. #[cfg(not(target_arch = "wasm32"))]
  527. #[uniffi::export(async_runtime = "tokio")]
  528. impl Wallet {
  529. /// Get a quote for a BIP353 melt
  530. ///
  531. /// This method resolves a BIP353 address (e.g., "alice@example.com") to a Lightning offer
  532. /// and then creates a melt quote for that offer.
  533. pub async fn melt_bip353_quote(
  534. &self,
  535. bip353_address: String,
  536. amount_msat: Amount,
  537. ) -> Result<MeltQuote, FfiError> {
  538. let cdk_amount: cdk::Amount = amount_msat.into();
  539. let quote = self
  540. .inner
  541. .melt_bip353_quote(&bip353_address, cdk_amount)
  542. .await?;
  543. Ok(quote.into())
  544. }
  545. /// Get a quote for a Lightning address melt
  546. ///
  547. /// This method resolves a Lightning address (e.g., "alice@example.com") to a Lightning invoice
  548. /// and then creates a melt quote for that invoice.
  549. pub async fn melt_lightning_address_quote(
  550. &self,
  551. lightning_address: String,
  552. amount_msat: Amount,
  553. ) -> Result<MeltQuote, FfiError> {
  554. let cdk_amount: cdk::Amount = amount_msat.into();
  555. let quote = self
  556. .inner
  557. .melt_lightning_address_quote(&lightning_address, cdk_amount)
  558. .await?;
  559. Ok(quote.into())
  560. }
  561. /// Get a quote for a human-readable address melt
  562. ///
  563. /// This method accepts a human-readable address that could be either a BIP353 address
  564. /// or a Lightning address. It intelligently determines which to try based on mint support:
  565. ///
  566. /// 1. If the mint supports Bolt12, it tries BIP353 first
  567. /// 2. Falls back to Lightning address only if BIP353 DNS resolution fails
  568. /// 3. If BIP353 resolves but fails at the mint, it does NOT fall back to Lightning address
  569. /// 4. If the mint doesn't support Bolt12, it tries Lightning address directly
  570. pub async fn melt_human_readable(
  571. &self,
  572. address: String,
  573. amount_msat: Amount,
  574. ) -> Result<MeltQuote, FfiError> {
  575. let cdk_amount: cdk::Amount = amount_msat.into();
  576. let quote = self
  577. .inner
  578. .melt_human_readable_quote(&address, cdk_amount)
  579. .await?;
  580. Ok(quote.into())
  581. }
  582. }
  583. /// Auth methods for Wallet
  584. #[uniffi::export(async_runtime = "tokio")]
  585. impl Wallet {
  586. /// Set Clear Auth Token (CAT) for authentication
  587. pub async fn set_cat(&self, cat: String) -> Result<(), FfiError> {
  588. self.inner.set_cat(cat).await?;
  589. Ok(())
  590. }
  591. /// Set refresh token for authentication
  592. pub async fn set_refresh_token(&self, refresh_token: String) -> Result<(), FfiError> {
  593. self.inner.set_refresh_token(refresh_token).await?;
  594. Ok(())
  595. }
  596. /// Refresh access token using the stored refresh token
  597. pub async fn refresh_access_token(&self) -> Result<(), FfiError> {
  598. self.inner.refresh_access_token().await?;
  599. Ok(())
  600. }
  601. /// Mint blind auth tokens
  602. pub async fn mint_blind_auth(&self, amount: Amount) -> Result<Proofs, FfiError> {
  603. let proofs = self.inner.mint_blind_auth(amount.into()).await?;
  604. Ok(proofs.into_iter().map(|p| p.into()).collect())
  605. }
  606. /// Get unspent auth proofs
  607. pub async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, FfiError> {
  608. let auth_proofs = self.inner.get_unspent_auth_proofs().await?;
  609. Ok(auth_proofs.into_iter().map(Into::into).collect())
  610. }
  611. }
  612. /// Configuration for creating wallets
  613. #[derive(Debug, Clone, uniffi::Record)]
  614. pub struct WalletConfig {
  615. pub target_proof_count: Option<u32>,
  616. }
  617. /// Generates a new random mnemonic phrase
  618. #[uniffi::export]
  619. pub fn generate_mnemonic() -> Result<String, FfiError> {
  620. let mnemonic = Mnemonic::generate(12)
  621. .map_err(|e| FfiError::internal(format!("Failed to generate mnemonic: {}", e)))?;
  622. Ok(mnemonic.to_string())
  623. }
  624. /// Converts a mnemonic phrase to its entropy bytes
  625. #[uniffi::export]
  626. pub fn mnemonic_to_entropy(mnemonic: String) -> Result<Vec<u8>, FfiError> {
  627. let m = Mnemonic::parse(&mnemonic)
  628. .map_err(|e| FfiError::internal(format!("Invalid mnemonic: {}", e)))?;
  629. Ok(m.to_entropy())
  630. }