wallet.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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::types::*;
  8. /// FFI-compatible Wallet
  9. #[derive(uniffi::Object)]
  10. pub struct Wallet {
  11. inner: Arc<CdkWallet>,
  12. }
  13. #[uniffi::export(async_runtime = "tokio")]
  14. impl Wallet {
  15. /// Create a new Wallet from mnemonic using WalletDatabase trait
  16. #[uniffi::constructor]
  17. pub async fn new(
  18. mint_url: String,
  19. unit: CurrencyUnit,
  20. mnemonic: String,
  21. db: Arc<dyn crate::database::WalletDatabase>,
  22. config: WalletConfig,
  23. ) -> Result<Self, FfiError> {
  24. // Parse mnemonic and generate seed without passphrase
  25. let m = Mnemonic::parse(&mnemonic)
  26. .map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
  27. let seed = m.to_seed_normalized("");
  28. // Convert the FFI database trait to a CDK database implementation
  29. let localstore = crate::database::create_cdk_database_from_ffi(db);
  30. let wallet =
  31. CdkWalletBuilder::new()
  32. .mint_url(mint_url.parse().map_err(|e: cdk::mint_url::Error| {
  33. FfiError::InvalidUrl { msg: e.to_string() }
  34. })?)
  35. .unit(unit.into())
  36. .localstore(localstore)
  37. .seed(seed)
  38. .target_proof_count(config.target_proof_count.unwrap_or(3) as usize)
  39. .build()
  40. .map_err(FfiError::from)?;
  41. Ok(Self {
  42. inner: Arc::new(wallet),
  43. })
  44. }
  45. /// Get the mint URL
  46. pub fn mint_url(&self) -> MintUrl {
  47. self.inner.mint_url.clone().into()
  48. }
  49. /// Get the currency unit
  50. pub fn unit(&self) -> CurrencyUnit {
  51. self.inner.unit.clone().into()
  52. }
  53. /// Get total balance
  54. pub async fn total_balance(&self) -> Result<Amount, FfiError> {
  55. let balance = self.inner.total_balance().await?;
  56. Ok(balance.into())
  57. }
  58. /// Get total pending balance
  59. pub async fn total_pending_balance(&self) -> Result<Amount, FfiError> {
  60. let balance = self.inner.total_pending_balance().await?;
  61. Ok(balance.into())
  62. }
  63. /// Get total reserved balance
  64. pub async fn total_reserved_balance(&self) -> Result<Amount, FfiError> {
  65. let balance = self.inner.total_reserved_balance().await?;
  66. Ok(balance.into())
  67. }
  68. /// Get mint info
  69. pub async fn get_mint_info(&self) -> Result<Option<MintInfo>, FfiError> {
  70. let info = self.inner.fetch_mint_info().await?;
  71. Ok(info.map(Into::into))
  72. }
  73. /// Receive tokens
  74. pub async fn receive(
  75. &self,
  76. token: std::sync::Arc<Token>,
  77. options: ReceiveOptions,
  78. ) -> Result<Amount, FfiError> {
  79. let amount = self
  80. .inner
  81. .receive(&token.to_string(), options.into())
  82. .await?;
  83. Ok(amount.into())
  84. }
  85. /// Restore wallet from seed
  86. pub async fn restore(&self) -> Result<Amount, FfiError> {
  87. let amount = self.inner.restore().await?;
  88. Ok(amount.into())
  89. }
  90. /// Verify token DLEQ proofs
  91. pub async fn verify_token_dleq(&self, token: std::sync::Arc<Token>) -> Result<(), FfiError> {
  92. let cdk_token = token.inner.clone();
  93. self.inner.verify_token_dleq(&cdk_token).await?;
  94. Ok(())
  95. }
  96. /// Receive proofs directly
  97. pub async fn receive_proofs(
  98. &self,
  99. proofs: Proofs,
  100. options: ReceiveOptions,
  101. memo: Option<String>,
  102. ) -> Result<Amount, FfiError> {
  103. let cdk_proofs: Vec<cdk::nuts::Proof> =
  104. proofs.into_iter().map(|p| p.inner.clone()).collect();
  105. let amount = self
  106. .inner
  107. .receive_proofs(cdk_proofs, options.into(), memo)
  108. .await?;
  109. Ok(amount.into())
  110. }
  111. /// Prepare a send operation
  112. pub async fn prepare_send(
  113. &self,
  114. amount: Amount,
  115. options: SendOptions,
  116. ) -> Result<std::sync::Arc<PreparedSend>, FfiError> {
  117. let prepared = self
  118. .inner
  119. .prepare_send(amount.into(), options.into())
  120. .await?;
  121. Ok(std::sync::Arc::new(prepared.into()))
  122. }
  123. /// Get a mint quote
  124. pub async fn mint_quote(
  125. &self,
  126. amount: Amount,
  127. description: Option<String>,
  128. ) -> Result<MintQuote, FfiError> {
  129. let quote = self.inner.mint_quote(amount.into(), description).await?;
  130. Ok(quote.into())
  131. }
  132. /// Mint tokens
  133. pub async fn mint(
  134. &self,
  135. quote_id: String,
  136. amount_split_target: SplitTarget,
  137. spending_conditions: Option<SpendingConditions>,
  138. ) -> Result<Proofs, FfiError> {
  139. // Convert spending conditions if provided
  140. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  141. let proofs = self
  142. .inner
  143. .mint(&quote_id, amount_split_target.into(), conditions)
  144. .await?;
  145. Ok(proofs
  146. .into_iter()
  147. .map(|p| std::sync::Arc::new(p.into()))
  148. .collect())
  149. }
  150. /// Get a melt quote
  151. pub async fn melt_quote(
  152. &self,
  153. request: String,
  154. options: Option<MeltOptions>,
  155. ) -> Result<MeltQuote, FfiError> {
  156. let cdk_options = options.map(Into::into);
  157. let quote = self.inner.melt_quote(request, cdk_options).await?;
  158. Ok(quote.into())
  159. }
  160. /// Melt tokens
  161. pub async fn melt(&self, quote_id: String) -> Result<Melted, FfiError> {
  162. let melted = self.inner.melt(&quote_id).await?;
  163. Ok(melted.into())
  164. }
  165. /// Get a quote for a bolt12 mint
  166. pub async fn mint_bolt12_quote(
  167. &self,
  168. amount: Option<Amount>,
  169. description: Option<String>,
  170. ) -> Result<MintQuote, FfiError> {
  171. let quote = self
  172. .inner
  173. .mint_bolt12_quote(amount.map(Into::into), description)
  174. .await?;
  175. Ok(quote.into())
  176. }
  177. /// Mint tokens using bolt12
  178. pub async fn mint_bolt12(
  179. &self,
  180. quote_id: String,
  181. amount: Option<Amount>,
  182. amount_split_target: SplitTarget,
  183. spending_conditions: Option<SpendingConditions>,
  184. ) -> Result<Proofs, FfiError> {
  185. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  186. let proofs = self
  187. .inner
  188. .mint_bolt12(
  189. &quote_id,
  190. amount.map(Into::into),
  191. amount_split_target.into(),
  192. conditions,
  193. )
  194. .await?;
  195. Ok(proofs
  196. .into_iter()
  197. .map(|p| std::sync::Arc::new(p.into()))
  198. .collect())
  199. }
  200. /// Get a quote for a bolt12 melt
  201. pub async fn melt_bolt12_quote(
  202. &self,
  203. request: String,
  204. options: Option<MeltOptions>,
  205. ) -> Result<MeltQuote, FfiError> {
  206. let cdk_options = options.map(Into::into);
  207. let quote = self.inner.melt_bolt12_quote(request, cdk_options).await?;
  208. Ok(quote.into())
  209. }
  210. /// Swap proofs
  211. pub async fn swap(
  212. &self,
  213. amount: Option<Amount>,
  214. amount_split_target: SplitTarget,
  215. input_proofs: Proofs,
  216. spending_conditions: Option<SpendingConditions>,
  217. include_fees: bool,
  218. ) -> Result<Option<Proofs>, FfiError> {
  219. let cdk_proofs: Vec<cdk::nuts::Proof> =
  220. input_proofs.into_iter().map(|p| p.inner.clone()).collect();
  221. // Convert spending conditions if provided
  222. let conditions = spending_conditions.map(|sc| sc.try_into()).transpose()?;
  223. let result = self
  224. .inner
  225. .swap(
  226. amount.map(Into::into),
  227. amount_split_target.into(),
  228. cdk_proofs,
  229. conditions,
  230. include_fees,
  231. )
  232. .await?;
  233. Ok(result.map(|proofs| {
  234. proofs
  235. .into_iter()
  236. .map(|p| std::sync::Arc::new(p.into()))
  237. .collect()
  238. }))
  239. }
  240. /// Get proofs by states
  241. pub async fn get_proofs_by_states(&self, states: Vec<ProofState>) -> Result<Proofs, FfiError> {
  242. let mut all_proofs = Vec::new();
  243. for state in states {
  244. let proofs = match state {
  245. ProofState::Unspent => self.inner.get_unspent_proofs().await?,
  246. ProofState::Pending => self.inner.get_pending_proofs().await?,
  247. ProofState::Reserved => self.inner.get_reserved_proofs().await?,
  248. ProofState::PendingSpent => self.inner.get_pending_spent_proofs().await?,
  249. ProofState::Spent => {
  250. // CDK doesn't have a method to get spent proofs directly
  251. // They are removed from the database when spent
  252. continue;
  253. }
  254. };
  255. for proof in proofs {
  256. all_proofs.push(std::sync::Arc::new(proof.into()));
  257. }
  258. }
  259. Ok(all_proofs)
  260. }
  261. /// Check if proofs are spent
  262. pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result<Vec<bool>, FfiError> {
  263. let cdk_proofs: Vec<cdk::nuts::Proof> =
  264. proofs.into_iter().map(|p| p.inner.clone()).collect();
  265. let proof_states = self.inner.check_proofs_spent(cdk_proofs).await?;
  266. // Convert ProofState to bool (spent = true, unspent = false)
  267. let spent_bools = proof_states
  268. .into_iter()
  269. .map(|proof_state| {
  270. matches!(
  271. proof_state.state,
  272. cdk::nuts::State::Spent | cdk::nuts::State::PendingSpent
  273. )
  274. })
  275. .collect();
  276. Ok(spent_bools)
  277. }
  278. /// List transactions
  279. pub async fn list_transactions(
  280. &self,
  281. direction: Option<TransactionDirection>,
  282. ) -> Result<Vec<Transaction>, FfiError> {
  283. let cdk_direction = direction.map(Into::into);
  284. let transactions = self.inner.list_transactions(cdk_direction).await?;
  285. Ok(transactions.into_iter().map(Into::into).collect())
  286. }
  287. /// Get transaction by ID
  288. pub async fn get_transaction(
  289. &self,
  290. id: TransactionId,
  291. ) -> Result<Option<Transaction>, FfiError> {
  292. let cdk_id = id.try_into()?;
  293. let transaction = self.inner.get_transaction(cdk_id).await?;
  294. Ok(transaction.map(Into::into))
  295. }
  296. /// Revert a transaction
  297. pub async fn revert_transaction(&self, id: TransactionId) -> Result<(), FfiError> {
  298. let cdk_id = id.try_into()?;
  299. self.inner.revert_transaction(cdk_id).await?;
  300. Ok(())
  301. }
  302. /// Subscribe to wallet events
  303. pub async fn subscribe(
  304. &self,
  305. params: SubscribeParams,
  306. ) -> Result<std::sync::Arc<ActiveSubscription>, FfiError> {
  307. let cdk_params: cdk_common::subscription::Params = params.clone().into();
  308. let sub_id = cdk_params.id.to_string();
  309. let active_sub = self.inner.subscribe(cdk_params).await;
  310. Ok(std::sync::Arc::new(ActiveSubscription::new(
  311. active_sub, sub_id,
  312. )))
  313. }
  314. /// Refresh keysets from the mint
  315. pub async fn refresh_keysets(&self) -> Result<Vec<KeySetInfo>, FfiError> {
  316. let keysets = self.inner.refresh_keysets().await?;
  317. Ok(keysets.into_iter().map(Into::into).collect())
  318. }
  319. /// Get the active keyset for the wallet's unit
  320. pub async fn get_active_keyset(&self) -> Result<KeySetInfo, FfiError> {
  321. let keyset = self.inner.get_active_keyset().await?;
  322. Ok(keyset.into())
  323. }
  324. /// Get fees for a specific keyset ID
  325. pub async fn get_keyset_fees_by_id(&self, keyset_id: String) -> Result<u64, FfiError> {
  326. let id = cdk::nuts::Id::from_str(&keyset_id)
  327. .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
  328. let fees = self.inner.get_keyset_fees_by_id(id).await?;
  329. Ok(fees)
  330. }
  331. /// Reclaim unspent proofs (mark them as unspent in the database)
  332. pub async fn reclaim_unspent(&self, proofs: Proofs) -> Result<(), FfiError> {
  333. let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
  334. self.inner.reclaim_unspent(cdk_proofs).await?;
  335. Ok(())
  336. }
  337. /// Check all pending proofs and return the total amount reclaimed
  338. pub async fn check_all_pending_proofs(&self) -> Result<Amount, FfiError> {
  339. let amount = self.inner.check_all_pending_proofs().await?;
  340. Ok(amount.into())
  341. }
  342. /// Calculate fee for a given number of proofs with the specified keyset
  343. pub async fn calculate_fee(
  344. &self,
  345. proof_count: u32,
  346. keyset_id: String,
  347. ) -> Result<Amount, FfiError> {
  348. let id = cdk::nuts::Id::from_str(&keyset_id)
  349. .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
  350. let fee_ppk = self.inner.get_keyset_fees_by_id(id).await?;
  351. let total_fee = (proof_count as u64 * fee_ppk) / 1000; // fee is per thousand
  352. Ok(Amount::new(total_fee))
  353. }
  354. }
  355. /// BIP353 methods for Wallet
  356. #[cfg(feature = "bip353")]
  357. #[uniffi::export(async_runtime = "tokio")]
  358. impl Wallet {
  359. /// Get a quote for a BIP353 melt
  360. ///
  361. /// This method resolves a BIP353 address (e.g., "alice@example.com") to a Lightning offer
  362. /// and then creates a melt quote for that offer.
  363. pub async fn melt_bip353_quote(
  364. &self,
  365. bip353_address: String,
  366. amount_msat: Amount,
  367. ) -> Result<MeltQuote, FfiError> {
  368. let cdk_amount: cdk::Amount = amount_msat.into();
  369. let quote = self
  370. .inner
  371. .melt_bip353_quote(&bip353_address, cdk_amount)
  372. .await?;
  373. Ok(quote.into())
  374. }
  375. }
  376. /// Auth methods for Wallet
  377. #[cfg(feature = "auth")]
  378. #[uniffi::export(async_runtime = "tokio")]
  379. impl Wallet {
  380. /// Set Clear Auth Token (CAT) for authentication
  381. pub async fn set_cat(&self, cat: String) -> Result<(), FfiError> {
  382. self.inner.set_cat(cat).await?;
  383. Ok(())
  384. }
  385. /// Set refresh token for authentication
  386. pub async fn set_refresh_token(&self, refresh_token: String) -> Result<(), FfiError> {
  387. self.inner.set_refresh_token(refresh_token).await?;
  388. Ok(())
  389. }
  390. /// Refresh access token using the stored refresh token
  391. pub async fn refresh_access_token(&self) -> Result<(), FfiError> {
  392. self.inner.refresh_access_token().await?;
  393. Ok(())
  394. }
  395. /// Mint blind auth tokens
  396. pub async fn mint_blind_auth(&self, amount: Amount) -> Result<Proofs, FfiError> {
  397. let proofs = self.inner.mint_blind_auth(amount.into()).await?;
  398. Ok(proofs
  399. .into_iter()
  400. .map(|p| std::sync::Arc::new(p.into()))
  401. .collect())
  402. }
  403. /// Get unspent auth proofs
  404. pub async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, FfiError> {
  405. let auth_proofs = self.inner.get_unspent_auth_proofs().await?;
  406. Ok(auth_proofs.into_iter().map(Into::into).collect())
  407. }
  408. }
  409. /// Configuration for creating wallets
  410. #[derive(Debug, Clone, uniffi::Record)]
  411. pub struct WalletConfig {
  412. pub target_proof_count: Option<u32>,
  413. }
  414. /// Generates a new random mnemonic phrase
  415. #[uniffi::export]
  416. pub fn generate_mnemonic() -> Result<String, FfiError> {
  417. let mnemonic =
  418. Mnemonic::generate(12).map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
  419. Ok(mnemonic.to_string())
  420. }
  421. /// Converts a mnemonic phrase to its entropy bytes
  422. #[uniffi::export]
  423. pub fn mnemonic_to_entropy(mnemonic: String) -> Result<Vec<u8>, FfiError> {
  424. let m =
  425. Mnemonic::parse(&mnemonic).map_err(|e| FfiError::InvalidMnemonic { msg: e.to_string() })?;
  426. Ok(m.to_entropy())
  427. }