wallet.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. use std::ops::Deref;
  2. use std::str::FromStr;
  3. use std::sync::Arc;
  4. use cdk::amount::SplitTarget;
  5. use cdk::nuts::{Proofs, SecretKey};
  6. use cdk::url::UncheckedUrl;
  7. use cdk::wallet::Wallet;
  8. use cdk::Amount;
  9. use cdk_rexie::RexieWalletDatabase;
  10. use wasm_bindgen::prelude::*;
  11. use crate::error::{into_err, Result};
  12. use crate::nuts::nut04::JsMintQuoteBolt11Response;
  13. use crate::nuts::nut05::JsMeltQuoteBolt11Response;
  14. use crate::nuts::nut11::JsP2PKSpendingConditions;
  15. use crate::nuts::nut14::JsHTLCSpendingConditions;
  16. use crate::nuts::{JsCurrencyUnit, JsMintInfo, JsProof};
  17. use crate::types::melt_quote::JsMeltQuote;
  18. use crate::types::{JsAmount, JsMelted, JsMintQuote};
  19. #[wasm_bindgen(js_name = Wallet)]
  20. pub struct JsWallet {
  21. inner: Wallet,
  22. }
  23. impl Deref for JsWallet {
  24. type Target = Wallet;
  25. fn deref(&self) -> &Self::Target {
  26. &self.inner
  27. }
  28. }
  29. impl From<Wallet> for JsWallet {
  30. fn from(inner: Wallet) -> JsWallet {
  31. JsWallet { inner }
  32. }
  33. }
  34. #[wasm_bindgen(js_class = Wallet)]
  35. impl JsWallet {
  36. #[wasm_bindgen(constructor)]
  37. pub async fn new(seed: Vec<u8>) -> Self {
  38. let db = RexieWalletDatabase::new().await.unwrap();
  39. Wallet::new(Arc::new(db), &seed).into()
  40. }
  41. #[wasm_bindgen(js_name = unitBalance)]
  42. pub async fn unit_balance(&self, unit: JsCurrencyUnit) -> Result<JsAmount> {
  43. Ok(self
  44. .inner
  45. .unit_balance(unit.into())
  46. .await
  47. .map_err(into_err)?
  48. .into())
  49. }
  50. #[wasm_bindgen(js_name = pendingUnitBalance)]
  51. pub async fn pending_unit_balance(&self, unit: JsCurrencyUnit) -> Result<JsAmount> {
  52. Ok(self
  53. .inner
  54. .pending_unit_balance(unit.into())
  55. .await
  56. .map_err(into_err)?
  57. .into())
  58. }
  59. #[wasm_bindgen(js_name = totalBalance)]
  60. pub async fn total_balance(&self) -> Result<JsValue> {
  61. Ok(serde_wasm_bindgen::to_value(
  62. &self.inner.total_balance().await.map_err(into_err)?,
  63. )?)
  64. }
  65. #[wasm_bindgen(js_name = totalPendingBalance)]
  66. pub async fn total_pending_balance(&self) -> Result<JsValue> {
  67. Ok(serde_wasm_bindgen::to_value(
  68. &self.inner.total_pending_balance().await.map_err(into_err)?,
  69. )?)
  70. }
  71. #[wasm_bindgen(js_name = checkAllPendingProofs)]
  72. pub async fn check_all_pending_proofs(&self, mint_url: Option<String>) -> Result<JsAmount> {
  73. let mint_url = match mint_url {
  74. Some(url) => Some(UncheckedUrl::from_str(&url).map_err(into_err)?),
  75. None => None,
  76. };
  77. Ok(self
  78. .inner
  79. .check_all_pending_proofs(mint_url)
  80. .await
  81. .map_err(into_err)?
  82. .into())
  83. }
  84. #[wasm_bindgen(js_name = mintBalances)]
  85. pub async fn mint_balances(&self) -> Result<JsValue> {
  86. let mint_balances = self.inner.mint_balances().await.map_err(into_err)?;
  87. Ok(serde_wasm_bindgen::to_value(&mint_balances)?)
  88. }
  89. #[wasm_bindgen(js_name = addMint)]
  90. pub async fn add_mint(&self, mint_url: String) -> Result<Option<JsMintInfo>> {
  91. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  92. Ok(self
  93. .inner
  94. .add_mint(mint_url)
  95. .await
  96. .map_err(into_err)?
  97. .map(|i| i.into()))
  98. }
  99. #[wasm_bindgen(js_name = refreshMint)]
  100. pub async fn refresh_mint_keys(&self, mint_url: String) -> Result<()> {
  101. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  102. self.inner
  103. .refresh_mint_keys(&mint_url)
  104. .await
  105. .map_err(into_err)?;
  106. Ok(())
  107. }
  108. #[wasm_bindgen(js_name = mintQuote)]
  109. pub async fn mint_quote(
  110. &mut self,
  111. mint_url: String,
  112. amount: u64,
  113. unit: JsCurrencyUnit,
  114. ) -> Result<JsMintQuote> {
  115. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  116. let quote = self
  117. .inner
  118. .mint_quote(mint_url, amount.into(), unit.into())
  119. .await
  120. .map_err(into_err)?;
  121. Ok(quote.into())
  122. }
  123. #[wasm_bindgen(js_name = mintQuoteStatus)]
  124. pub async fn mint_quote_status(
  125. &self,
  126. mint_url: String,
  127. quote_id: String,
  128. ) -> Result<JsMintQuoteBolt11Response> {
  129. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  130. let quote = self
  131. .inner
  132. .mint_quote_status(mint_url, &quote_id)
  133. .await
  134. .map_err(into_err)?;
  135. Ok(quote.into())
  136. }
  137. #[wasm_bindgen(js_name = checkAllMintQuotes)]
  138. pub async fn check_all_mint_quotes(&self) -> Result<JsAmount> {
  139. let amount = self.inner.check_all_mint_quotes().await.map_err(into_err)?;
  140. Ok(amount.into())
  141. }
  142. #[wasm_bindgen(js_name = mint)]
  143. pub async fn mint(
  144. &mut self,
  145. mint_url: String,
  146. quote_id: String,
  147. split_target_amount: Option<JsAmount>,
  148. ) -> Result<JsAmount> {
  149. let target = split_target_amount
  150. .map(|a| SplitTarget::Value(*a.deref()))
  151. .unwrap_or_default();
  152. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  153. Ok(self
  154. .inner
  155. .mint(mint_url, &quote_id, target)
  156. .await
  157. .map_err(into_err)?
  158. .into())
  159. }
  160. #[wasm_bindgen(js_name = meltQuote)]
  161. pub async fn melt_quote(
  162. &mut self,
  163. mint_url: String,
  164. unit: JsCurrencyUnit,
  165. request: String,
  166. ) -> Result<JsMeltQuote> {
  167. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  168. let melt_quote = self
  169. .inner
  170. .melt_quote(mint_url, unit.into(), request)
  171. .await
  172. .map_err(into_err)?;
  173. Ok(melt_quote.into())
  174. }
  175. #[wasm_bindgen(js_name = meltQuoteStatus)]
  176. pub async fn melt_quote_status(
  177. &self,
  178. mint_url: String,
  179. quote_id: String,
  180. ) -> Result<JsMeltQuoteBolt11Response> {
  181. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  182. let quote = self
  183. .inner
  184. .melt_quote_status(mint_url, &quote_id)
  185. .await
  186. .map_err(into_err)?;
  187. Ok(quote.into())
  188. }
  189. #[wasm_bindgen(js_name = melt)]
  190. pub async fn melt(
  191. &mut self,
  192. mint_url: String,
  193. quote_id: String,
  194. split_target_amount: Option<JsAmount>,
  195. ) -> Result<JsMelted> {
  196. let target = split_target_amount
  197. .map(|a| SplitTarget::Value(*a.deref()))
  198. .unwrap_or_default();
  199. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  200. let melted = self
  201. .inner
  202. .melt(&mint_url, &quote_id, target)
  203. .await
  204. .map_err(into_err)?;
  205. Ok(melted.into())
  206. }
  207. #[wasm_bindgen(js_name = receive)]
  208. pub async fn receive(
  209. &mut self,
  210. encoded_token: String,
  211. signing_keys: JsValue,
  212. preimages: JsValue,
  213. ) -> Result<JsAmount> {
  214. let signing_keys: Option<Vec<SecretKey>> = serde_wasm_bindgen::from_value(signing_keys)?;
  215. let preimages: Option<Vec<String>> = serde_wasm_bindgen::from_value(preimages)?;
  216. Ok(self
  217. .inner
  218. .receive(
  219. &encoded_token,
  220. &SplitTarget::default(),
  221. signing_keys,
  222. preimages,
  223. )
  224. .await
  225. .map_err(into_err)?
  226. .into())
  227. }
  228. #[allow(clippy::too_many_arguments)]
  229. #[wasm_bindgen(js_name = send)]
  230. pub async fn send(
  231. &mut self,
  232. mint_url: String,
  233. unit: JsCurrencyUnit,
  234. memo: Option<String>,
  235. amount: u64,
  236. p2pk_condition: Option<JsP2PKSpendingConditions>,
  237. htlc_condition: Option<JsHTLCSpendingConditions>,
  238. split_target_amount: Option<JsAmount>,
  239. ) -> Result<String> {
  240. let conditions = match (p2pk_condition, htlc_condition) {
  241. (Some(_), Some(_)) => {
  242. return Err(JsValue::from_str(
  243. "Cannot define both p2pk and htlc conditions",
  244. ));
  245. }
  246. (None, Some(htlc_condition)) => Some(htlc_condition.deref().clone()),
  247. (Some(p2pk_condition), None) => Some(p2pk_condition.deref().clone()),
  248. (None, None) => None,
  249. };
  250. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  251. let target = split_target_amount
  252. .map(|a| SplitTarget::Value(*a.deref()))
  253. .unwrap_or_default();
  254. self.inner
  255. .send(
  256. &mint_url,
  257. unit.into(),
  258. memo,
  259. Amount::from(amount),
  260. &target,
  261. conditions,
  262. )
  263. .await
  264. .map_err(into_err)
  265. }
  266. #[allow(clippy::too_many_arguments)]
  267. #[wasm_bindgen(js_name = swap)]
  268. pub async fn swap(
  269. &mut self,
  270. mint_url: String,
  271. unit: JsCurrencyUnit,
  272. amount: u64,
  273. input_proofs: Vec<JsProof>,
  274. p2pk_condition: Option<JsP2PKSpendingConditions>,
  275. htlc_condition: Option<JsHTLCSpendingConditions>,
  276. split_target_amount: Option<JsAmount>,
  277. ) -> Result<JsValue> {
  278. let conditions = match (p2pk_condition, htlc_condition) {
  279. (Some(_), Some(_)) => {
  280. return Err(JsValue::from_str(
  281. "Cannot define both p2pk and htlc conditions",
  282. ));
  283. }
  284. (None, Some(htlc_condition)) => Some(htlc_condition.deref().clone()),
  285. (Some(p2pk_condition), None) => Some(p2pk_condition.deref().clone()),
  286. (None, None) => None,
  287. };
  288. let mint_url = UncheckedUrl::from_str(&mint_url).map_err(into_err)?;
  289. let proofs: Proofs = input_proofs.iter().map(|p| p.deref()).cloned().collect();
  290. let target = split_target_amount
  291. .map(|a| SplitTarget::Value(*a.deref()))
  292. .unwrap_or_default();
  293. let post_swap_proofs = self
  294. .inner
  295. .swap(
  296. &mint_url,
  297. &unit.into(),
  298. Some(Amount::from(amount)),
  299. &target,
  300. proofs,
  301. conditions,
  302. )
  303. .await
  304. .map_err(into_err)?;
  305. Ok(serde_wasm_bindgen::to_value(&post_swap_proofs)?)
  306. }
  307. }