mod backup; mod error; mod event; mod logger; pub mod model; mod signer; mod utils; use std::path::PathBuf; use std::rc::Rc; use std::str::FromStr; use crate::event::{EventListener, WasmEventListener}; use crate::model::*; use anyhow::anyhow; use breez_sdk_liquid::bitcoin::bip32::{Fingerprint, Xpub}; use breez_sdk_liquid::elements::hex::ToHex; use breez_sdk_liquid::persist::Persister; use breez_sdk_liquid::sdk::{LiquidSdk, LiquidSdkBuilder}; use breez_sdk_liquid::wallet::LiquidOnchainWallet; use breez_sdk_liquid::PRODUCTION_BREEZSERVER_URL; use log::LevelFilter; use logger::{Logger, WasmLogger}; use signer::{Signer, WasmSigner}; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct BindingLiquidSdk { sdk: Rc, } #[wasm_bindgen(js_name = "connect")] pub async fn connect(req: ConnectRequest) -> WasmResult { let signer = Box::new(LiquidSdk::default_signer(&req.clone().into())?); connect_inner(req.config, signer).await } #[wasm_bindgen(js_name = "connectWithSigner")] pub async fn connect_with_signer( req: ConnectWithSignerRequest, signer: Signer, ) -> WasmResult { let signer: Box = Box::new(WasmSigner { signer }); connect_inner(req.config, signer).await } async fn connect_inner( config: Config, signer: Box, ) -> WasmResult { let config: breez_sdk_liquid::model::Config = config.into(); let signer = Rc::new(signer); let mut sdk_builder = LiquidSdkBuilder::new( config.clone(), PRODUCTION_BREEZSERVER_URL.to_string(), Rc::clone(&signer), )?; let fingerprint: Fingerprint = Xpub::decode( &signer .xpub() .map_err(|e| anyhow!("Failed to get xpub: {e}"))?, ) .map_err(|e| anyhow!(e.to_string()))? .identifier()[0..4] .try_into() .map_err(|e| anyhow!("Failed to get fingerprint: {e}"))?; let fingerprint = fingerprint.to_hex(); let wallet_dir = PathBuf::from_str(&config.get_wallet_dir(&config.working_dir, &fingerprint)?) .map_err(|e| anyhow!(e.to_string()))?; let maybe_backup_bytes = backup::load_backup(&wallet_dir).await.unwrap_or_else(|e| { log::error!("Failed to load backup: {:?}", e); None }); let persister = Rc::new(Persister::new_in_memory( &config.working_dir, config.network, config.sync_enabled(), config.asset_metadata.clone(), maybe_backup_bytes, )?); let onchain_wallet = Rc::new(LiquidOnchainWallet::new_in_memory( config.clone(), Rc::clone(&persister), signer, )?); sdk_builder.persister(persister.clone()); sdk_builder.onchain_wallet(onchain_wallet); let sdk = sdk_builder.build()?; sdk.start().await?; let (sender, receiver) = tokio::sync::mpsc::channel(20); let listener = backup::ForwardingEventListener::new(sender); sdk.add_event_listener(Box::new(listener)).await?; backup::start_backup_task(persister, receiver, wallet_dir); Ok(BindingLiquidSdk { sdk }) } #[wasm_bindgen(js_name = "defaultConfig")] pub fn default_config(network: LiquidNetwork, breez_api_key: Option) -> WasmResult { let config = match network { LiquidNetwork::Mainnet => breez_sdk_liquid::model::Config::mainnet_esplora(breez_api_key), LiquidNetwork::Testnet => breez_sdk_liquid::model::Config::testnet_esplora(breez_api_key), LiquidNetwork::Regtest => breez_sdk_liquid::model::Config::regtest_esplora(), }; Ok(config.into()) } #[wasm_bindgen(js_name = "parseInvoice")] pub fn parse_invoice(input: String) -> WasmResult { Ok(LiquidSdk::parse_invoice(&input)?.into()) } #[wasm_bindgen(js_name = "setLogger")] pub fn set_logger(logger: Logger) -> WasmResult<()> { crate::logger::WASM_LOGGER.set(Some(logger)); let wasm_logger = WasmLogger {}; Ok(log::set_boxed_logger(Box::new(wasm_logger)) .map(|_| log::set_max_level(LevelFilter::Trace)) .map_err(|_| anyhow!("Logger already created"))?) } #[wasm_bindgen] impl BindingLiquidSdk { #[wasm_bindgen(js_name = "getInfo")] pub async fn get_info(&self) -> WasmResult { Ok(self.sdk.get_info().await?.into()) } #[wasm_bindgen(js_name = "signMessage")] pub fn sign_message(&self, req: SignMessageRequest) -> WasmResult { Ok(self.sdk.sign_message(&req.into())?.into()) } #[wasm_bindgen(js_name = "checkMessage")] pub fn check_message(&self, req: CheckMessageRequest) -> WasmResult { Ok(self.sdk.check_message(&req.into())?.into()) } #[wasm_bindgen(js_name = "parse")] pub async fn parse(&self, input: String) -> WasmResult { Ok(self.sdk.parse(&input).await?.into()) } #[wasm_bindgen(js_name = "addEventListener")] pub async fn add_event_listener(&self, listener: EventListener) -> WasmResult { Ok(self .sdk .add_event_listener(Box::new(WasmEventListener { listener })) .await?) } #[wasm_bindgen(js_name = "removeEventListener")] pub async fn remove_event_listener(&self, id: String) -> WasmResult<()> { self.sdk.remove_event_listener(id).await?; Ok(()) } #[wasm_bindgen(js_name = "prepareSendPayment")] pub async fn prepare_send_payment( &self, req: PrepareSendRequest, ) -> WasmResult { Ok(self.sdk.prepare_send_payment(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "sendPayment")] pub async fn send_payment(&self, req: SendPaymentRequest) -> WasmResult { Ok(self.sdk.send_payment(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "prepareReceivePayment")] pub async fn prepare_receive_payment( &self, req: PrepareReceiveRequest, ) -> WasmResult { Ok(self.sdk.prepare_receive_payment(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "receivePayment")] pub async fn receive_payment( &self, req: ReceivePaymentRequest, ) -> WasmResult { Ok(self.sdk.receive_payment(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "fetchLightningLimits")] pub async fn fetch_lightning_limits(&self) -> WasmResult { Ok(self.sdk.fetch_lightning_limits().await?.into()) } #[wasm_bindgen(js_name = "fetchOnchainLimits")] pub async fn fetch_onchain_limits(&self) -> WasmResult { Ok(self.sdk.fetch_onchain_limits().await?.into()) } #[wasm_bindgen(js_name = "preparePayOnchain")] pub async fn prepare_pay_onchain( &self, req: PreparePayOnchainRequest, ) -> WasmResult { Ok(self.sdk.prepare_pay_onchain(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "payOnchain")] pub async fn pay_onchain(&self, req: PayOnchainRequest) -> WasmResult { Ok(self.sdk.pay_onchain(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "prepareBuyBitcoin")] pub async fn prepare_buy_bitcoin( &self, req: PrepareBuyBitcoinRequest, ) -> WasmResult { Ok(self.sdk.prepare_buy_bitcoin(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "buyBitcoin")] pub async fn buy_bitcoin(&self, req: BuyBitcoinRequest) -> WasmResult { Ok(self.sdk.buy_bitcoin(&req.into()).await?) } #[wasm_bindgen(js_name = "listPayments")] pub async fn list_payments(&self, req: ListPaymentsRequest) -> WasmResult> { Ok(self .sdk .list_payments(&req.into()) .await? .into_iter() .map(|r| r.into()) .collect()) } #[wasm_bindgen(js_name = "getPayment")] pub async fn get_payment(&self, req: GetPaymentRequest) -> WasmResult> { Ok(self.sdk.get_payment(&req.into()).await?.map(|r| r.into())) } #[wasm_bindgen(js_name = "fetchPaymentProposedFees")] pub async fn fetch_payment_proposed_fees( &self, req: FetchPaymentProposedFeesRequest, ) -> WasmResult { Ok(self .sdk .fetch_payment_proposed_fees(&req.into()) .await? .into()) } #[wasm_bindgen(js_name = "acceptPaymentProposedFees")] pub async fn accept_payment_proposed_fees( &self, req: AcceptPaymentProposedFeesRequest, ) -> WasmResult<()> { self.sdk.accept_payment_proposed_fees(&req.into()).await?; Ok(()) } #[wasm_bindgen(js_name = "prepareLnurlPay")] pub async fn prepare_lnurl_pay( &self, req: PrepareLnUrlPayRequest, ) -> WasmResult { Ok(self.sdk.prepare_lnurl_pay(req.into()).await?.into()) } #[wasm_bindgen(js_name = "lnurlPay")] pub async fn lnurl_pay(&self, req: LnUrlPayRequest) -> WasmResult { Ok(self.sdk.lnurl_pay(req.into()).await?.into()) } #[wasm_bindgen(js_name = "lnurlWithdraw")] pub async fn lnurl_withdraw( &self, req: LnUrlWithdrawRequest, ) -> WasmResult { Ok(self.sdk.lnurl_withdraw(req.into()).await?.into()) } #[wasm_bindgen(js_name = "lnurlAuth")] pub async fn lnurl_auth( &self, req_data: LnUrlAuthRequestData, ) -> WasmResult { Ok(self.sdk.lnurl_auth(req_data.into()).await?.into()) } #[wasm_bindgen(js_name = "registerWebhook")] pub async fn register_webhook(&self, webhook_url: String) -> WasmResult<()> { self.sdk.register_webhook(webhook_url).await?; Ok(()) } #[wasm_bindgen(js_name = "unregisterWebhook")] pub async fn unregister_webhook(&self) -> WasmResult<()> { self.sdk.unregister_webhook().await?; Ok(()) } #[wasm_bindgen(js_name = "fetchFiatRates")] pub async fn fetch_fiat_rates(&self) -> WasmResult> { Ok(self .sdk .fetch_fiat_rates() .await? .into_iter() .map(|r| r.into()) .collect()) } #[wasm_bindgen(js_name = "listFiatCurrencies")] pub async fn list_fiat_currencies(&self) -> WasmResult> { Ok(self .sdk .list_fiat_currencies() .await? .into_iter() .map(|r| r.into()) .collect()) } #[wasm_bindgen(js_name = "listRefundables")] pub async fn list_refundables(&self) -> WasmResult> { Ok(self .sdk .list_refundables() .await? .into_iter() .map(|r| r.into()) .collect()) } #[wasm_bindgen(js_name = "prepareRefund")] pub async fn prepare_refund( &self, req: PrepareRefundRequest, ) -> WasmResult { Ok(self.sdk.prepare_refund(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "refund")] pub async fn refund(&self, req: RefundRequest) -> WasmResult { Ok(self.sdk.refund(&req.into()).await?.into()) } #[wasm_bindgen(js_name = "rescanOnchainSwaps")] pub async fn rescan_onchain_swaps(&self) -> WasmResult<()> { self.sdk.rescan_onchain_swaps().await?; Ok(()) } #[wasm_bindgen(js_name = "sync")] pub async fn sync(&self) -> WasmResult<()> { self.sdk.sync(false).await?; Ok(()) } #[wasm_bindgen(js_name = "recommendedFees")] pub async fn recommended_fees(&self) -> WasmResult { Ok(self.sdk.recommended_fees().await?.into()) } #[wasm_bindgen(js_name = "backup")] pub fn backup(&self, req: BackupRequest) -> WasmResult<()> { self.sdk.backup(req.into())?; Ok(()) } #[wasm_bindgen(js_name = "restore")] pub fn restore(&self, req: RestoreRequest) -> WasmResult<()> { self.sdk.restore(req.into())?; Ok(()) } #[wasm_bindgen(js_name = "disconnect")] pub async fn disconnect(&self) -> WasmResult<()> { self.sdk.disconnect().await?; Ok(()) } }