From fd3dce4aa728c33e74160e062881500aaf742a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Tue, 25 Jul 2023 16:36:23 -0300 Subject: [PATCH] Deezy integration This PR allows deezy via lndhubx: * Swap BTC -> LN * Swap LN -> BTC Fix #243 --- src/lightning.rs | 57 ++++++++++++++++++++++++++++++++ src/web.rs | 28 ++++++++++++++++ tests/lightning.rs | 81 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 150 insertions(+), 16 deletions(-) diff --git a/src/lightning.rs b/src/lightning.rs index 89a0083a..e0a105b7 100644 --- a/src/lightning.rs +++ b/src/lightning.rs @@ -122,6 +122,38 @@ pub struct CheckPaymentResponse { paid: bool, } +/// Swap BTC onchain to Lightning response +#[derive(Debug, Deserialize)] +pub struct SwapBtcLnResponse { + pub address: String, + pub commitment: String, + pub signature: String, + pub secret_access_key: String, +} + +/// Speed of the onchain transaction +#[derive(Serialize, Deserialize, Debug)] +pub enum OnchainSpeed { + Fast, + Medium, + Slow, +} + +/// Swap Lightning to BTC onchain request +#[derive(Serialize, Deserialize, Debug)] +pub struct SwapLnBTCRequest { + pub amount: u64, + pub address: String, + pub speed: Option, +} + +/// Swap Lightning to BTC onchain response +#[derive(Debug, Deserialize)] +pub struct SwapLnBtcResponse { + pub bolt11_invoice: String, + pub fee_sats: u32, +} + /// Creates a new lightning custodial wallet pub async fn create_wallet(username: &str, password: &str) -> Result { let endpoint = LNDHUB_ENDPOINT.read().await; @@ -220,3 +252,28 @@ pub async fn check_payment(payment_hash: &str) -> Result { Ok(r.paid) } + +/// Swap BTC onchain to Lightning +pub async fn swap_btc_ln(token: &str) -> Result { + let endpoint = LNDHUB_ENDPOINT.read().await; + let url = format!("{endpoint}/get_onchain_address"); + let response = get(&url, Some(token)).await?; + let r = serde_json::from_str::(&response)?; + + Ok(r) +} + +/// Swap Lightning to BTC onchain +pub async fn swap_ln_btc(address: &str, amount: u64, token: &str) -> Result { + let endpoint = LNDHUB_ENDPOINT.read().await; + let url = format!("{endpoint}/make_onchain_swap"); + let req = SwapLnBTCRequest { + address: address.to_string(), + amount, + speed: Some(OnchainSpeed::Fast), + }; + let response = post_json_auth(&url, &Some(req), Some(token)).await?; + let r = serde_json::from_str::(&response)?; + + Ok(r) +} diff --git a/src/web.rs b/src/web.rs index c58b8594..7f265c26 100644 --- a/src/web.rs +++ b/src/web.rs @@ -668,6 +668,34 @@ pub mod lightning { } }) } + + #[wasm_bindgen] + pub fn swap_btc_ln(token: String) -> Promise { + set_panic_hook(); + + future_to_promise(async move { + match crate::lightning::swap_btc_ln(&token).await { + Ok(result) => Ok(JsValue::from_string( + serde_json::to_string(&result).unwrap(), + )), + Err(err) => Err(JsValue::from_string(err.to_string())), + } + }) + } + + #[wasm_bindgen] + pub fn swap_ln_btc(address: String, amount: u64, token: String) -> Promise { + set_panic_hook(); + + future_to_promise(async move { + match crate::lightning::swap_ln_btc(&address, amount, &token).await { + Ok(result) => Ok(JsValue::from_string( + serde_json::to_string(&result).unwrap(), + )), + Err(err) => Err(JsValue::from_string(err.to_string())), + } + }) + } } pub mod carbonado { diff --git a/tests/lightning.rs b/tests/lightning.rs index 533a8cc0..b781a4b6 100644 --- a/tests/lightning.rs +++ b/tests/lightning.rs @@ -5,19 +5,17 @@ use bitmask_core::{ info, lightning::{ auth, check_payment, create_invoice, create_wallet, decode_invoice, get_balance, get_txs, - pay_invoice, AuthResponse, CreateWalletResponse, Transaction, + pay_invoice, swap_btc_ln, swap_ln_btc, AuthResponse, CreateWalletResponse, Transaction, }, util::init_logging, }; use std::{thread, time}; -async fn new_wallet() -> Result { - // we generate a random string to be used as username and password - let random_number = bip39::rand::random::(); - let s = hex::encode(random_number.to_le_bytes()); - // We put to sleep the test to avoid hit too fast the API - thread::sleep(time::Duration::from_secs(1)); - let res = create_wallet(&s, &s).await?; +async fn static_wallet() -> Result { + // We have a static username and password for a wallet + let res = CreateWalletResponse::Username { + username: "556bcb9f4cea5b6a".to_string(), + }; Ok(res) } @@ -26,7 +24,11 @@ async fn new_wallet() -> Result { pub async fn create_wallet_test() -> Result<()> { init_logging("lightning=debug"); - let res = new_wallet().await?; + // We create a new wallet only for this test + // we generate a random string to be used as username and password + let random_number = bip39::rand::random::(); + let s = hex::encode(random_number.to_le_bytes()); + let res = create_wallet(&s, &s).await?; let mut uname = String::new(); if let CreateWalletResponse::Username { username } = res { uname = username; @@ -41,7 +43,7 @@ pub async fn create_wallet_test() -> Result<()> { pub async fn auth_test() -> Result<()> { init_logging("lightning=warn"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut uname = String::new(); if let CreateWalletResponse::Username { username } = res { uname = username; @@ -71,7 +73,7 @@ pub async fn auth_failed_test() -> Result<()> { pub async fn create_decode_invoice_test() -> Result<()> { init_logging("lightning=warn"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut uname = String::new(); if let CreateWalletResponse::Username { username } = res { uname = username; @@ -97,7 +99,7 @@ pub async fn create_decode_invoice_test() -> Result<()> { pub async fn get_balance_test() -> Result<()> { init_logging("lightning=warn"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut uname = String::new(); if let CreateWalletResponse::Username { username } = res { uname = username; @@ -118,7 +120,7 @@ pub async fn pay_invoice_error_test() -> Result<()> { init_logging("tests=debug"); info!("We create user Alice"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut alice = String::new(); if let CreateWalletResponse::Username { username } = res { alice = username; @@ -129,7 +131,7 @@ pub async fn pay_invoice_error_test() -> Result<()> { info!("Alice invoice"); let invoice = create_invoice("testing pay alice invoice", 33, &token).await?; info!("We create user Bob"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut bob = String::new(); if let CreateWalletResponse::Username { username } = res { bob = username; @@ -150,7 +152,7 @@ pub async fn pay_invoice_error_test() -> Result<()> { pub async fn get_txs_test() -> Result<()> { init_logging("lightning=warn"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut uname = String::new(); if let CreateWalletResponse::Username { username } = res { uname = username; @@ -169,7 +171,7 @@ pub async fn get_txs_test() -> Result<()> { pub async fn check_payment_test() -> Result<()> { init_logging("lightning=warn"); - let res = new_wallet().await?; + let res = static_wallet().await?; let mut uname = String::new(); if let CreateWalletResponse::Username { username } = res { uname = username; @@ -188,3 +190,50 @@ pub async fn check_payment_test() -> Result<()> { Ok(()) } + +#[tokio::test] +pub async fn swap_btc_ln_test() -> Result<()> { + init_logging("lightning=warn"); + + let res = static_wallet().await?; + let mut uname = String::new(); + if let CreateWalletResponse::Username { username } = res { + uname = username; + } + let response = auth(&uname, &uname).await?; + thread::sleep(time::Duration::from_secs(1)); + if let AuthResponse::Result { refresh: _, token } = response { + let response = swap_btc_ln(&token).await?; + assert_eq!(response.secret_access_key.len(), 64); + } else { + panic!("Auth failed"); + } + + Ok(()) +} + +#[tokio::test] +pub async fn swap_ln_btc_test() -> Result<()> { + init_logging("lightning=warn"); + + let res = static_wallet().await?; + let mut uname = String::new(); + if let CreateWalletResponse::Username { username } = res { + uname = username; + } + let response = auth(&uname, &uname).await?; + thread::sleep(time::Duration::from_secs(1)); + if let AuthResponse::Result { refresh: _, token } = response { + let amount = 500000; + let response = + swap_ln_btc("bc1q6m6efx4gzlltgcr9hgrke0je4z3hvsyzzazl8z", amount, &token).await?; + let decoded_invoice = decode_invoice(&response.bolt11_invoice)?; + let invoice_amount = decoded_invoice.amount_milli_satoshis().unwrap() / 1000; + let total_amount = amount + response.fee_sats as u64; + assert_eq!(total_amount, invoice_amount); + } else { + panic!("Auth failed"); + } + + Ok(()) +}