wallet.rs 11 KB

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