From 359c5bb99f350fef81724a923c6ed4eb004aaf45 Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Thu, 12 Oct 2023 11:32:04 +0200 Subject: [PATCH 1/7] feat: stablesats pay invoice --- moksha-mint/src/error.rs | 6 + moksha-mint/src/lightning/mod.rs | 1 + moksha-mint/src/lightning/stablesats.rs | 190 ++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 moksha-mint/src/lightning/stablesats.rs diff --git a/moksha-mint/src/error.rs b/moksha-mint/src/error.rs index ad4c71e9..12aa812a 100644 --- a/moksha-mint/src/error.rs +++ b/moksha-mint/src/error.rs @@ -24,6 +24,9 @@ pub enum MokshaMintError { #[error("Failed to pay invoice {0} - Error {1}")] PayInvoice(String, LightningError), + #[error("Failed to pay invoice {0} - Error {1}")] + PayInvoiceStablesats(String, String), // FIXME + #[error("DB Error {0}")] Db(#[from] rocksdb::Error), @@ -59,6 +62,9 @@ pub enum MokshaMintError { #[error("Lightning Error {0}")] Lightning(#[from] LightningError), + + #[error("Deserialize Error {0}")] + DeserializeResponse(String), } impl IntoResponse for MokshaMintError { diff --git a/moksha-mint/src/lightning/mod.rs b/moksha-mint/src/lightning/mod.rs index 4f64816c..9dff2709 100644 --- a/moksha-mint/src/lightning/mod.rs +++ b/moksha-mint/src/lightning/mod.rs @@ -17,6 +17,7 @@ use lightning_invoice::{Bolt11Invoice as LNInvoice, SignedRawBolt11Invoice}; mod alby; pub mod error; mod lnbits; +mod stablesats; mod strike; #[cfg(test)] diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs new file mode 100644 index 00000000..de6f531a --- /dev/null +++ b/moksha-mint/src/lightning/stablesats.rs @@ -0,0 +1,190 @@ +use async_trait::async_trait; +use axum::http::HeaderValue; + +use hyper::header::CONTENT_TYPE; +use serde_derive::{Deserialize, Serialize}; +use url::Url; + +use crate::{ + error::MokshaMintError, + model::{CreateInvoiceResult, PayInvoiceResult}, +}; + +use super::{error::LightningError, Lightning}; + +#[derive(Deserialize, Serialize, Debug, Clone, Default)] +pub struct StablesatsSettings { + pub auth_bearer: Option, + pub galoy_url: Option, // FIXME use Url type instead +} + +impl StablesatsSettings { + pub fn new(auth_bearer: &str, galoy_url: &str) -> StablesatsSettings { + StablesatsSettings { + auth_bearer: Some(auth_bearer.to_owned()), + galoy_url: Some(galoy_url.to_owned()), + } + } +} + +#[derive(Clone, Debug)] +struct StablesatsLightning { + auth_bearer: String, + galoy_url: Url, + usd_wallet_id: String, + reqwest_client: reqwest::Client, +} + +impl StablesatsLightning { + pub fn new( + auth_bearer: &str, + galoy_url: &str, + usd_wallet_id: &str, + ) -> Result { + let galoy_url = Url::parse(galoy_url)?; + + let reqwest_client = reqwest::Client::builder().build()?; + + Ok(StablesatsLightning { + auth_bearer: auth_bearer.to_owned(), + galoy_url, + reqwest_client, + usd_wallet_id: usd_wallet_id.to_owned(), + }) + } + + pub async fn make_gqlpost(&self, body: &str) -> Result { + let response = self + .reqwest_client + .post(self.galoy_url.clone()) + .bearer_auth(self.auth_bearer.clone()) + .header( + CONTENT_TYPE, + HeaderValue::from_str("application/json").expect("Invalid header value"), + ) + .body(body.to_string()) + .send() + .await?; + + if response.status() == reqwest::StatusCode::NOT_FOUND { + return Err(LightningError::NotFound); + } + + if response.status() == reqwest::StatusCode::UNAUTHORIZED { + return Err(LightningError::Unauthorized); + } + + Ok(response.text().await?) + } +} + +#[async_trait] +impl Lightning for StablesatsLightning { + async fn is_invoice_paid(&self, _invoice: String) -> Result { + // Not implemented for Stablesats + unimplemented!() + } + + async fn create_invoice(&self, _amount: u64) -> Result { + unimplemented!() + } + + async fn pay_invoice( + &self, + payment_request: String, + ) -> Result { + let invoice = self.decode_invoice(payment_request.clone()).await?; + let payment_hash = invoice.payment_hash().to_vec(); + + let input = LnInvoicePaymentSendInput { + payment_request: payment_request.clone(), + wallet_id: self.usd_wallet_id.clone(), + }; + let query = format!( + r#"{{"query":"mutation LnInvoicePaymentSend($input: LnInvoicePaymentInput!) {{ lnInvoicePaymentSend(input: $input) {{ status errors {{ message path code }} }} }}","variables":{{"input":{}}}}}"#, + serde_json::to_string(&input).map_err(MokshaMintError::Serialization)? + ); + + println!("query: {}", query.clone()); + let response = self + .make_gqlpost(&query) + .await + .map_err(|err| MokshaMintError::PayInvoice(payment_request.clone(), err))?; + + println!("response: {:?}", response.clone()); + + let response: serde_json::Value = serde_json::from_str(&response).unwrap(); + let status = response["data"]["lnInvoicePaymentSend"]["status"] + .as_str() + .unwrap(); + + if status == "SUCCESS" { + Ok(PayInvoiceResult { + payment_hash: hex::encode(payment_hash), + }) + } else { + Err(MokshaMintError::PayInvoiceStablesats( + payment_request, + "Error paying invoice".to_owned(), + )) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct LnInvoicePaymentSendInput { + payment_request: String, + wallet_id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ResponseData { + pub data: LnInvoicePaymentSend, +} + +#[derive(Debug, Serialize, Deserialize)] +struct LnInvoicePaymentSend { + pub status: String, + pub errors: Option>, // FIXME use specific type +} + +#[derive(Debug, Serialize, Deserialize)] +struct LnInvoicePaymentStatusInput { + payment_request: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct LnInvoicePaymentStatusData { + pub ln_invoice_payment_status: LnInvoicePaymentStatus, +} + +#[derive(Debug, Serialize, Deserialize)] +struct LnInvoicePaymentStatus { + pub status: String, + pub errors: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct LnInvoicePaymentStatusError { + pub message: String, + pub path: Vec, + pub code: Option, +} + +#[cfg(test)] +mod tests { + + use super::StablesatsLightning; + use crate::lightning::Lightning; + + #[tokio::test] + #[ignore] + async fn test_pay_invoice() -> anyhow::Result<()> { + let ln = + StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id")?; + let result = ln.pay_invoice("lnbc180...".to_string()).await; + println!("{:?}", result); + Ok(()) + } +} From 181b161a9b3b948be3cf377cd0b2abf8abcc9edb Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Thu, 12 Oct 2023 13:50:41 +0200 Subject: [PATCH 2/7] feat: stablesats create invoice --- moksha-mint/src/lightning/stablesats.rs | 60 ++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs index de6f531a..8da78362 100644 --- a/moksha-mint/src/lightning/stablesats.rs +++ b/moksha-mint/src/lightning/stablesats.rs @@ -85,8 +85,46 @@ impl Lightning for StablesatsLightning { unimplemented!() } - async fn create_invoice(&self, _amount: u64) -> Result { - unimplemented!() + async fn create_invoice( + &self, + amount_in_usd_cent: u64, + ) -> Result { + let input = LnUsdInvoiceCreateInput { + amount: amount_in_usd_cent, + wallet_id: self.usd_wallet_id.clone(), + }; + let query = format!( + r#"{{"query":"mutation lnUsdInvoiceCreate($input: LnUsdInvoiceCreateInput!) {{ lnUsdInvoiceCreate(input: $input) {{ invoice {{ paymentRequest paymentHash satoshis }} }} }}","variables":{{"input":{}}}}}"#, + serde_json::to_string(&input).map_err(MokshaMintError::Serialization)? + ); + + let response = self + .make_gqlpost(&query) + .await + .map_err(|err| MokshaMintError::PayInvoice("payment_request".to_string(), err))?; // FIXME + + println!("response: {:?}", response.clone()); + + let response: serde_json::Value = serde_json::from_str(&response).unwrap(); + let payment_request = response["data"]["lnUsdInvoiceCreate"]["invoice"]["paymentRequest"] + .as_str() + .unwrap() + .to_owned(); + + let payment_hash = response["data"]["lnUsdInvoiceCreate"]["invoice"]["paymentHash"] + .as_str() + .unwrap(); + + let sats = response["data"]["lnUsdInvoiceCreate"]["invoice"]["satoshis"] + .as_u64() + .unwrap(); + + println!("sats {}", sats); + + Ok(CreateInvoiceResult { + payment_hash: payment_hash.as_bytes().to_vec(), + payment_request, + }) } async fn pay_invoice( @@ -172,6 +210,14 @@ struct LnInvoicePaymentStatusError { pub code: Option, } +// # create invoice +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct LnUsdInvoiceCreateInput { + amount: u64, + wallet_id: String, +} + #[cfg(test)] mod tests { @@ -187,4 +233,14 @@ mod tests { println!("{:?}", result); Ok(()) } + + #[tokio::test] + #[ignore] + async fn test_create_invoice() -> anyhow::Result<()> { + let ln = + StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id")?; + let result = ln.create_invoice(50).await?; + println!("{:?}", result); + Ok(()) + } } From 3d82504b7f8d239d3a9aaa92889b3c7ee0ce225b Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Thu, 12 Oct 2023 14:13:49 +0200 Subject: [PATCH 3/7] feat: stablesats is invoice paid --- moksha-mint/src/lightning/stablesats.rs | 68 +++++++++++++------------ 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs index 8da78362..37b0ec96 100644 --- a/moksha-mint/src/lightning/stablesats.rs +++ b/moksha-mint/src/lightning/stablesats.rs @@ -80,9 +80,31 @@ impl StablesatsLightning { #[async_trait] impl Lightning for StablesatsLightning { - async fn is_invoice_paid(&self, _invoice: String) -> Result { - // Not implemented for Stablesats - unimplemented!() + async fn is_invoice_paid(&self, invoice: String) -> Result { + let input = LnInvoicePaymentStatusInput { + payment_request: invoice, + }; + let query = format!( + r#"{{"query":"query LnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) {{ lnInvoicePaymentStatus(input: $input) {{ status errors {{ message path code }} }} }}","variables":{{"input":{}}}}}"#, + serde_json::to_string(&input).map_err(MokshaMintError::Serialization)? + ); + + let response = self + .make_gqlpost(&query) + .await + .map_err(|err| MokshaMintError::PayInvoice("payment_request".to_string(), err))?; + + println!("response: {:?}", response.clone()); + + let response: serde_json::Value = serde_json::from_str(&response).unwrap(); + let status = response["data"]["lnInvoicePaymentStatus"]["status"] + .as_str() + .unwrap() + .to_owned(); + + println!("invoice paid status: {}", status.clone()); + + Ok(status == "PAID") } async fn create_invoice( @@ -177,39 +199,11 @@ struct LnInvoicePaymentSendInput { } #[derive(Debug, Serialize, Deserialize)] -struct ResponseData { - pub data: LnInvoicePaymentSend, -} - -#[derive(Debug, Serialize, Deserialize)] -struct LnInvoicePaymentSend { - pub status: String, - pub errors: Option>, // FIXME use specific type -} - -#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] struct LnInvoicePaymentStatusInput { payment_request: String, } -#[derive(Debug, Serialize, Deserialize)] -struct LnInvoicePaymentStatusData { - pub ln_invoice_payment_status: LnInvoicePaymentStatus, -} - -#[derive(Debug, Serialize, Deserialize)] -struct LnInvoicePaymentStatus { - pub status: String, - pub errors: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct LnInvoicePaymentStatusError { - pub message: String, - pub path: Vec, - pub code: Option, -} - // # create invoice #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -243,4 +237,14 @@ mod tests { println!("{:?}", result); Ok(()) } + + #[tokio::test] + #[ignore] + async fn test_is_invoice_paid() -> anyhow::Result<()> { + let ln = + StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id")?; + let result = ln.is_invoice_paid("lnbc30...".to_owned()).await?; + println!("{:?}", result); + Ok(()) + } } From a54c49bc40feb1fb4fd9b5d27d74f9357b5d7964 Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Thu, 12 Oct 2023 15:26:16 +0200 Subject: [PATCH 4/7] feat: stablesats add settings --- .env.example | 6 ++++ moksha-mint/src/bin/moksha-mint.rs | 10 ++++-- moksha-mint/src/lib.rs | 16 ++++++++++ moksha-mint/src/lightning/mod.rs | 9 ++++-- moksha-mint/src/lightning/stablesats.rs | 42 ++++++++++++++++--------- 5 files changed, 65 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index 08e7c797..94857b47 100644 --- a/.env.example +++ b/.env.example @@ -55,6 +55,12 @@ LND_TLS_CERT_PATH="/../tls.cert" LND_GRPC_HOST="https://localhost:10004" +STABLESATS_AUTH_BEARER=YOUR_AUTH_BEARER +STABLESATS_GALOY_URL=YOUR_GALOY_URL +STABLESATS_USD_WALLET_ID=YOUR_USD_WALLET_ID + + + ### environment variables for the fedimint-cli CLI_FEDIMINT_CONNECTION="fed...." \ No newline at end of file diff --git a/moksha-mint/src/bin/moksha-mint.rs b/moksha-mint/src/bin/moksha-mint.rs index 3e694290..b71e21cc 100644 --- a/moksha-mint/src/bin/moksha-mint.rs +++ b/moksha-mint/src/bin/moksha-mint.rs @@ -1,8 +1,8 @@ use mokshamint::{ info::MintInfoSettings, lightning::{ - AlbyLightningSettings, LightningType, LnbitsLightningSettings, LndLightningSettings, - StrikeLightningSettings, + stablesats::StablesatsSettings, AlbyLightningSettings, LightningType, + LnbitsLightningSettings, LndLightningSettings, StrikeLightningSettings, }, MintBuilder, }; @@ -58,6 +58,12 @@ pub async fn main() -> anyhow::Result<()> { .from_env::() .expect("Please provide strike info"); LightningType::Strike(strike_settings) + }, + "Stablesats" => { + let settings = envy::prefixed("STABLESATS_") + .from_env::() + .expect("Please provide stablesats info"); + LightningType::Stablesats(settings) } _ => panic!( "env MINT_LIGHTNING_BACKEND not found or invalid values. Valid values are Lnbits, Lnd, Alby, and Strike" diff --git a/moksha-mint/src/lib.rs b/moksha-mint/src/lib.rs index ceb52aad..6860fbc8 100644 --- a/moksha-mint/src/lib.rs +++ b/moksha-mint/src/lib.rs @@ -13,6 +13,7 @@ use error::MokshaMintError; use hyper::http::{HeaderName, HeaderValue}; use hyper::Method; use info::{MintInfoResponse, MintInfoSettings, Parameter}; +use lightning::stablesats::StablesatsLightning; use lightning::{AlbyLightning, Lightning, LightningType, LnbitsLightning, StrikeLightning}; use mint::{LightningFeeConfig, Mint}; use model::{GetMintQuery, PostMintQuery}; @@ -93,6 +94,21 @@ impl MintBuilder { Some(LightningType::Strike(strike_settings)) => Arc::new(StrikeLightning::new( strike_settings.api_key.expect("STRIKE_API_KEY not set"), )), + Some(LightningType::Stablesats(settings)) => Arc::new(StablesatsLightning::new( + settings + .auth_bearer + .expect("STABLESATS_AUTH_BEARER not set") + .as_str(), + settings + .galoy_url + .expect("STABLESATS_GALOY_URL not set") + .as_str(), + settings + .usd_wallet_id + .expect("STABLESATS_USD_WALLET_ID not set") + .as_str(), + )), + Some(LightningType::Lnd(lnd_settings)) => Arc::new( lightning::LndLightning::new( lnd_settings.grpc_host.expect("LND_GRPC_HOST not set"), diff --git a/moksha-mint/src/lightning/mod.rs b/moksha-mint/src/lightning/mod.rs index 9dff2709..fbea96a3 100644 --- a/moksha-mint/src/lightning/mod.rs +++ b/moksha-mint/src/lightning/mod.rs @@ -17,14 +17,17 @@ use lightning_invoice::{Bolt11Invoice as LNInvoice, SignedRawBolt11Invoice}; mod alby; pub mod error; mod lnbits; -mod stablesats; +pub mod stablesats; mod strike; #[cfg(test)] use mockall::automock; use std::{path::PathBuf, str::FromStr, sync::Arc}; -use self::{alby::AlbyClient, error::LightningError, lnbits::LNBitsClient, strike::StrikeClient}; +use self::{ + alby::AlbyClient, error::LightningError, lnbits::LNBitsClient, stablesats::StablesatsSettings, + strike::StrikeClient, +}; #[derive(Debug, Clone)] pub enum LightningType { @@ -32,6 +35,7 @@ pub enum LightningType { Alby(AlbyLightningSettings), Strike(StrikeLightningSettings), Lnd(LndLightningSettings), + Stablesats(StablesatsSettings), } impl fmt::Display for LightningType { @@ -41,6 +45,7 @@ impl fmt::Display for LightningType { LightningType::Alby(settings) => write!(f, "Alby: {}", settings), LightningType::Strike(settings) => write!(f, "Strike: {}", settings), LightningType::Lnd(settings) => write!(f, "Lnd: {}", settings), + LightningType::Stablesats(settings) => write!(f, "Stablesats: {}", settings), } } } diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs index 37b0ec96..3f209f16 100644 --- a/moksha-mint/src/lightning/stablesats.rs +++ b/moksha-mint/src/lightning/stablesats.rs @@ -1,8 +1,11 @@ +use std::fmt::{self, Formatter}; + use async_trait::async_trait; use axum::http::HeaderValue; use hyper::header::CONTENT_TYPE; use serde_derive::{Deserialize, Serialize}; + use url::Url; use crate::{ @@ -16,19 +19,32 @@ use super::{error::LightningError, Lightning}; pub struct StablesatsSettings { pub auth_bearer: Option, pub galoy_url: Option, // FIXME use Url type instead + pub usd_wallet_id: Option, } impl StablesatsSettings { - pub fn new(auth_bearer: &str, galoy_url: &str) -> StablesatsSettings { + pub fn new(auth_bearer: &str, galoy_url: &str, usd_wallet_id: &str) -> StablesatsSettings { StablesatsSettings { auth_bearer: Some(auth_bearer.to_owned()), galoy_url: Some(galoy_url.to_owned()), + usd_wallet_id: Some(usd_wallet_id.to_owned()), } } } +impl fmt::Display for StablesatsSettings { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "auth_bearer: {}, galoy_url: {}", + self.auth_bearer.as_ref().unwrap(), + self.galoy_url.as_ref().unwrap(), + ) + } +} + #[derive(Clone, Debug)] -struct StablesatsLightning { +pub struct StablesatsLightning { auth_bearer: String, galoy_url: Url, usd_wallet_id: String, @@ -36,21 +52,19 @@ struct StablesatsLightning { } impl StablesatsLightning { - pub fn new( - auth_bearer: &str, - galoy_url: &str, - usd_wallet_id: &str, - ) -> Result { - let galoy_url = Url::parse(galoy_url)?; + pub fn new(auth_bearer: &str, galoy_url: &str, usd_wallet_id: &str) -> StablesatsLightning { + let galoy_url = Url::parse(galoy_url).expect("invalid galoy url"); - let reqwest_client = reqwest::Client::builder().build()?; + let reqwest_client = reqwest::Client::builder() + .build() + .expect("invalid reqwest client"); - Ok(StablesatsLightning { + StablesatsLightning { auth_bearer: auth_bearer.to_owned(), galoy_url, reqwest_client, usd_wallet_id: usd_wallet_id.to_owned(), - }) + } } pub async fn make_gqlpost(&self, body: &str) -> Result { @@ -222,7 +236,7 @@ mod tests { #[ignore] async fn test_pay_invoice() -> anyhow::Result<()> { let ln = - StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id")?; + StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id"); let result = ln.pay_invoice("lnbc180...".to_string()).await; println!("{:?}", result); Ok(()) @@ -232,7 +246,7 @@ mod tests { #[ignore] async fn test_create_invoice() -> anyhow::Result<()> { let ln = - StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id")?; + StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id"); let result = ln.create_invoice(50).await?; println!("{:?}", result); Ok(()) @@ -242,7 +256,7 @@ mod tests { #[ignore] async fn test_is_invoice_paid() -> anyhow::Result<()> { let ln = - StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id")?; + StablesatsLightning::new("auth bearer", "https://api.blink.sv/graphql", "wallet id"); let result = ln.is_invoice_paid("lnbc30...".to_owned()).await?; println!("{:?}", result); Ok(()) From 1643d91f4018322799f3036a4aa5312b1c109f1f Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Tue, 17 Oct 2023 08:32:59 +0200 Subject: [PATCH 5/7] feat: stablesats get quote --- moksha-core/src/model.rs | 5 ++++ moksha-mint/src/lib.rs | 23 +++++++++++++++--- moksha-mint/src/lightning/mod.rs | 5 ++++ moksha-mint/src/lightning/stablesats.rs | 15 ++++++++++++ moksha-wallet/src/client/mod.rs | 10 ++++++-- moksha-wallet/src/client/reqwest.rs | 14 +++++++++-- moksha-wallet/src/wallet.rs | 31 ++++++++++++++++++++----- moksha-wallet/tests/tests.rs | 12 ++++++++-- 8 files changed, 100 insertions(+), 15 deletions(-) diff --git a/moksha-core/src/model.rs b/moksha-core/src/model.rs index 4d3d11c6..f8d03eda 100644 --- a/moksha-core/src/model.rs +++ b/moksha-core/src/model.rs @@ -438,6 +438,11 @@ pub struct CashuErrorResponse { pub error: String, } +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct InvoiceQuoteResult { + pub amount_in_cent: u64, +} + #[cfg(test)] mod tests { use crate::{ diff --git a/moksha-mint/src/lib.rs b/moksha-mint/src/lib.rs index 6860fbc8..916bbe09 100644 --- a/moksha-mint/src/lib.rs +++ b/moksha-mint/src/lib.rs @@ -4,7 +4,7 @@ use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; -use axum::extract::{Query, State}; +use axum::extract::{Path, Query, State}; use axum::routing::{get_service, post}; use axum::Router; use axum::{routing::get, Json}; @@ -18,8 +18,9 @@ use lightning::{AlbyLightning, Lightning, LightningType, LnbitsLightning, Strike use mint::{LightningFeeConfig, Mint}; use model::{GetMintQuery, PostMintQuery}; use moksha_core::model::{ - CheckFeesRequest, CheckFeesResponse, Keysets, PaymentRequest, PostMeltRequest, - PostMeltResponse, PostMintRequest, PostMintResponse, PostSplitRequest, PostSplitResponse, + CheckFeesRequest, CheckFeesResponse, InvoiceQuoteResult, Keysets, PaymentRequest, + PostMeltRequest, PostMeltResponse, PostMintRequest, PostMintResponse, PostSplitRequest, + PostSplitResponse, }; use secp256k1::PublicKey; @@ -187,6 +188,7 @@ fn app(mint: Mint, serve_wallet_path: Option, prefix: Option) - .route("/keysets", get(get_keysets)) .route("/mint", get(get_mint).post(post_mint)) .route("/checkfees", post(post_check_fees)) + .route("/melt/:invoice", get(get_melt)) .route("/melt", post(post_melt)) .route("/split", post(post_split)) .route("/info", get(get_info)); @@ -258,6 +260,21 @@ async fn post_check_fees( })) } +async fn get_melt( + Path(invoice): Path, + State(mint): State, +) -> Result, MokshaMintError> { + println!(">>>>>>>>>>>>>>>> melt2"); + + let quote = mint.lightning.get_quote(invoice.to_owned()).await?; + + println!("quote: {:?}", "e); + + Ok(Json(InvoiceQuoteResult { + amount_in_cent: quote.amount_in_cent, + })) +} + async fn get_info(State(mint): State) -> Result, MokshaMintError> { let mint_info = MintInfoResponse { name: mint.mint_info.name, diff --git a/moksha-mint/src/lightning/mod.rs b/moksha-mint/src/lightning/mod.rs index fbea96a3..c596a76b 100644 --- a/moksha-mint/src/lightning/mod.rs +++ b/moksha-mint/src/lightning/mod.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use moksha_core::model::InvoiceQuoteResult; use std::fmt::{self, Formatter}; use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard}; use tonic_lnd::Client; @@ -64,6 +65,10 @@ pub trait Lightning: Send + Sync { LNInvoice::from_str(&payment_request) .map_err(|err| MokshaMintError::DecodeInvoice(payment_request, err)) } + + async fn get_quote(&self, _invoice: String) -> Result { + Ok(Default::default()) + } } #[derive(Deserialize, Serialize, Debug, Clone, Default)] diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs index 3f209f16..195391bc 100644 --- a/moksha-mint/src/lightning/stablesats.rs +++ b/moksha-mint/src/lightning/stablesats.rs @@ -4,8 +4,11 @@ use async_trait::async_trait; use axum::http::HeaderValue; use hyper::header::CONTENT_TYPE; +use lightning_invoice::Bolt11Invoice; +use moksha_core::model::InvoiceQuoteResult; use serde_derive::{Deserialize, Serialize}; +use tracing::info; use url::Url; use crate::{ @@ -121,6 +124,18 @@ impl Lightning for StablesatsLightning { Ok(status == "PAID") } + async fn get_quote(&self, pr: String) -> Result { + let inv: Bolt11Invoice = self.decode_invoice(pr.clone()).await.unwrap(); + + let invoice_amount_sat = inv.amount_milli_satoshis().unwrap() / 1_000; + let btc_price_usd = 27915.68 / 100_000_000.0; + let price_in_usd_cents = (btc_price_usd * invoice_amount_sat as f64) * 100.0; + + Ok(InvoiceQuoteResult { + amount_in_cent: price_in_usd_cents as u64, + }) + } + async fn create_invoice( &self, amount_in_usd_cent: u64, diff --git a/moksha-wallet/src/client/mod.rs b/moksha-wallet/src/client/mod.rs index 17a0cdf4..c9f6d345 100644 --- a/moksha-wallet/src/client/mod.rs +++ b/moksha-wallet/src/client/mod.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use async_trait::async_trait; use moksha_core::model::{ - BlindedMessage, CheckFeesResponse, Keysets, PaymentRequest, PostMeltResponse, PostMintResponse, - PostSplitResponse, Proofs, + BlindedMessage, CheckFeesResponse, InvoiceQuoteResult, Keysets, PaymentRequest, + PostMeltResponse, PostMintResponse, PostSplitResponse, Proofs, }; use secp256k1::PublicKey; use url::Url; @@ -38,6 +38,12 @@ pub trait Client { outputs: Vec, ) -> Result; + async fn get_melt_tokens( + &self, + mint_url: &Url, + pr: String, + ) -> Result; + async fn post_checkfees( &self, mint_url: &Url, diff --git a/moksha-wallet/src/client/reqwest.rs b/moksha-wallet/src/client/reqwest.rs index a55e373f..c69903e6 100644 --- a/moksha-wallet/src/client/reqwest.rs +++ b/moksha-wallet/src/client/reqwest.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use async_trait::async_trait; use moksha_core::model::{ - BlindedMessage, CashuErrorResponse, CheckFeesRequest, CheckFeesResponse, Keysets, - PaymentRequest, PostMeltRequest, PostMeltResponse, PostMintRequest, PostMintResponse, + BlindedMessage, CashuErrorResponse, CheckFeesRequest, CheckFeesResponse, InvoiceQuoteResult, + Keysets, PaymentRequest, PostMeltRequest, PostMeltResponse, PostMintRequest, PostMintResponse, PostSplitRequest, PostSplitResponse, Proofs, }; use reqwest::{ @@ -100,6 +100,16 @@ impl Client for HttpClient { extract_response_data::(resp).await } + async fn get_melt_tokens( + &self, + mint_url: &Url, + pr: String, + ) -> Result { + let url = mint_url.join(&format!("melt/{}", pr))?; + let resp = self.request_client.get(url).send().await?; + extract_response_data::(resp).await + } + async fn get_mint_keys( &self, mint_url: &Url, diff --git a/moksha-wallet/src/wallet.rs b/moksha-wallet/src/wallet.rs index f8185b46..31d3a032 100644 --- a/moksha-wallet/src/wallet.rs +++ b/moksha-wallet/src/wallet.rs @@ -172,11 +172,22 @@ impl Wallet { .post_checkfees(&self.mint_url, invoice.clone()) .await?; - let ln_amount = Self::get_invoice_amount(&invoice)? + fees.fee; + // FIXME check if quote is available + // FIXME get currency from mint + // let ln_amount = Self::get_invoice_amount(&invoice)? + fees.fee; + + // if ln_amount > all_proofs.total_amount() { + // return Err(MokshaWalletError::NotEnoughTokens); + // } + + let quote_result = self + .client + .get_melt_tokens(&self.mint_url, invoice.clone()) + .await?; + + let ln_amount = quote_result.amount_in_cent; + println!("quote_result: {:?}", quote_result); - if ln_amount > all_proofs.total_amount() { - return Err(MokshaWalletError::NotEnoughTokens); - } let selected_proofs = all_proofs.proofs_for_amount(ln_amount)?; let total_proofs = { @@ -453,8 +464,8 @@ mod tests { use async_trait::async_trait; use moksha_core::fixture::{read_fixture, read_fixture_as}; use moksha_core::model::{ - BlindedMessage, CheckFeesResponse, Keysets, MintKeyset, PaymentRequest, PostMeltResponse, - PostMintResponse, PostSplitResponse, Proofs, Token, TokenV3, + BlindedMessage, CheckFeesResponse, InvoiceQuoteResult, Keysets, MintKeyset, PaymentRequest, + PostMeltResponse, PostMintResponse, PostSplitResponse, Proofs, Token, TokenV3, }; use secp256k1::PublicKey; use std::collections::HashMap; @@ -569,6 +580,14 @@ mod tests { Ok(self.split_response.clone()) } + async fn get_melt_tokens( + &self, + _mint_url: &Url, + _pr: String, + ) -> Result { + unimplemented!() + } + async fn post_mint_payment_request( &self, _mint_url: &Url, diff --git a/moksha-wallet/tests/tests.rs b/moksha-wallet/tests/tests.rs index e38ec9f0..75e1c2e8 100644 --- a/moksha-wallet/tests/tests.rs +++ b/moksha-wallet/tests/tests.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use async_trait::async_trait; use moksha_core::fixture::{read_fixture, read_fixture_as}; use moksha_core::model::{ - BlindedMessage, CheckFeesResponse, Keysets, MintKeyset, PaymentRequest, PostMeltResponse, - PostMintResponse, PostSplitResponse, Proofs, TokenV3, + BlindedMessage, CheckFeesResponse, InvoiceQuoteResult, Keysets, MintKeyset, PaymentRequest, + PostMeltResponse, PostMintResponse, PostSplitResponse, Proofs, TokenV3, }; use moksha_wallet::localstore::sqlite::SqliteLocalStore; use moksha_wallet::localstore::LocalStore; @@ -51,6 +51,14 @@ impl Client for MockClient { Ok(self.split_response.clone()) } + async fn get_melt_tokens( + &self, + _mint_url: &Url, + _pr: String, + ) -> Result { + unimplemented!() + } + async fn post_mint_payment_request( &self, _mint_url: &Url, From 949dfd7b06d6b94e49bc8aeb15a932cac751681c Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Tue, 17 Oct 2023 09:47:02 +0200 Subject: [PATCH 6/7] fix: stablesats get btcprice from galoy --- moksha-mint/src/lightning/stablesats.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs index 195391bc..052b5f67 100644 --- a/moksha-mint/src/lightning/stablesats.rs +++ b/moksha-mint/src/lightning/stablesats.rs @@ -93,6 +93,24 @@ impl StablesatsLightning { Ok(response.text().await?) } + + async fn get_btc_price(&self) -> Result { + let query = r#"{"query":"query btcPrice {btcPrice { base currencyUnit formattedAmount offset}}","variables":{}}"#; + + let response = self + .make_gqlpost(query) + .await + .map_err(|err| MokshaMintError::PayInvoice("payment_request".to_string(), err))?; // FIXME + + let response: serde_json::Value = serde_json::from_str(&response).unwrap(); + let formatted_amount = response["data"]["btcPrice"]["formattedAmount"] + .as_str() + .unwrap() + .to_owned(); + + let btc_price = formatted_amount.parse::().unwrap(); // FIXME + Ok(btc_price / 100.0) + } } #[async_trait] @@ -111,16 +129,12 @@ impl Lightning for StablesatsLightning { .await .map_err(|err| MokshaMintError::PayInvoice("payment_request".to_string(), err))?; - println!("response: {:?}", response.clone()); - let response: serde_json::Value = serde_json::from_str(&response).unwrap(); let status = response["data"]["lnInvoicePaymentStatus"]["status"] .as_str() .unwrap() .to_owned(); - println!("invoice paid status: {}", status.clone()); - Ok(status == "PAID") } @@ -128,7 +142,7 @@ impl Lightning for StablesatsLightning { let inv: Bolt11Invoice = self.decode_invoice(pr.clone()).await.unwrap(); let invoice_amount_sat = inv.amount_milli_satoshis().unwrap() / 1_000; - let btc_price_usd = 27915.68 / 100_000_000.0; + let btc_price_usd = self.get_btc_price().await?; let price_in_usd_cents = (btc_price_usd * invoice_amount_sat as f64) * 100.0; Ok(InvoiceQuoteResult { From d2749245a287c05bbca8dcef59e0f72b7269ff9d Mon Sep 17 00:00:00 2001 From: ngutech21 Date: Tue, 17 Oct 2023 13:27:04 +0200 Subject: [PATCH 7/7] chore: cleanup --- moksha-mint/src/lib.rs | 5 ----- moksha-mint/src/lightning/stablesats.rs | 7 ------- 2 files changed, 12 deletions(-) diff --git a/moksha-mint/src/lib.rs b/moksha-mint/src/lib.rs index 916bbe09..328b2ce8 100644 --- a/moksha-mint/src/lib.rs +++ b/moksha-mint/src/lib.rs @@ -264,12 +264,7 @@ async fn get_melt( Path(invoice): Path, State(mint): State, ) -> Result, MokshaMintError> { - println!(">>>>>>>>>>>>>>>> melt2"); - let quote = mint.lightning.get_quote(invoice.to_owned()).await?; - - println!("quote: {:?}", "e); - Ok(Json(InvoiceQuoteResult { amount_in_cent: quote.amount_in_cent, })) diff --git a/moksha-mint/src/lightning/stablesats.rs b/moksha-mint/src/lightning/stablesats.rs index 052b5f67..a2f3d97a 100644 --- a/moksha-mint/src/lightning/stablesats.rs +++ b/moksha-mint/src/lightning/stablesats.rs @@ -168,8 +168,6 @@ impl Lightning for StablesatsLightning { .await .map_err(|err| MokshaMintError::PayInvoice("payment_request".to_string(), err))?; // FIXME - println!("response: {:?}", response.clone()); - let response: serde_json::Value = serde_json::from_str(&response).unwrap(); let payment_request = response["data"]["lnUsdInvoiceCreate"]["invoice"]["paymentRequest"] .as_str() @@ -184,8 +182,6 @@ impl Lightning for StablesatsLightning { .as_u64() .unwrap(); - println!("sats {}", sats); - Ok(CreateInvoiceResult { payment_hash: payment_hash.as_bytes().to_vec(), payment_request, @@ -208,14 +204,11 @@ impl Lightning for StablesatsLightning { serde_json::to_string(&input).map_err(MokshaMintError::Serialization)? ); - println!("query: {}", query.clone()); let response = self .make_gqlpost(&query) .await .map_err(|err| MokshaMintError::PayInvoice(payment_request.clone(), err))?; - println!("response: {:?}", response.clone()); - let response: serde_json::Value = serde_json::from_str(&response).unwrap(); let status = response["data"]["lnInvoicePaymentSend"]["status"] .as_str()