From 0a98df583205de0ddb5f745857371cd4b0ca1378 Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Wed, 27 Sep 2023 17:36:20 -0700 Subject: [PATCH] Project import generated by Copybara. GitOrigin-RevId: e8ca93608cf9baae92f8d548828bbc214565fcb5 --- Cargo.toml | 1 - README.md | 1 - copy.bara.sky | 2 +- .../Cargo.toml | 1 - .../src/main.rs | 14 +- lightspark-remote-signing/src/lib.rs | 1 + lightspark/CHANGELOG.md | 4 + lightspark/Cargo.toml | 16 +- lightspark/README.md | 4 +- lightspark/examples/rk_test.rs | 41 ++ lightspark/src/client.rs | 87 +-- lightspark/src/error.rs | 10 +- lightspark/src/lib.rs | 10 +- lightspark/src/objects/account.rs | 79 +-- .../objects/account_to_nodes_connection.rs | 6 +- lightspark/src/objects/channel.rs | 16 +- lightspark/src/objects/graph_node.rs | 9 +- lightspark/src/objects/incoming_payment.rs | 9 +- lightspark/src/objects/invoice_data.rs | 5 +- lightspark/src/objects/lightspark_node.rs | 10 +- .../src/objects/lightspark_node_with_o_s_k.rs | 30 +- .../lightspark_node_with_remote_signing.rs | 30 +- lightspark/src/objects/outgoing_payment.rs | 13 +- .../src/objects/outgoing_payment_attempt.rs | 9 +- lightspark/src/objects/wallet.rs | 30 +- lightspark/src/objects/withdrawal_request.rs | 16 +- lightspark/src/request/requester.rs | 109 ++-- lightspark/src/types/graphql_requester.rs | 13 + lightspark/src/types/mod.rs | 1 + uma/Cargo.toml | 28 - uma/LICENSE | 201 ------- uma/README.md | 3 - uma/src/currency.rs | 16 - uma/src/kyc_status.rs | 18 - uma/src/lib.rs | 14 - uma/src/payer_data.rs | 145 ----- uma/src/protocol.rs | 292 ----------- uma/src/public_key_cache.rs | 59 --- uma/src/uma.rs | 495 ------------------ uma/src/uma_test.rs | 285 ---------- uma/src/version.rs | 102 ---- 41 files changed, 267 insertions(+), 1968 deletions(-) create mode 100644 lightspark/src/types/graphql_requester.rs delete mode 100644 uma/Cargo.toml delete mode 100644 uma/LICENSE delete mode 100644 uma/README.md delete mode 100644 uma/src/currency.rs delete mode 100644 uma/src/kyc_status.rs delete mode 100644 uma/src/lib.rs delete mode 100644 uma/src/payer_data.rs delete mode 100644 uma/src/protocol.rs delete mode 100644 uma/src/public_key_cache.rs delete mode 100644 uma/src/uma.rs delete mode 100644 uma/src/uma_test.rs delete mode 100644 uma/src/version.rs diff --git a/Cargo.toml b/Cargo.toml index 67bab4d..beb6d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,5 @@ resolver = "2" members = [ "lightspark", "lightspark-remote-signing", - "uma", "examples/*", ] \ No newline at end of file diff --git a/README.md b/README.md index 2c768fe..0b0597d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ The Lightspark Rust SDK provides a convenient way to interact with the Lightspar The rust-sdk consists of multiple crates that can be picked at your convenience: - `lightspark`: The main crate that contains the SDK. - `lightspark-remote-signing`: The SDK for handling remote signing webhook handler. -- `uma`: The UMA protocol implementation. - `example`: Examples that shows you how to use the SDK. ## License diff --git a/copy.bara.sky b/copy.bara.sky index 1caf98f..fc5cbbb 100644 --- a/copy.bara.sky +++ b/copy.bara.sky @@ -46,7 +46,7 @@ core.workflow( origin_files = glob( ["python-sdk/**", "copy.bara.sky"], - exclude = ["python-sdk/examples/internal_example.py"], + exclude = ["python-sdk/examples/internal_example.py", "python-sdk/RELEASE-internal.md"], ), authoring = authoring.pass_thru("Lightspark Eng "), diff --git a/examples/lightspark-remote-signing-server/Cargo.toml b/examples/lightspark-remote-signing-server/Cargo.toml index 25bc47f..6f438c5 100644 --- a/examples/lightspark-remote-signing-server/Cargo.toml +++ b/examples/lightspark-remote-signing-server/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lightspark = "0.6.3" lightspark-remote-signing = { path = "../../lightspark-remote-signing" } tokio = "1.32.0" actix-web = "4.4.0" diff --git a/examples/lightspark-remote-signing-server/src/main.rs b/examples/lightspark-remote-signing-server/src/main.rs index 40cb1e5..a808173 100644 --- a/examples/lightspark-remote-signing-server/src/main.rs +++ b/examples/lightspark-remote-signing-server/src/main.rs @@ -1,6 +1,6 @@ use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder}; use futures_util::StreamExt as _; -use lightspark::{ +use lightspark_remote_signing::lightspark::{ key::Secp256k1SigningKey, request::auth_provider::AccountAuthProvider, webhooks::WebhookEvent, }; use lightspark_remote_signing::{ @@ -24,14 +24,20 @@ async fn webhook_handler( data: web::Data, ) -> impl Responder { let headers = req.headers(); - let signature = headers.get(lightspark::webhooks::SIGNATURE_HEADER).unwrap(); + let signature = headers + .get(lightspark_remote_signing::lightspark::webhooks::SIGNATURE_HEADER) + .unwrap(); let mut bytes = web::BytesMut::new(); while let Some(item) = body.next().await { bytes.extend_from_slice(&item.unwrap()); } let auth = AccountAuthProvider::new(data.api_client_id.clone(), data.api_client_secret.clone()); - let client = lightspark::client::LightsparkClient::::new(auth).unwrap(); + let client = lightspark_remote_signing::lightspark::client::LightsparkClient::< + Secp256k1SigningKey, + >::new(auth) + .unwrap(); + // client.requester.set_base_url(data.api_endpoint.clone()); let seed = Seed::new(hex::decode(data.master_seed_hex.clone()).unwrap()); let signer = @@ -63,7 +69,7 @@ async fn main() -> std::io::Result<()> { .service(ping) .service(webhook_handler) }) - .bind(("127.0.0.1", 8080))? + .bind(("0.0.0.0", 8080))? .run() .await } diff --git a/lightspark-remote-signing/src/lib.rs b/lightspark-remote-signing/src/lib.rs index f0a2b07..1f0359b 100644 --- a/lightspark-remote-signing/src/lib.rs +++ b/lightspark-remote-signing/src/lib.rs @@ -9,6 +9,7 @@ //! This crates also provides a `signer::LightsparkSigner` that can be used to sign transactions. use std::fmt; +pub extern crate lightspark; pub mod handler; pub mod response; pub mod signer; diff --git a/lightspark/CHANGELOG.md b/lightspark/CHANGELOG.md index 59c93cf..f49b515 100644 --- a/lightspark/CHANGELOG.md +++ b/lightspark/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# v0.7.0 +- Refine requester. +- Separate crate into features. + # v0.6.4 - Remove all openssl dependencies. diff --git a/lightspark/Cargo.toml b/lightspark/Cargo.toml index f76e7f5..bc976e9 100644 --- a/lightspark/Cargo.toml +++ b/lightspark/Cargo.toml @@ -2,7 +2,7 @@ name = "lightspark" description = "Lightspark Rust SDK" authors = ["Lightspark Group, Inc. "] -version = "0.6.4" +version = "0.7.0" edition = "2021" documentation = "https://app.lightspark.com/docs/sdk" homepage = "https://www.lightspark.com/" @@ -10,12 +10,18 @@ repository = "https://github.com/lightsparkdev/lightspark-rs" license = "Apache-2.0" readme = "README.md" +[features] +default = ["base", "objects", "client"] +base = [] +objects = ["base"] +webhooks = ["base", "objects"] +client = ["base", "objects", "dep:reqwest"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -reqwest = { version = "0.11", default-features = false, features = ["blocking", "json"] } +reqwest = { version = "0.11", features = ["blocking", "json"], optional = true } futures = "0.3" -tokio = { version = "1.12.0", features = ["full"] } base64 = "0.21.0" serde_json = "1.0.94" serde = { version = "1.0.155", features = ["derive"] } @@ -33,3 +39,7 @@ bitcoin = "0.30.1" pbkdf2 = "0.12.2" rsa = { version ="0.9.2", features = ["sha2"] } cbc = "0.1.2" +async-trait = "0.1.73" + +[dev-dependencies] +tokio = { version = "1.12.0", features = ["full"] } diff --git a/lightspark/README.md b/lightspark/README.md index 72e2300..9a0d6be 100644 --- a/lightspark/README.md +++ b/lightspark/README.md @@ -1,8 +1,8 @@ -# Lightspark Rust SDK - v0.6.4 +# Lightspark Rust SDK - v0.7.0 The Lightspark Rust SDK provides a convenient way to interact with the Lightspark services from applications written in the Rust language. -***WARNING: This SDK is in version 0.6.0 (active development). It means that its APIs may not be fully stable. Please expect that changes to the APIs may happen until we move to v1.0.0.*** +***WARNING: This SDK is in version 0.7.0 (active development). It means that its APIs may not be fully stable. Please expect that changes to the APIs may happen until we move to v1.0.0.*** ## Documentation diff --git a/lightspark/examples/rk_test.rs b/lightspark/examples/rk_test.rs index 178926c..7335f96 100644 --- a/lightspark/examples/rk_test.rs +++ b/lightspark/examples/rk_test.rs @@ -5,9 +5,11 @@ use lightspark::{ async fn create_invoice() { let api_id = std::env::var("RK_API_CLIENT_ID").unwrap(); let api_token = std::env::var("RK_API_CLIENT_SECRET").unwrap(); + let endpoint = std::env::var("RK_API_ENDPOINT").unwrap(); let auth = AccountAuthProvider::new(api_id.to_string(), api_token.to_string()); let mut client = LightsparkClient::::new(auth).unwrap(); + client.requester.set_base_url(Some(endpoint)); let node_id = std::env::var("RK_NODE_ID").unwrap(); @@ -35,7 +37,46 @@ async fn create_invoice() { println!("Payment response: {:?}", response.unwrap().id); } +async fn test_payment() { + let api_id = std::env::var("RK_API_CLIENT_ID").unwrap(); + let api_token = std::env::var("RK_API_CLIENT_SECRET").unwrap(); + let endpoint = std::env::var("RK_API_ENDPOINT").unwrap(); + + let auth = AccountAuthProvider::new(api_id.to_string(), api_token.to_string()); + let mut client = LightsparkClient::::new(auth).unwrap(); + client.requester.set_base_url(Some(endpoint)); + + let node_id = std::env::var("RK_NODE_ID").unwrap(); + + let _ = client.fund_node(&node_id, 1000000).await; + let master_seed = std::env::var("RK_MASTER_SEED_HEX").unwrap(); + let _ = client.provide_master_seed( + &node_id, + hex::decode(master_seed).unwrap(), + lightspark::objects::bitcoin_network::BitcoinNetwork::Mainnet, + ); + + println!("API ID: {:?}", api_id); + println!("API Token: {:?}", api_token); + println!("Node ID: {:?}", node_id); + + let account = client.get_current_account().await.unwrap(); + println!("Account: {:?}", account.name); + + let invoice = client + .create_test_mode_invoice(&node_id, 10000, Some("test"), None) + .await; + let payment_request = invoice.unwrap().replace("\"", ""); + println!("Invoice created: {:?}", payment_request); + + let response = client + .pay_invoice(&node_id, &payment_request, 100, None, 1000) + .await; + println!("Payment response: {:?}", response.unwrap().id); +} + #[tokio::main] async fn main() { create_invoice().await; + test_payment().await; } diff --git a/lightspark/src/client.rs b/lightspark/src/client.rs index 6eeba6e..e88a476 100644 --- a/lightspark/src/client.rs +++ b/lightspark/src/client.rs @@ -29,8 +29,9 @@ use crate::objects::{api_token, outgoing_payment}; use crate::objects::{bitcoin_network, withdrawal_request}; use crate::objects::{fee_estimate, lightning_fee_estimate_output}; use crate::request::auth_provider::AuthProvider; -use crate::request::requester::{Requester, RequesterError}; +use crate::request::requester::Requester; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; const SIGNING_KEY_PATH: &str = "m/5"; @@ -95,11 +96,7 @@ impl LightsparkClient { variables.insert("bitcoin_network", bitcoin_network); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let json = self - .requester - .execute_graphql(&query, Some(value)) - .await - .map_err(Error::ClientError)?; + let json = self.requester.execute_graphql(&query, Some(value)).await?; let result = serde_json::from_value(json["bitcoin_fee_estimate"].clone()) .map_err(Error::JsonError)?; Ok(result) @@ -138,11 +135,7 @@ impl LightsparkClient { variables.insert("amount_msats", amount_msats.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let json = self - .requester - .execute_graphql(&query, Some(value)) - .await - .map_err(Error::ClientError)?; + let json = self.requester.execute_graphql(&query, Some(value)).await?; let result: LightningFeeEstimateOutput = serde_json::from_value(json["lightning_fee_estimate_for_node"].clone()) .map_err(Error::JsonError)?; @@ -179,11 +172,7 @@ impl LightsparkClient { variables.insert("amount_msats", amount_msats.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let json = self - .requester - .execute_graphql(&query, Some(value)) - .await - .map_err(Error::ClientError)?; + let json = self.requester.execute_graphql(&query, Some(value)).await?; let result: LightningFeeEstimateOutput = serde_json::from_value(json["lightning_fee_estimate_for_node"].clone()) .map_err(Error::JsonError)?; @@ -203,11 +192,7 @@ impl LightsparkClient { account::FRAGMENT ); - let json = self - .requester - .execute_graphql(&query, None) - .await - .map_err(Error::ClientError)?; + let json = self.requester.execute_graphql(&query, None).await?; let result = serde_json::from_value(json["current_account"].clone()).map_err(Error::JsonError)?; Ok(result) @@ -254,8 +239,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["create_api_token"]["api_token"].clone()) .map_err(Error::JsonError)?; @@ -286,8 +270,7 @@ impl LightsparkClient { let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; self.requester .execute_graphql(operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; Ok(()) } @@ -302,8 +285,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(T::get_entity_query().as_str(), Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["entity"].clone()).map_err(Error::JsonError)?; Ok(result) @@ -350,8 +332,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["create_invoice"]["invoice"].clone()) .map_err(Error::JsonError)?; @@ -400,8 +381,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["create_lnurl_invoice"]["invoice"].clone()) .map_err(Error::JsonError)?; @@ -438,8 +418,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["fund_node"]["amount"].clone()) .map_err(Error::JsonError)?; @@ -487,8 +466,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["decoded_payment_request"].clone()) .map_err(Error::JsonError)?; @@ -521,19 +499,14 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let encrypted_key = json["entity"]["encrypted_signing_private_key"]["encrypted_value"] .as_str() - .ok_or(Error::ClientError(RequesterError::GraphqlError( - "missing encrypted_value".to_owned(), - )))?; + .ok_or(Error::GraphqlError("missing encrypted_value".to_owned()))?; let cipher = json["entity"]["encrypted_signing_private_key"]["cipher"] .as_str() - .ok_or(Error::ClientError(RequesterError::GraphqlError( - "missing cipher".to_owned(), - )))?; + .ok_or(Error::GraphqlError("missing cipher".to_owned()))?; let decrypted_private_key = decrypt_private_key(cipher, encrypted_key, node_password) .map_err(Error::CryptoError)?; @@ -592,8 +565,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql_signing(&operation, Some(value), Some(signing_key)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["pay_invoice"]["payment"].clone()) .map_err(Error::JsonError)?; @@ -648,8 +620,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql_signing(&operation, Some(value), Some(signing_key)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["send_payment"]["payment"].clone()) .map_err(Error::JsonError)?; @@ -665,8 +636,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; Ok(json) } @@ -678,8 +648,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(operation, Some(variables)) - .await - .map_err(Error::ClientError)?; + .await?; Ok(json) } @@ -702,15 +671,12 @@ impl LightsparkClient { let json = self .requester .execute_graphql(&operation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; if let Some(result) = json["create_node_wallet_address"]["wallet_address"].as_str() { Ok(result.to_owned()) } else { - Err(Error::ClientError(RequesterError::GraphqlError( - "missing wallet_address".to_owned(), - ))) + Err(Error::GraphqlError("missing wallet_address".to_owned())) } } @@ -756,8 +722,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql_signing(&operation, Some(value), Some(signing_key)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["request_withdrawal"]["request"].clone()) .map_err(Error::JsonError)?; @@ -799,8 +764,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql(mutation, Some(value)) - .await - .map_err(Error::ClientError)?; + .await?; let result = json["create_test_mode_invoice"]["encoded_payment_request"].clone(); Ok(result.to_string()) @@ -848,8 +812,7 @@ impl LightsparkClient { let json = self .requester .execute_graphql_signing(&mutation, Some(value), Some(signing_key)) - .await - .map_err(Error::ClientError)?; + .await?; let result = serde_json::from_value(json["create_test_mode_payment"]["payment"].clone()) .map_err(Error::JsonError)?; diff --git a/lightspark/src/error.rs b/lightspark/src/error.rs index 651b405..42a81b4 100644 --- a/lightspark/src/error.rs +++ b/lightspark/src/error.rs @@ -2,13 +2,15 @@ use std::fmt; -use crate::{crypto::CryptoError, request::requester::RequesterError}; +use crate::crypto::CryptoError; #[derive(Debug)] pub enum Error { + ReqwestError(String), + GraphqlError(String), + InvalidHeaderValue, ClientCreationError(String), JsonError(serde_json::Error), - ClientError(RequesterError), ConversionError(serde_json::Error), CryptoError(CryptoError), WebhookSignatureError, @@ -18,9 +20,11 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + Self::ReqwestError(err) => write!(f, "Reqwest error {}", err), + Self::GraphqlError(err) => write!(f, "Graphql error {}", err), + Self::InvalidHeaderValue => write!(f, "Invalid header value"), Self::ClientCreationError(err) => write!(f, "Client creation error {}", err), Self::JsonError(err) => write!(f, "JSON Parser error {}", err), - Self::ClientError(err) => write!(f, "Client error {}", err), Self::ConversionError(err) => write!(f, "Parameter conversion error {}", err), Self::CryptoError(err) => write!(f, "Crypto error {}", err), Self::WebhookSignatureError => { diff --git a/lightspark/src/lib.rs b/lightspark/src/lib.rs index ec017a0..cd7924f 100644 --- a/lightspark/src/lib.rs +++ b/lightspark/src/lib.rs @@ -28,13 +28,21 @@ //! See more examples in examples/example.rs //! /// The version of this library. -pub const VERSION: &str = "0.6.4"; +pub const VERSION: &str = "0.7.0"; +#[cfg(feature = "client")] pub mod client; +#[cfg(feature = "base")] pub mod crypto; +#[cfg(feature = "base")] pub mod error; +#[cfg(feature = "base")] pub mod key; +#[cfg(feature = "objects")] pub mod objects; +#[cfg(feature = "client")] pub mod request; +#[cfg(feature = "base")] pub mod types; +#[cfg(feature = "webhooks")] pub mod webhooks; diff --git a/lightspark/src/objects/account.rs b/lightspark/src/objects/account.rs index d3a9def..ae5fb18 100644 --- a/lightspark/src/objects/account.rs +++ b/lightspark/src/objects/account.rs @@ -14,9 +14,9 @@ use crate::objects::lightspark_node_owner::LightsparkNodeOwner; use crate::objects::transaction_failures::TransactionFailures; use crate::objects::transaction_status::TransactionStatus; use crate::objects::transaction_type::TransactionType; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -101,7 +101,7 @@ fragment AccountFragment on Account { impl Account { pub async fn get_api_tokens( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, ) -> Result { @@ -137,10 +137,7 @@ impl Account { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["api_tokens"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -148,7 +145,7 @@ impl Account { pub async fn get_blockchain_balance( &self, - requester: &Requester, + requester: &impl GraphQLRequester, bitcoin_networks: Option>, node_ids: Option>, ) -> Result, Error> { @@ -215,10 +212,7 @@ impl Account { variables.insert("node_ids", node_ids.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["blockchain_balance"].clone(); let result = if json.is_null() { None @@ -230,7 +224,7 @@ impl Account { pub async fn get_conductivity( &self, - requester: &Requester, + requester: &impl GraphQLRequester, bitcoin_networks: Option>, node_ids: Option>, ) -> Result, Error> { @@ -247,10 +241,7 @@ impl Account { variables.insert("node_ids", node_ids.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["conductivity"].clone(); let result = json.as_i64(); Ok(result) @@ -258,7 +249,7 @@ impl Account { pub async fn get_local_balance( &self, - requester: &Requester, + requester: &impl GraphQLRequester, bitcoin_networks: Option>, node_ids: Option>, ) -> Result, Error> { @@ -282,10 +273,7 @@ impl Account { variables.insert("node_ids", node_ids.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["local_balance"].clone(); let result = if json.is_null() { None @@ -297,7 +285,7 @@ impl Account { pub async fn get_nodes( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, bitcoin_networks: Option>, node_ids: Option>, @@ -536,10 +524,7 @@ impl Account { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["nodes"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -547,7 +532,7 @@ impl Account { pub async fn get_remote_balance( &self, - requester: &Requester, + requester: &impl GraphQLRequester, bitcoin_networks: Option>, node_ids: Option>, ) -> Result, Error> { @@ -571,10 +556,7 @@ impl Account { variables.insert("node_ids", node_ids.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["remote_balance"].clone(); let result = if json.is_null() { None @@ -586,7 +568,7 @@ impl Account { pub async fn get_uptime_percentage( &self, - requester: &Requester, + requester: &impl GraphQLRequester, after_date: Option>, before_date: Option>, bitcoin_networks: Option>, @@ -607,10 +589,7 @@ impl Account { variables.insert("node_ids", node_ids.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["uptime_percentage"].clone(); let result = json.as_i64(); Ok(result) @@ -618,7 +597,7 @@ impl Account { pub async fn get_channels( &self, - requester: &Requester, + requester: &impl GraphQLRequester, bitcoin_network: BitcoinNetwork, lightning_node_id: Option, after_date: Option>, @@ -738,10 +717,7 @@ impl Account { variables.insert("first", first.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["channels"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -750,7 +726,7 @@ impl Account { #[allow(clippy::too_many_arguments)] pub async fn get_transactions( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, types: Option>, @@ -1308,10 +1284,7 @@ impl Account { ); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["transactions"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -1320,7 +1293,7 @@ impl Account { #[allow(clippy::too_many_arguments)] pub async fn get_payment_requests( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, after_date: Option>, @@ -1610,10 +1583,7 @@ impl Account { variables.insert("lightning_node_id", lightning_node_id.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["payment_requests"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -1621,7 +1591,7 @@ impl Account { pub async fn get_wallets( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, third_party_ids: Option>, @@ -1689,10 +1659,7 @@ impl Account { variables.insert("third_party_ids", third_party_ids.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["wallets"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/account_to_nodes_connection.rs b/lightspark/src/objects/account_to_nodes_connection.rs index 71dc3dc..6a13f4b 100644 --- a/lightspark/src/objects/account_to_nodes_connection.rs +++ b/lightspark/src/objects/account_to_nodes_connection.rs @@ -1,11 +1,11 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved use crate::objects::connection::Connection; -use crate::objects::lightspark_node::LightsparkNodeEnum; -use serde::Deserialize; - use crate::objects::page_info::PageInfo; +use serde::Deserialize; use std::vec::Vec; +use crate::objects::lightspark_node::LightsparkNodeEnum; + /// A connection between an account and the nodes it manages. #[derive(Clone, Deserialize)] pub struct AccountToNodesConnection { diff --git a/lightspark/src/objects/channel.rs b/lightspark/src/objects/channel.rs index e725091..5592446 100644 --- a/lightspark/src/objects/channel.rs +++ b/lightspark/src/objects/channel.rs @@ -6,10 +6,10 @@ use crate::objects::channel_to_transactions_connection::ChannelToTransactionsCon use crate::objects::currency_amount::CurrencyAmount; use crate::objects::entity::Entity; use crate::objects::transaction_type::TransactionType; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -231,7 +231,7 @@ fragment ChannelFragment on Channel { impl Channel { pub async fn get_uptime_percentage( &self, - requester: &Requester, + requester: &impl GraphQLRequester, after_date: Option>, before_date: Option>, ) -> Result, Error> { @@ -248,10 +248,7 @@ impl Channel { variables.insert("before_date", before_date.map(|dt| dt.to_rfc3339()).into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["uptime_percentage"].clone(); let result = json.as_i64(); Ok(result) @@ -259,7 +256,7 @@ impl Channel { pub async fn get_transactions( &self, - requester: &Requester, + requester: &impl GraphQLRequester, types: Option>, after_date: Option>, before_date: Option>, @@ -305,10 +302,7 @@ impl Channel { variables.insert("before_date", before_date.map(|dt| dt.to_rfc3339()).into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["transactions"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/graph_node.rs b/lightspark/src/objects/graph_node.rs index fce8b15..de83156 100644 --- a/lightspark/src/objects/graph_node.rs +++ b/lightspark/src/objects/graph_node.rs @@ -5,9 +5,9 @@ use crate::objects::entity::Entity; use crate::objects::node::Node; use crate::objects::node_address_type::NodeAddressType; use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -147,7 +147,7 @@ fragment GraphNodeFragment on GraphNode { impl GraphNode { pub async fn get_addresses( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, types: Option>, ) -> Result { @@ -172,10 +172,7 @@ impl GraphNode { variables.insert("types", types.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["addresses"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/incoming_payment.rs b/lightspark/src/objects/incoming_payment.rs index 3aac007..f182669 100644 --- a/lightspark/src/objects/incoming_payment.rs +++ b/lightspark/src/objects/incoming_payment.rs @@ -8,11 +8,11 @@ use crate::objects::lightning_transaction::LightningTransaction; use crate::objects::post_transaction_data::PostTransactionData; use crate::objects::transaction::Transaction; use crate::objects::transaction_status::TransactionStatus; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::custom_date_formats::custom_date_format_option; use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -178,7 +178,7 @@ fragment IncomingPaymentFragment on IncomingPayment { impl IncomingPayment { pub async fn get_attempts( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, statuses: Option>, after: Option, @@ -226,10 +226,7 @@ impl IncomingPayment { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["attempts"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/invoice_data.rs b/lightspark/src/objects/invoice_data.rs index b5dfb36..18ee5b9 100644 --- a/lightspark/src/objects/invoice_data.rs +++ b/lightspark/src/objects/invoice_data.rs @@ -1,12 +1,11 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::bitcoin_network::BitcoinNetwork; use crate::objects::currency_amount::CurrencyAmount; use crate::objects::node::NodeEnum; use crate::objects::payment_request_data::PaymentRequestData; -use serde::Deserialize; - -use crate::objects::bitcoin_network::BitcoinNetwork; use crate::types::custom_date_formats::custom_date_format; use chrono::{DateTime, Utc}; +use serde::Deserialize; /// This object represents the data associated with a BOLT #11 invoice. You can retrieve this object to receive the relevant data associated with a specific invoice. #[derive(Clone, Deserialize)] diff --git a/lightspark/src/objects/lightspark_node.rs b/lightspark/src/objects/lightspark_node.rs index a62d523..5e60496 100644 --- a/lightspark/src/objects/lightspark_node.rs +++ b/lightspark/src/objects/lightspark_node.rs @@ -1,15 +1,15 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::objects::entity::Entity; +use crate::objects::node::Node; + use super::lightspark_node_with_o_s_k::LightsparkNodeWithOSK; +use super::lightspark_node_with_remote_signing::LightsparkNodeWithRemoteSigning; use crate::objects::blockchain_balance::BlockchainBalance; use crate::objects::currency_amount::CurrencyAmount; -use crate::objects::entity::Entity; use crate::objects::lightspark_node_status::LightsparkNodeStatus; -use crate::objects::node::Node; +use crate::types::entity_wrapper::EntityWrapper; use serde::{Deserialize, Deserializer}; use serde_json::Value; - -use super::lightspark_node_with_remote_signing::LightsparkNodeWithRemoteSigning; -use crate::types::entity_wrapper::EntityWrapper; use std::vec::Vec; pub trait LightsparkNode: Node + Entity { diff --git a/lightspark/src/objects/lightspark_node_with_o_s_k.rs b/lightspark/src/objects/lightspark_node_with_o_s_k.rs index 31f27a6..d1734ee 100644 --- a/lightspark/src/objects/lightspark_node_with_o_s_k.rs +++ b/lightspark/src/objects/lightspark_node_with_o_s_k.rs @@ -1,27 +1,27 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::error::Error; use crate::objects::bitcoin_network::BitcoinNetwork; use crate::objects::blockchain_balance::BlockchainBalance; -use crate::objects::channel_status::ChannelStatus; -use crate::objects::currency_amount::CurrencyAmount; use crate::objects::entity::Entity; use crate::objects::lightspark_node::LightsparkNode; +use crate::objects::lightspark_node_status::LightsparkNodeStatus; use crate::objects::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; +use crate::objects::node::Node; use crate::objects::node_address_type::NodeAddressType; +use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; use crate::objects::secret::Secret; use crate::types::custom_date_formats::custom_date_format; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; +use serde_json::Value; use std::collections::HashMap; use std::vec::Vec; -use crate::error::Error; -use crate::objects::lightspark_node_status::LightsparkNodeStatus; -use crate::objects::node::Node; -use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; -use crate::request::requester::Requester; +use crate::objects::channel_status::ChannelStatus; +use crate::objects::currency_amount::CurrencyAmount; use crate::types::entity_wrapper::EntityWrapper; -use serde_json::Value; /// This is a Lightspark node with OSK. #[derive(Clone, Deserialize)] @@ -337,7 +337,7 @@ fragment LightsparkNodeWithOSKFragment on LightsparkNodeWithOSK { impl LightsparkNodeWithOSK { pub async fn get_addresses( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, types: Option>, ) -> Result { @@ -362,10 +362,7 @@ impl LightsparkNodeWithOSK { variables.insert("types", types.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["addresses"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -373,7 +370,7 @@ impl LightsparkNodeWithOSK { pub async fn get_channels( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, statuses: Option>, after: Option, @@ -496,10 +493,7 @@ impl LightsparkNodeWithOSK { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["channels"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/lightspark_node_with_remote_signing.rs b/lightspark/src/objects/lightspark_node_with_remote_signing.rs index a619ca0..ad7c3af 100644 --- a/lightspark/src/objects/lightspark_node_with_remote_signing.rs +++ b/lightspark/src/objects/lightspark_node_with_remote_signing.rs @@ -1,26 +1,26 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::error::Error; use crate::objects::bitcoin_network::BitcoinNetwork; use crate::objects::blockchain_balance::BlockchainBalance; -use crate::objects::channel_status::ChannelStatus; -use crate::objects::currency_amount::CurrencyAmount; use crate::objects::entity::Entity; use crate::objects::lightspark_node::LightsparkNode; +use crate::objects::lightspark_node_status::LightsparkNodeStatus; use crate::objects::lightspark_node_to_channels_connection::LightsparkNodeToChannelsConnection; +use crate::objects::node::Node; use crate::objects::node_address_type::NodeAddressType; +use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; use crate::types::custom_date_formats::custom_date_format; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; +use serde_json::Value; use std::collections::HashMap; use std::vec::Vec; -use crate::error::Error; -use crate::objects::lightspark_node_status::LightsparkNodeStatus; -use crate::objects::node::Node; -use crate::objects::node_to_addresses_connection::NodeToAddressesConnection; -use crate::request::requester::Requester; +use crate::objects::channel_status::ChannelStatus; +use crate::objects::currency_amount::CurrencyAmount; use crate::types::entity_wrapper::EntityWrapper; -use serde_json::Value; /// This is a Lightspark node with remote signing. #[derive(Clone, Deserialize)] @@ -327,7 +327,7 @@ fragment LightsparkNodeWithRemoteSigningFragment on LightsparkNodeWithRemoteSign impl LightsparkNodeWithRemoteSigning { pub async fn get_addresses( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, types: Option>, ) -> Result { @@ -352,10 +352,7 @@ impl LightsparkNodeWithRemoteSigning { variables.insert("types", types.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["addresses"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -363,7 +360,7 @@ impl LightsparkNodeWithRemoteSigning { pub async fn get_channels( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, statuses: Option>, after: Option, @@ -486,10 +483,7 @@ impl LightsparkNodeWithRemoteSigning { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["channels"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/outgoing_payment.rs b/lightspark/src/objects/outgoing_payment.rs index f2a709b..69a9118 100644 --- a/lightspark/src/objects/outgoing_payment.rs +++ b/lightspark/src/objects/outgoing_payment.rs @@ -1,5 +1,8 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved +use crate::types::graphql_requester::GraphQLRequester; use serde::Deserialize; +use serde_json::Value; +use std::vec::Vec; use crate::error::Error; use crate::objects::currency_amount::CurrencyAmount; @@ -12,15 +15,12 @@ use crate::objects::post_transaction_data::PostTransactionData; use crate::objects::rich_text::RichText; use crate::objects::transaction::Transaction; use crate::objects::transaction_status::TransactionStatus; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::custom_date_formats::custom_date_format_option; use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; use chrono::{DateTime, Utc}; -use serde_json::Value; use std::collections::HashMap; -use std::vec::Vec; /// This object represents a Lightning Network payment sent from a Lightspark Node. You can retrieve this object to receive payment related information about any payment sent from your Lightspark Node on the Lightning Network. #[derive(Clone, Deserialize)] @@ -450,7 +450,7 @@ fragment OutgoingPaymentFragment on OutgoingPayment { impl OutgoingPayment { pub async fn get_attempts( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, ) -> Result { @@ -506,10 +506,7 @@ impl OutgoingPayment { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["attempts"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/outgoing_payment_attempt.rs b/lightspark/src/objects/outgoing_payment_attempt.rs index 5e7f51b..ae2d20c 100644 --- a/lightspark/src/objects/outgoing_payment_attempt.rs +++ b/lightspark/src/objects/outgoing_payment_attempt.rs @@ -5,11 +5,11 @@ use crate::objects::entity::Entity; use crate::objects::htlc_attempt_failure_code::HtlcAttemptFailureCode; use crate::objects::outgoing_payment_attempt_status::OutgoingPaymentAttemptStatus; use crate::objects::outgoing_payment_attempt_to_hops_connection::OutgoingPaymentAttemptToHopsConnection; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::custom_date_formats::custom_date_format_option; use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -142,7 +142,7 @@ fragment OutgoingPaymentAttemptFragment on OutgoingPaymentAttempt { impl OutgoingPaymentAttempt { pub async fn get_hops( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, ) -> Result { @@ -197,10 +197,7 @@ impl OutgoingPaymentAttempt { variables.insert("after", after.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["hops"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/wallet.rs b/lightspark/src/objects/wallet.rs index cac903c..49aef0e 100644 --- a/lightspark/src/objects/wallet.rs +++ b/lightspark/src/objects/wallet.rs @@ -9,11 +9,11 @@ use crate::objects::transaction_type::TransactionType; use crate::objects::wallet_status::WalletStatus; use crate::objects::wallet_to_payment_requests_connection::WalletToPaymentRequestsConnection; use crate::objects::wallet_to_transactions_connection::WalletToTransactionsConnection; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::custom_date_formats::custom_date_format_option; use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -147,7 +147,7 @@ impl Wallet { #[allow(clippy::too_many_arguments)] pub async fn get_transactions( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, created_after_date: Option>, @@ -678,10 +678,7 @@ impl Wallet { variables.insert("types", types.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["transactions"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -689,7 +686,7 @@ impl Wallet { pub async fn get_payment_requests( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, after: Option, created_after_date: Option>, @@ -981,10 +978,7 @@ impl Wallet { ); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["payment_requests"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -992,7 +986,7 @@ impl Wallet { pub async fn get_total_amount_received( &self, - requester: &Requester, + requester: &impl GraphQLRequester, created_after_date: Option>, created_before_date: Option>, ) -> Result { @@ -1022,10 +1016,7 @@ impl Wallet { ); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["total_amount_received"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -1033,7 +1024,7 @@ impl Wallet { pub async fn get_total_amount_sent( &self, - requester: &Requester, + requester: &impl GraphQLRequester, created_after_date: Option>, created_before_date: Option>, ) -> Result { @@ -1063,10 +1054,7 @@ impl Wallet { ); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["total_amount_sent"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/objects/withdrawal_request.rs b/lightspark/src/objects/withdrawal_request.rs index a161922..7543159 100644 --- a/lightspark/src/objects/withdrawal_request.rs +++ b/lightspark/src/objects/withdrawal_request.rs @@ -6,11 +6,11 @@ use crate::objects::withdrawal_mode::WithdrawalMode; use crate::objects::withdrawal_request_status::WithdrawalRequestStatus; use crate::objects::withdrawal_request_to_channel_closing_transactions_connection::WithdrawalRequestToChannelClosingTransactionsConnection; use crate::objects::withdrawal_request_to_channel_opening_transactions_connection::WithdrawalRequestToChannelOpeningTransactionsConnection; -use crate::request::requester::Requester; use crate::types::custom_date_formats::custom_date_format; use crate::types::custom_date_formats::custom_date_format_option; use crate::types::entity_wrapper::EntityWrapper; use crate::types::get_entity::GetEntity; +use crate::types::graphql_requester::GraphQLRequester; use chrono::{DateTime, Utc}; use serde::Deserialize; use serde_json::Value; @@ -137,7 +137,7 @@ fragment WithdrawalRequestFragment on WithdrawalRequest { impl WithdrawalRequest { pub async fn get_channel_closing_transactions( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, ) -> Result { let query = "query FetchWithdrawalRequestToChannelClosingTransactionsConnection($entity_id: ID!, $first: Int) { @@ -194,10 +194,7 @@ impl WithdrawalRequest { variables.insert("first", first.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["channel_closing_transactions"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) @@ -205,7 +202,7 @@ impl WithdrawalRequest { pub async fn get_channel_opening_transactions( &self, - requester: &Requester, + requester: &impl GraphQLRequester, first: Option, ) -> Result { let query = "query FetchWithdrawalRequestToChannelOpeningTransactionsConnection($entity_id: ID!, $first: Int) { @@ -262,10 +259,7 @@ impl WithdrawalRequest { variables.insert("first", first.into()); let value = serde_json::to_value(variables).map_err(Error::ConversionError)?; - let result = requester - .execute_graphql(query, Some(value)) - .await - .map_err(Error::ClientError)?; + let result = requester.execute_graphql(query, Some(value)).await?; let json = result["entity"]["channel_opening_transactions"].clone(); let result = serde_json::from_value(json).map_err(Error::JsonError)?; Ok(result) diff --git a/lightspark/src/request/requester.rs b/lightspark/src/request/requester.rs index 89c91f1..4a6b3b0 100644 --- a/lightspark/src/request/requester.rs +++ b/lightspark/src/request/requester.rs @@ -1,14 +1,15 @@ // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved -use std::{env, fmt}; +use std::env; use crate::{ - crypto::CryptoError, error::Error, key::{OperationSigningKey, RSASigningKey}, request::auth_provider::AuthProvider, + types::graphql_requester::GraphQLRequester, VERSION, }; +use async_trait::async_trait; use chrono::{Duration, Utc}; use os_version::detect; use rand::RngCore; @@ -20,36 +21,10 @@ use serde_json::{json, to_string, Value}; const DEFAULT_BASE_URL: &str = "https://api.lightspark.com/graphql/server/2023-09-13"; -#[derive(Debug)] -pub enum RequesterError { - ReqwestError(reqwest::Error), - GraphqlError(String), - SigningError(CryptoError), - InvalidHeaderValue, -} - -impl fmt::Display for RequesterError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::ReqwestError(err) => write!(f, "Network error {}", err), - Self::GraphqlError(err) => write!(f, "Graphql error {}", err), - Self::SigningError(err) => write!(f, "Signing error {}", err), - Self::InvalidHeaderValue => write!(f, "Invalid header value"), - } - } -} - -impl std::error::Error for RequesterError {} - -impl From for RequesterError { - fn from(error: reqwest::Error) -> Self { - RequesterError::ReqwestError(error) - } -} - /// A Requester struct for graphql operations. pub struct Requester { client: reqwest::Client, + base_url: Option, } fn user_agent() -> String { @@ -70,6 +45,26 @@ fn user_agent() -> String { ) } +#[async_trait] +impl GraphQLRequester for Requester { + /// This executes a graphql operaion without signing. + /// + /// Returns the json result for the operation. + /// + /// # Arguments + /// + /// * `operation` - graphql query or mutation to be executed. + /// * `variables` - variable for the graphql. + async fn execute_graphql( + &self, + operation: &str, + variables: Option, + ) -> Result { + self.execute_graphql_signing::(operation, variables, None) + .await + } +} + impl Requester { pub fn new(auth_provider: T) -> Result { let mut headers = reqwest::header::HeaderMap::new(); @@ -96,7 +91,10 @@ impl Requester { headers.insert("X-Lightspark-SDK", user_agent_header_value); match reqwest::Client::builder().default_headers(headers).build() { - Ok(client) => Ok(Requester { client }), + Ok(client) => Ok(Requester { + client, + base_url: None, + }), Err(err) => Err(Error::ClientCreationError(format!( "reqwest client creation error: {}", err @@ -104,21 +102,8 @@ impl Requester { } } - /// This executes a graphql operaion without signing. - /// - /// Returns the json result for the operation. - /// - /// # Arguments - /// - /// * `operation` - graphql query or mutation to be executed. - /// * `variables` - variable for the graphql. - pub async fn execute_graphql( - &self, - operation: &str, - variables: Option, - ) -> Result { - self.execute_graphql_signing::(operation, variables, None) - .await + pub fn set_base_url(&mut self, base_url: Option) { + self.base_url = base_url; } /// This executes a graphql operaion. If the signing_key is provided, the operation will be @@ -136,9 +121,9 @@ impl Requester { operation: &str, variables: Option, signing_key: Option, - ) -> Result { + ) -> Result { let re = regex::Regex::new(r"\s*(?:query|mutation)\s+(\w+)").map_err(|_| { - RequesterError::GraphqlError("The operation is not a query or a mutation".to_owned()) + Error::GraphqlError("The operation is not a query or a mutation".to_owned()) })?; let operation_name = re .captures(operation) @@ -173,37 +158,43 @@ impl Requester { } if let Some(key) = signing_key { - let json_string = to_string(&body) - .map_err(|_| RequesterError::GraphqlError("Body malformat.".to_owned()))?; + let json_string = + to_string(&body).map_err(|_| Error::GraphqlError("Body malformat.".to_owned()))?; let payload: Vec = json_string.into_bytes(); - let signing = key - .sign_payload(&payload) - .map_err(RequesterError::SigningError)?; + let signing = key.sign_payload(&payload).map_err(Error::CryptoError)?; headers.insert( "X-Lightspark-Signing", - HeaderValue::from_str(signing.as_str()) - .map_err(|_| RequesterError::InvalidHeaderValue)?, + HeaderValue::from_str(signing.as_str()).map_err(|_| Error::InvalidHeaderValue)?, ); } + let url = match &self.base_url { + Some(base_url) => base_url.clone(), + None => DEFAULT_BASE_URL.to_owned(), + }; + let response = self .client - .post(DEFAULT_BASE_URL) + .post(url) .headers(headers) .json(&body) .send() - .await?; + .await + .map_err(|e| Error::ReqwestError(e.to_string()))?; - let response_json: Value = response.json().await?; + let response_json: Value = response + .json() + .await + .map_err(|e| Error::ReqwestError(e.to_string()))?; if let Some(_errors) = response_json.get("errors") { // Check if there are any errors in the response - Err(RequesterError::GraphqlError(_errors.to_string())) + Err(Error::GraphqlError(_errors.to_string())) } else if let Some(data) = response_json.get("data") { // Return the data field of the response as json Ok(data.clone()) } else { - Err(RequesterError::GraphqlError("missing data".to_owned())) + Err(Error::GraphqlError("missing data".to_owned())) } } } diff --git a/lightspark/src/types/graphql_requester.rs b/lightspark/src/types/graphql_requester.rs new file mode 100644 index 0000000..41aa2b7 --- /dev/null +++ b/lightspark/src/types/graphql_requester.rs @@ -0,0 +1,13 @@ +use async_trait::async_trait; +use serde_json::Value; + +use crate::error::Error; + +#[async_trait] +pub trait GraphQLRequester: Send + Sync { + async fn execute_graphql( + &self, + operation: &str, + variables: Option, + ) -> Result; +} diff --git a/lightspark/src/types/mod.rs b/lightspark/src/types/mod.rs index 7e27e32..79c4397 100644 --- a/lightspark/src/types/mod.rs +++ b/lightspark/src/types/mod.rs @@ -3,3 +3,4 @@ pub mod custom_date_formats; pub mod entity_wrapper; pub mod get_entity; +pub mod graphql_requester; diff --git a/uma/Cargo.toml b/uma/Cargo.toml deleted file mode 100644 index 7981e3a..0000000 --- a/uma/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "uma" -description = "UMA Protocol SDK for Rust" -authors = ["Lightspark Group, Inc. "] -version = "0.1.0" -edition = "2021" -documentation = "https://app.lightspark.com/docs/uma-sdk" -homepage = "https://www.lightspark.com/" -repository = "https://github.com/lightsparkdev/lightspark-rs" -license = "Apache-2.0" -readme = "README.md" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -reqwest = { version = "0.11", features = ["blocking", "json"] } -futures = "0.3" -tokio = { version = "1.12.0", features = ["full"] } -base64 = "0.21.0" -serde_json = "1.0.94" -serde = { version = "1.0.155", features = ["derive"] } -sha2 = "0.10.7" -rand_core = "0.6.4" -ecies = "0.2.6" -bitcoin = "0.30.1" -url = "2.4.1" -chrono = "0.4.24" -hex = "0.4.3" diff --git a/uma/LICENSE b/uma/LICENSE deleted file mode 100644 index cbb91e4..0000000 --- a/uma/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 Lightspark Group, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/uma/README.md b/uma/README.md deleted file mode 100644 index 9f493b1..0000000 --- a/uma/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# uma - -The UMA protocol implementation for Rust! Check out the [full documentation](https://app.lightspark.com/docs/uma-sdk/introduction) for more info. \ No newline at end of file diff --git a/uma/src/currency.rs b/uma/src/currency.rs deleted file mode 100644 index 1ecb67b..0000000 --- a/uma/src/currency.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct Currency { - pub code: String, - pub name: String, - pub symbol: String, - #[serde(rename = "multiplier")] - pub millisatoshi_per_unit: i64, - #[serde(rename = "minSendable")] - pub min_sendable: i64, - #[serde(rename = "maxSendable")] - pub max_sendable: i64, -} diff --git a/uma/src/kyc_status.rs b/uma/src/kyc_status.rs deleted file mode 100644 index f9f5a62..0000000 --- a/uma/src/kyc_status.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum KycStatus { - #[serde(rename = "UNKNOWN")] - KycStatusUnknown, - - #[serde(rename = "NOT_VERIFIED")] - KycStatusNotVerified, - - #[serde(rename = "PENDING")] - KycStatusPending, - - #[serde(rename = "VERIFIED")] - KycStatusVerified, -} diff --git a/uma/src/lib.rs b/uma/src/lib.rs deleted file mode 100644 index 05d8f6d..0000000 --- a/uma/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -/// The UMA protocol implementation for Rust. Check out -/// the full documentation: for more info. -pub mod currency; -pub mod kyc_status; -pub mod payer_data; -pub mod protocol; -pub mod public_key_cache; -pub mod uma; -pub mod version; - -#[cfg(test)] -mod uma_test; diff --git a/uma/src/payer_data.rs b/uma/src/payer_data.rs deleted file mode 100644 index 426c625..0000000 --- a/uma/src/payer_data.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use crate::kyc_status::KycStatus; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PayerDataOptions { - pub name_required: bool, - pub email_required: bool, - pub compliance_required: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -struct PayerDataOption { - mandatory: bool, -} - -impl Serialize for PayerDataOptions { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - use serde::ser::SerializeStruct; - - let mut state = serializer.serialize_struct("PayerDataOptions", 4)?; - state.serialize_field("identifier", &PayerDataOption { mandatory: true })?; - state.serialize_field( - "name", - &PayerDataOption { - mandatory: self.name_required, - }, - )?; - state.serialize_field( - "email", - &PayerDataOption { - mandatory: self.email_required, - }, - )?; - state.serialize_field( - "compliance", - &PayerDataOption { - mandatory: self.compliance_required, - }, - )?; - state.end() - } -} - -impl<'de> Deserialize<'de> for PayerDataOptions { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de::{MapAccess, Visitor}; - - struct PayerDataOptionsVisitor; - - impl<'de> Visitor<'de> for PayerDataOptionsVisitor { - type Value = PayerDataOptions; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct PayerDataOptions") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut name_required = false; - let mut email_required = false; - let mut compliance_required = false; - - while let Some(key) = map.next_key::()? { - match key.as_str() { - "name" => { - let option: PayerDataOption = map.next_value()?; - name_required = option.mandatory; - } - "email" => { - let option: PayerDataOption = map.next_value()?; - email_required = option.mandatory; - } - "compliance" => { - let option: PayerDataOption = map.next_value()?; - compliance_required = option.mandatory; - } - _ => { - let _: PayerDataOption = map.next_value()?; - } - } - } - - Ok(PayerDataOptions { - name_required, - email_required, - compliance_required, - }) - } - } - - deserializer.deserialize_map(PayerDataOptionsVisitor) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct PayerData { - pub name: Option, - pub email: Option, - pub identifier: String, - pub compliance: CompliancePayerData, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct CompliancePayerData { - /// utxos is the list of UTXOs of the sender's channels that might be used to fund the payment. - pub utxos: Vec, - - /// node_pubkey is the public key of the sender's node if known. - #[serde(rename = "nodePubkey")] - pub node_pubkey: Option, - - /// kyc_status indicates whether VASP1 has KYC information about the sender. - #[serde(rename = "kycStatus")] - pub kyc_status: KycStatus, - - /// encrypted_travel_rule_info is the travel rule information of the sender. This is encrypted - /// with the receiver's public encryption key. - #[serde(rename = "encryptedTravelRuleInfo")] - pub encrypted_travel_rule_info: Option, - - // signature is the hex-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). - pub signature: String, - - #[serde(rename = "signatureNonce")] - pub signature_nonce: String, - - #[serde(rename = "signatureTimestamp")] - pub signature_timestamp: i64, - - /// UtxoCallback is the URL that the receiver will call to send UTXOs of the channel that the - /// receiver used to receive the payment once it completes. - #[serde(rename = "utxoCallback")] - pub utxo_callback: String, -} diff --git a/uma/src/protocol.rs b/uma/src/protocol.rs deleted file mode 100644 index 156a6a1..0000000 --- a/uma/src/protocol.rs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use std::fmt; - -use serde::{Deserialize, Serialize}; -use url::Url; - -use crate::kyc_status::KycStatus; - -use super::{ - currency::Currency, - payer_data::{PayerData, PayerDataOptions}, -}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Error { - InvalidReceiverAddress, - InvalidUrl, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InvalidReceiverAddress => write!(f, "Invalid receiver address"), - Self::InvalidUrl => write!(f, "Invalid URL"), - } - } -} - -/// LnurlpRequest is the first request in the UMA protocol. -/// It is sent by the VASP that is sending the payment to find out information about the receiver. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct LnurlpRequest { - /// receiver_address is the address of the user at VASP2 that is receiving the payment. - pub receiver_address: String, - - /// nonce is a random string that is used to prevent replay attacks. - pub nonce: String, - - /// signature is the hex-encoded signature of sha256(receiver_address|nonce|timestamp). - pub signature: String, - - /// is_subject_to_travel_rule indicates VASP1 is a financial institution that requires travel - /// rule information. - pub is_subject_to_travel_rule: bool, - - /// vasp_domain is the domain of the VASP that is sending the payment. It will be used by VASP2 - /// to fetch the public keys of VASP1. - pub vasp_domain: String, - - /// timestamp is the unix timestamp of when the request was sent. Used in the signature. - pub timestamp: i64, - - /// uma_version is the version of the UMA protocol that VASP1 prefers to use for this - /// transaction. For the version negotiation flow, - /// see https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png - pub uma_version: String, -} - -impl LnurlpRequest { - pub fn encode_to_url(&self) -> Result { - let receiver_address_parts: Vec<&str> = self.receiver_address.split('@').collect(); - if receiver_address_parts.len() != 2 { - return Err(Error::InvalidReceiverAddress); - } - let scheme = if receiver_address_parts[1].starts_with("localhost:") { - "http" - } else { - "https" - }; - let mut lnurlp_url = Url::parse(&format!( - "{}://{}/.well-known/lnurlp/{}", - scheme, receiver_address_parts[1], receiver_address_parts[0] - )) - .map_err(|_| Error::InvalidUrl)?; - - lnurlp_url - .query_pairs_mut() - .append_pair("signature", &self.signature) - .append_pair("vaspDomain", &self.vasp_domain) - .append_pair("nonce", &self.nonce) - .append_pair( - "isSubjectToTravelRule", - &self.is_subject_to_travel_rule.to_string(), - ) - .append_pair("timestamp", &self.timestamp.to_string()) - .append_pair("umaVersion", &self.uma_version); - - Ok(lnurlp_url) - } - - pub fn signable_payload(&self) -> Vec { - let payload_string = format!( - "{}|{}|{}", - self.receiver_address, self.nonce, self.timestamp - ); - payload_string.into_bytes() - } -} - -/// LnurlpResponse is the response to the LnurlpRequest. -/// It is sent by the VASP that is receiving the payment to provide information to the sender about the receiver. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct LnurlpResponse { - pub tag: String, - pub callback: String, - - #[serde(rename = "minSendable")] - pub min_sendable: i64, - - #[serde(rename = "maxSendable")] - pub max_sendable: i64, - - #[serde(rename = "metadata")] - pub encoded_metadata: String, - - pub currencies: Vec, - - #[serde(rename = "payerData")] - pub required_payer_data: PayerDataOptions, - - pub compliance: LnurlComplianceResponse, - - /// UmaVersion is the version of the UMA protocol that VASP2 has chosen for this transaction - /// based on its own support and VASP1's specified preference in the LnurlpRequest. For the - /// version negotiation flow, see - /// https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png - #[serde(rename = "umaVersion")] - pub uma_version: String, -} - -/// LnurlComplianceResponse is the `compliance` field of the LnurlpResponse. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct LnurlComplianceResponse { - /// kyc_status indicates whether VASP2 has KYC information about the receiver. - #[serde(rename = "kycStatus")] - pub kyc_status: KycStatus, - - /// signature is the hex-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). - pub signature: String, - - /// nonce is a random string that is used to prevent replay attacks. - #[serde(rename = "signatureNonce")] - pub nonce: String, - - /// timestamp is the unix timestamp of when the request was sent. Used in the signature. - #[serde(rename = "signatureTimestamp")] - pub timestamp: i64, - - /// is_subject_to_travel_rule indicates whether VASP2 is a financial institution that requires travel rule information. - #[serde(rename = "isSubjectToTravelRule")] - pub is_subject_to_travel_rule: bool, - - /// receiver_identifier is the identifier of the receiver at VASP2. - #[serde(rename = "receiverIdentifier")] - pub receiver_identifier: String, -} - -impl LnurlpResponse { - pub fn signable_payload(&self) -> Vec { - let payload_string = format!( - "{}|{}|{}", - self.compliance.receiver_identifier, self.compliance.nonce, self.compliance.timestamp - ); - payload_string.into_bytes() - } -} - -/// PayRequest is the request sent by the sender to the receiver to retrieve an invoice. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PayRequest { - /// currency_code is the ISO 3-digit currency code that the receiver will receive for this - /// payment. - #[serde(rename = "currencyCode")] - pub currency_code: String, - - /// amount is the amount that the receiver will receive for this payment in the smallest unit of - /// the specified currency (i.e. cents for USD). - pub amount: i64, - - /// PayerData is the data that the sender will send to the receiver to identify themselves. - #[serde(rename = "payerData")] - pub payer_data: PayerData, -} - -impl PayRequest { - pub fn signable_payload(&self) -> Vec { - let payload_string = format!( - "{}|{}|{}", - self.payer_data.identifier, - self.payer_data.compliance.signature_nonce, - self.payer_data.compliance.signature_timestamp, - ); - payload_string.into_bytes() - } -} - -/// PayReqResponse is the response sent by the receiver to the sender to provide an invoice. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PayReqResponse { - /// encoded_invoice is the BOLT11 invoice that the sender will pay. - #[serde(rename = "pr")] - pub encoded_invoice: String, - - /// routes is usually just an empty list from legacy LNURL, which was replaced by route hints in - /// the BOLT11 invoice. - pub routes: Vec, - - pub compliance: PayReqResponseCompliance, - - #[serde(rename = "paymentInfo")] - pub payment_info: PayReqResponsePaymentInfo, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct Route { - pub pubkey: String, - pub path: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct Path { - pub pubkey: String, - pub fee: i64, - pub msatoshi: i64, - pub channel: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PayReqResponseCompliance { - /// node_pub_key is the public key of the receiver's node if known. - #[serde(rename = "nodePubKey")] - pub node_pub_key: Option, - - /// utxos is a list of UTXOs of channels over which the receiver will likely receive the - /// payment. - pub utxos: Vec, - - /// utxo_callback is the URL that the sender VASP will call to send UTXOs of the channel that - /// the sender used to send the payment once it completes. - #[serde(rename = "utxoCallback")] - pub utxo_callback: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PayReqResponsePaymentInfo { - /// CurrencyCode is the ISO 3-digit currency code that the receiver will receive for this - /// payment. - #[serde(rename = "currencyCode")] - pub currency_code: String, - - /// Multiplier is the conversion rate. It is the number of millisatoshis that the receiver will - /// receive for 1 unit of the specified currency. - #[serde(rename = "multiplier")] - pub multiplier: i64, - - /// ExchangeFeesMillisatoshi is the fees charged (in millisats) by the receiving VASP for this - /// transaction. This is separate from the Multiplier. - #[serde(rename = "exchangeFeesMillisatoshi")] - pub exchange_fees_millisatoshi: i64, -} - -/// PubKeyResponse is sent from a VASP to another VASP to provide its public keys. -/// It is the response to GET requests at `/.well-known/lnurlpubkey`. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct PubKeyResponse { - /// signing_pub_key is used to verify signatures from a VASP. - #[serde(rename = "signingPubKey")] - pub signing_pub_key: Vec, - - // encryption_pub_key is used to encrypt TR info sent to a VASP. - #[serde(rename = "encryptionPubKey")] - pub encryption_pub_key: Vec, - - // expiration_timestamp [Optional] Seconds since epoch at which these pub keys must be refreshed. - // They can be safely cached until this expiration (or forever if null). - #[serde(rename = "expirationTimestamp")] - pub expiration_timestamp: Option, -} - -/// UtxoWithAmount is a pair of utxo and amount transferred over that corresponding channel. -/// It can be used to register payment for KYT. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct UtxoWithAmount { - /// utxo The utxo of the channel over which the payment went through in the format of - /// :. - pub utxo: String, - - /// Amount The amount of funds transferred in the payment in mSats. - #[serde(rename = "amountMsats")] - pub amount: i64, -} diff --git a/uma/src/public_key_cache.rs b/uma/src/public_key_cache.rs deleted file mode 100644 index c360587..0000000 --- a/uma/src/public_key_cache.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use super::protocol::PubKeyResponse; -use std::collections::HashMap; - -/// PublicKeyCache is an interface for a cache of public keys for other VASPs. -/// -/// Implementations of this interface should be thread-safe. -pub trait PublicKeyCache { - /// fetch_public_key_for_vasp fetches the public key entry for a VASP if in the cache, otherwise - /// returns nil. - fn fetch_public_key_for_vasp(&self, vasp_domain: &str) -> Option<&PubKeyResponse>; - - /// add_public_key_for_vasp adds a public key entry for a VASP to the cache. - fn add_public_key_for_vasp(&mut self, vasp_domain: &str, public_key: &PubKeyResponse); - - /// remove_public_key_for_vasp removes a public key for a VASP from the cache. - fn remove_public_key_for_vasp(&mut self, vasp_domain: &str); - - /// clear clears the cache. - fn clear(&mut self); -} - -pub struct InMemoryPublicKeyCache { - cache: HashMap, -} - -impl Default for InMemoryPublicKeyCache { - fn default() -> Self { - Self::new() - } -} - -impl InMemoryPublicKeyCache { - pub fn new() -> InMemoryPublicKeyCache { - InMemoryPublicKeyCache { - cache: HashMap::new(), - } - } -} - -impl PublicKeyCache for InMemoryPublicKeyCache { - fn fetch_public_key_for_vasp(&self, vasp_domain: &str) -> Option<&PubKeyResponse> { - self.cache.get(vasp_domain) - } - - fn add_public_key_for_vasp(&mut self, vasp_domain: &str, public_key: &PubKeyResponse) { - self.cache - .insert(vasp_domain.to_string(), public_key.clone()); - } - - fn remove_public_key_for_vasp(&mut self, vasp_domain: &str) { - self.cache.remove(vasp_domain); - } - - fn clear(&mut self) { - self.cache.clear(); - } -} diff --git a/uma/src/uma.rs b/uma/src/uma.rs deleted file mode 100644 index f7094c6..0000000 --- a/uma/src/uma.rs +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use std::fmt; - -use bitcoin::secp256k1::{ - ecdsa::Signature, hashes::sha256, Message, PublicKey, Secp256k1, SecretKey, -}; -use rand_core::{OsRng, RngCore}; - -use crate::{ - currency::Currency, - kyc_status::KycStatus, - payer_data::{CompliancePayerData, PayerData, PayerDataOptions}, - protocol::{ - self, LnurlComplianceResponse, LnurlpRequest, LnurlpResponse, PayReqResponse, - PayReqResponseCompliance, PayReqResponsePaymentInfo, PayRequest, PubKeyResponse, - }, - public_key_cache, version, -}; - -#[derive(Debug)] -pub enum Error { - Secp256k1Error(bitcoin::secp256k1::Error), - EciesSecp256k1Error(ecies::SecpError), - SignatureFormatError, - InvalidSignature, - InvalidResponse, - ProtocolError(protocol::Error), - MissingUrlParam(String), - InvalidUrlPath, - InvalidHost, - InvalidData(serde_json::Error), - CreateInvoiceError(String), - InvalidUMAAddress, - InvalidVersion, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Secp256k1Error(err) => write!(f, "Secp256k1 error {}", err), - Self::EciesSecp256k1Error(err) => write!(f, "Ecies Secp256k1 error {}", err), - Self::SignatureFormatError => write!(f, "Signature format error"), - Self::InvalidSignature => write!(f, "Invalid signature"), - Self::InvalidResponse => write!(f, "Invalid response"), - Self::ProtocolError(err) => write!(f, "Protocol error {}", err), - Self::MissingUrlParam(param) => write!(f, "Missing URL param {}", param), - Self::InvalidUrlPath => write!(f, "Invalid URL path"), - Self::InvalidHost => write!(f, "Invalid host"), - Self::InvalidData(err) => write!(f, "Invalid data {}", err), - Self::CreateInvoiceError(err) => write!(f, "Create invoice error {}", err), - Self::InvalidUMAAddress => write!(f, "Invalid UMA address"), - Self::InvalidVersion => write!(f, "Invalid version"), - } - } -} - -/// Fetches the public key for another VASP. -/// -/// If the public key is not in the cache, it will be fetched from the VASP's domain. -/// The public key will be cached for future use. -/// -/// # Arguments -/// -/// * `vasp_domain` - the domain of the VASP. -/// * `cache` - the PublicKeyCache cache to use. You can use the InMemoryPublicKeyCache struct, or implement your own persistent cache with any storage type. -pub fn fetch_public_key_for_vasp( - vasp_domain: &str, - mut public_key_cache: T, -) -> Result -where - T: public_key_cache::PublicKeyCache, -{ - let publick_key = public_key_cache.fetch_public_key_for_vasp(vasp_domain); - if let Some(public_key) = publick_key { - return Ok(public_key.clone()); - } - - let scheme = match vasp_domain.starts_with("localhost:") { - true => "http", - false => "https", - }; - - let url = format!("{}//{}/.well-known/lnurlpubkey", scheme, vasp_domain); - let response = reqwest::blocking::get(url).map_err(|_| Error::InvalidResponse)?; - - if !response.status().is_success() { - return Err(Error::InvalidResponse); - } - - let bytes = response.bytes().map_err(|_| Error::InvalidResponse)?; - - let pubkey_response: PubKeyResponse = - serde_json::from_slice(&bytes).map_err(Error::InvalidData)?; - - public_key_cache.add_public_key_for_vasp(vasp_domain, &pubkey_response); - Ok(pubkey_response) -} - -pub fn generate_nonce() -> String { - OsRng.next_u64().to_string() -} - -fn sign_payload(payload: &[u8], private_key_bytes: &[u8]) -> Result { - let secp = Secp256k1::new(); - let sk = SecretKey::from_slice(private_key_bytes).map_err(Error::Secp256k1Error)?; - let msg = Message::from_hashed_data::(payload); - let signature = secp.sign_ecdsa(&msg, &sk); - let sig_string = hex::encode(signature.serialize_der()); - Ok(sig_string) -} - -fn verify_ecdsa(payload: &[u8], signature: &str, pub_key_bytes: &[u8]) -> Result<(), Error> { - let sig_bytes = hex::decode(signature).map_err(|_| Error::SignatureFormatError)?; - let secp = Secp256k1::new(); - let msg = Message::from_hashed_data::(payload); - let sig = Signature::from_der(&sig_bytes).map_err(Error::Secp256k1Error)?; - let pk = PublicKey::from_slice(pub_key_bytes).map_err(Error::Secp256k1Error)?; - secp.verify_ecdsa(&msg, &sig, &pk) - .map_err(|_| Error::InvalidSignature) -} - -/// Verifies the signature on a uma pay request based on the public key of the VASP making the request. -/// -/// # Arguments -/// -/// * `pay_req` - the signed query to verify. -/// * `other_vasp_pub_key` - the bytes of the signing public key of the VASP making this request. -pub fn verify_pay_req_signature( - pay_req: &PayRequest, - other_vasp_pub_key: &[u8], -) -> Result<(), Error> { - let payload = pay_req.signable_payload(); - verify_ecdsa( - &payload, - &pay_req.payer_data.compliance.signature, - other_vasp_pub_key, - ) -} - -/// Creates a signed uma request URL. -/// -/// # Arguments -/// -/// * `signing_private_key` - the private key of the VASP that is sending the payment. This will be used to sign the request. -/// * `receiver_address` - the address of the receiver of the payment (i.e. $bob@vasp2). -/// * `sender_vasp_domain` - the domain of the VASP that is sending the payment. It will be used by the receiver to fetch the public keys of the sender. -/// * `is_subject_to_travel_rule` - whether the sending VASP is a financial institution that requires travel rule information. -/// * `uma_version_override` - the version of the UMA protocol to use. If not specified, the latest version will be used. -pub fn get_signed_lnurlp_request_url( - signing_private_key: &[u8], - receiver_address: &str, - sender_vasp_domain: &str, - is_subject_to_travel_rule: bool, - uma_version_override: Option<&str>, -) -> Result { - let nonce = generate_nonce(); - let uma_version = match uma_version_override { - Some(version) => version.to_string(), - None => version::uma_protocol_version(), - }; - let mut unsigned_request = LnurlpRequest { - receiver_address: receiver_address.to_owned(), - nonce, - timestamp: chrono::Utc::now().timestamp(), - signature: "".to_owned(), - vasp_domain: sender_vasp_domain.to_owned(), - is_subject_to_travel_rule, - uma_version, - }; - - let sig = sign_payload(&unsigned_request.signable_payload(), signing_private_key)?; - unsigned_request.signature = sig; - - unsigned_request - .encode_to_url() - .map_err(Error::ProtocolError) -} - -/// Checks if the given URL is a valid UMA request. -pub fn is_uma_lnurl_query(url: &url::Url) -> bool { - parse_lnurlp_request(url).is_ok() -} - -/// Parses the message into an LnurlpRequest object. -/// -/// # Arguments -/// * `url` - the full URL of the uma request. -pub fn parse_lnurlp_request(url: &url::Url) -> Result { - let mut query = url.query_pairs(); - let signature = query - .find(|(key, _)| key == "signature") - .map(|(_, value)| value) - .ok_or(Error::MissingUrlParam("signature".to_string()))?; - - let mut query = url.query_pairs(); - let vasp_domain = query - .find(|(key, _)| key == "vaspDomain") - .map(|(_, value)| value) - .ok_or(Error::MissingUrlParam("vsapDomain".to_string()))?; - - let mut query = url.query_pairs(); - let nonce = query - .find(|(key, _)| key == "nonce") - .map(|(_, value)| value) - .ok_or(Error::MissingUrlParam("nonce".to_string()))?; - - let mut query = url.query_pairs(); - let is_subject_to_travel_rule = query - .find(|(key, _)| key == "isSubjectToTravelRule") - .map(|(_, value)| value.to_lowercase() == "true") - .unwrap_or(false); - - let mut query = url.query_pairs(); - let timestamp = query - .find(|(key, _)| key == "timestamp") - .map(|(_, value)| value.parse::()) - .ok_or(Error::MissingUrlParam("timestamp".to_string()))? - .map_err(|_| Error::MissingUrlParam("timestamp".to_string()))?; - - let mut query = url.query_pairs(); - let uma_version = query - .find(|(key, _)| key == "umaVersion") - .map(|(_, value)| value) - .ok_or(Error::MissingUrlParam("umaVersion".to_string()))?; - - let path_parts: Vec<&str> = url.path_segments().ok_or(Error::InvalidUrlPath)?.collect(); - if path_parts.len() != 3 || path_parts[0] != ".well-known" || path_parts[1] != "lnurlp" { - return Err(Error::InvalidUrlPath); - } - - let receiver_address = format!( - "{}@{}", - path_parts[2], - url.host_str().ok_or(Error::InvalidHost)? - ); - - Ok(LnurlpRequest { - vasp_domain: vasp_domain.into_owned(), - signature: signature.into_owned(), - receiver_address, - nonce: nonce.into_owned(), - timestamp, - is_subject_to_travel_rule, - uma_version: uma_version.into_owned(), - }) -} - -/// Verifies the signature on an uma Lnurlp query based on the public key of the VASP making the request. -/// -/// # Arguments -/// * `query` - the signed query to verify. -/// * `other_vasp_pub_key` - the bytes of the signing public key of the VASP making this request. -pub fn verify_uma_lnurl_query_signature( - query: LnurlpRequest, - other_vasp_pub_key: &[u8], -) -> Result<(), Error> { - verify_ecdsa( - &query.signable_payload(), - &query.signature, - other_vasp_pub_key, - ) -} - -#[allow(clippy::too_many_arguments)] -pub fn get_lnurlp_response( - query: &LnurlpRequest, - private_key_bytes: &[u8], - requires_travel_rule_info: bool, - callback: &str, - encoded_metadata: &str, - min_sendable_sats: i64, - max_sendable_sats: i64, - payer_data_options: &PayerDataOptions, - currency_options: &[Currency], - receiver_kyc_status: KycStatus, -) -> Result { - let compliance_response = get_signed_compliance_respionse( - query, - private_key_bytes, - requires_travel_rule_info, - receiver_kyc_status, - )?; - let uma_version = - version::select_lower_version(&query.uma_version, &version::uma_protocol_version()) - .map_err(|_| Error::InvalidVersion)?; - Ok(LnurlpResponse { - tag: "payRequest".to_string(), - callback: callback.to_string(), - min_sendable: min_sendable_sats, - max_sendable: max_sendable_sats, - encoded_metadata: encoded_metadata.to_string(), - currencies: currency_options.to_vec(), - required_payer_data: payer_data_options.clone(), - compliance: compliance_response, - uma_version, - }) -} - -fn get_signed_compliance_respionse( - query: &LnurlpRequest, - private_key_bytes: &[u8], - is_subject_to_travel_rule: bool, - receiver_kyc_status: KycStatus, -) -> Result { - let timestamp = chrono::Utc::now().timestamp(); - let nonce = generate_nonce(); - let payload_string = format!("{}|{}|{}", query.receiver_address, nonce, timestamp); - - let signature = sign_payload(payload_string.as_bytes(), private_key_bytes)?; - - Ok(LnurlComplianceResponse { - kyc_status: receiver_kyc_status, - signature, - nonce, - timestamp, - is_subject_to_travel_rule, - receiver_identifier: query.receiver_address.clone(), - }) -} - -/// Verifies the signature on an uma Lnurlp response based on the public key of the VASP making the request. -/// -/// # Arguments -/// * `response` - the signed response to verify. -/// * `other_vasp_pub_key` - the bytes of the signing public key of the VASP making this request. -pub fn verify_uma_lnurlp_response_signature( - response: LnurlpResponse, - other_vasp_pub_key: &[u8], -) -> Result<(), Error> { - let payload = response.signable_payload(); - verify_ecdsa(&payload, &response.compliance.signature, other_vasp_pub_key) -} - -pub fn parse_lnurlp_response(bytes: &[u8]) -> Result { - serde_json::from_slice(bytes).map_err(Error::InvalidData) -} - -/// Gets the domain of the VASP from an uma address. -pub fn get_vasp_domain_from_uma_address(uma_address: &str) -> Result { - let address_parts: Vec<&str> = uma_address.split('@').collect(); - if address_parts.len() != 2 { - Err(Error::InvalidUMAAddress) - } else { - Ok(address_parts[1].to_string()) - } -} - -/// Creates a signed uma pay request. -/// -/// # Arguments -/// * `receiver_encryption_pub_key` - the public key of the receiver of the payment. This will be used to encrypt the travel rule information. -/// * `sending_vasp_private_key` - the private key of the VASP that is sending the payment. This will be used to sign the request. -/// * `currency_code` - the currency code of the payment. -/// * `amount` - the amount of the payment in the smallest unit of the specified currency (i.e. cents for USD). -/// * `payer_identifier` - the identifier of the sender. For example, $alice@vasp1.com -/// * `payer_name` - the name of the sender. -/// * `payer_email` - the email of the sender. -/// * `tr_info` - the travel rule information to be encrypted. -/// * `payer_kyc_status` - the KYC status of the sender. -/// * `payer_uxtos` - the list of UTXOs of the sender's channels that might be used to fund the payment. -/// * `payer_node_pubkey` - If known, the public key of the sender's node. If supported by the receiving VASP's compliance provider, this will be used to pre-screen the sender's UTXOs for compliance purposes. -/// * `utxo_callback` - the URL that the receiver will use to fetch the sender's UTXOs. -#[allow(clippy::too_many_arguments)] -pub fn get_pay_request( - receiver_encryption_pub_key: &[u8], - sending_vasp_private_key: &[u8], - currency_code: &str, - amount: i64, - payer_identifier: &str, - payer_name: Option<&str>, - payer_email: Option<&str>, - tr_info: Option<&str>, - payer_kyc_status: KycStatus, - payer_uxtos: &[String], - payer_node_pubkey: Option<&str>, - utxo_callback: &str, -) -> Result { - let compliance_data = get_signed_compliance_payer_data( - receiver_encryption_pub_key, - sending_vasp_private_key, - payer_identifier, - tr_info, - payer_kyc_status, - payer_uxtos, - payer_node_pubkey, - utxo_callback, - )?; - Ok(PayRequest { - currency_code: currency_code.to_string(), - amount, - payer_data: PayerData { - name: payer_name.map(|s| s.to_string()), - email: payer_email.map(|s| s.to_string()), - identifier: payer_identifier.to_string(), - compliance: compliance_data, - }, - }) -} - -#[allow(clippy::too_many_arguments)] -fn get_signed_compliance_payer_data( - receiver_encryption_pub_key: &[u8], - sending_vasp_private_key: &[u8], - payer_identifier: &str, - tr_info: Option<&str>, - payer_kyc_status: KycStatus, - payer_uxtos: &[String], - payer_node_pubkey: Option<&str>, - utxo_callback: &str, -) -> Result { - let timestamp = chrono::Utc::now().timestamp(); - let nonce = generate_nonce(); - - let encrypted_tr_info = match tr_info { - Some(tr_info) => Some(encrypt_tr_info(tr_info, receiver_encryption_pub_key)?), - None => None, - }; - let payload_string = format!("{}|{}|{}", payer_identifier, nonce, timestamp); - let signature = sign_payload(payload_string.as_bytes(), sending_vasp_private_key)?; - - Ok(CompliancePayerData { - utxos: payer_uxtos.to_vec(), - node_pubkey: payer_node_pubkey.map(|s| s.to_string()), - kyc_status: payer_kyc_status, - encrypted_travel_rule_info: encrypted_tr_info, - signature, - signature_nonce: nonce, - signature_timestamp: timestamp, - utxo_callback: utxo_callback.to_string(), - }) -} - -fn encrypt_tr_info(tr_info: &str, receiver_encryption_pub_key: &[u8]) -> Result { - let cipher_text = ecies::encrypt(receiver_encryption_pub_key, tr_info.as_bytes()) - .map_err(Error::EciesSecp256k1Error)?; - Ok(hex::encode(cipher_text)) -} - -pub fn parse_pay_request(bytes: &[u8]) -> Result { - serde_json::from_slice(bytes).map_err(Error::InvalidData) -} - -pub trait UmaInvoiceCreator { - fn create_uma_invoice( - &self, - amount_msat: i64, - metadata: &str, - ) -> Result>; -} - -#[allow(clippy::too_many_arguments)] -pub fn get_pay_req_response( - query: &PayRequest, - invoice_creator: &T, - metadata: &str, - currency_code: &str, - conversion_rate: i64, - receiver_fees_millisats: i64, - receiver_channel_utxos: &[String], - receiver_node_pub_key: Option<&str>, - utxo_callback: &str, -) -> Result -where - T: UmaInvoiceCreator, -{ - let msats_amount = query.amount * conversion_rate + receiver_fees_millisats; - let encoded_payer_data = - serde_json::to_string(&query.payer_data).map_err(Error::InvalidData)?; - let encoded_invoice = invoice_creator - .create_uma_invoice( - msats_amount, - &format!("{}{{{}}}", metadata, encoded_payer_data), - ) - .map_err(|e| Error::CreateInvoiceError(e.to_string()))?; - - Ok(PayReqResponse { - encoded_invoice, - routes: [].to_vec(), - compliance: PayReqResponseCompliance { - node_pub_key: receiver_node_pub_key.map(|s| s.to_string()), - utxos: receiver_channel_utxos.to_vec(), - utxo_callback: utxo_callback.to_string(), - }, - payment_info: PayReqResponsePaymentInfo { - currency_code: currency_code.to_string(), - multiplier: conversion_rate, - exchange_fees_millisatoshi: receiver_fees_millisats, - }, - }) -} - -pub fn parse_pay_req_response(bytes: &[u8]) -> Result { - serde_json::from_slice(bytes).map_err(Error::InvalidData) -} diff --git a/uma/src/uma_test.rs b/uma/src/uma_test.rs deleted file mode 100644 index a395fa8..0000000 --- a/uma/src/uma_test.rs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -#[cfg(test)] -mod tests { - use ecies::utils::generate_keypair; - - use crate::uma::{ - get_lnurlp_response, get_pay_req_response, get_pay_request, get_signed_lnurlp_request_url, - is_uma_lnurl_query, parse_lnurlp_request, parse_lnurlp_response, parse_pay_req_response, - parse_pay_request, verify_pay_req_signature, verify_uma_lnurl_query_signature, - verify_uma_lnurlp_response_signature, UmaInvoiceCreator, - }; - - use crate::{currency::Currency, payer_data::PayerDataOptions, protocol::LnurlpRequest}; - - #[test] - fn test_parse() { - let timestamp = chrono::Utc::now().timestamp(); - let expected_query = LnurlpRequest { - receiver_address: "bob@vasp2.com".to_string(), - nonce: "12345".to_string(), - signature: "signature".to_string(), - is_subject_to_travel_rule: true, - vasp_domain: "vasp1.com".to_string(), - timestamp, - uma_version: "0.1".to_string(), - }; - - let url_string = format!("https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp={}",×tamp); - let url = url::Url::parse(&url_string).unwrap(); - - let query = parse_lnurlp_request(&url).unwrap(); - assert_eq!(query, expected_query); - } - - #[test] - fn test_is_uma_query_valid() { - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(is_uma_lnurl_query(&url)); - } - - #[test] - fn test_is_uma_query_missing_params() { - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&umaVersion=0.1&vaspDomain=vasp1.com×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - // isSubjectToTravelRule is optional - assert!(is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&umaVersion=0.1&vaspDomain=vasp1.com&isSubjectToTravelRule=true"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/.well-known/lnurlp/bob"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - } - - #[test] - fn test_is_uma_query_invalid_path() { - let url_string = "https://vasp2.com/.well-known/lnurla/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - - let url_string = "https://vasp2.com/?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - let url = url::Url::parse(url_string).unwrap(); - assert!(!is_uma_lnurl_query(&url)); - } - - #[test] - fn test_sign_and_verify_lnurlp_request() { - let (sk, pk) = generate_keypair(); - - let query_url = get_signed_lnurlp_request_url( - &sk.serialize(), - "$bob@vasp2.com", - "vasp1.com", - true, - None, - ) - .unwrap(); - - let query = parse_lnurlp_request(&query_url).unwrap(); - - let result = verify_uma_lnurl_query_signature(query, &pk.serialize()); - assert!(result.is_ok()); - } - - #[test] - fn test_sign_and_verify_lnurlp_request_invalid_signature() { - let (sk, _) = generate_keypair(); - - let query_url = get_signed_lnurlp_request_url( - &sk.serialize(), - "$bob@vasp2.com", - "vasp1.com", - true, - None, - ) - .unwrap(); - - let query = parse_lnurlp_request(&query_url).unwrap(); - - let (_, pk) = generate_keypair(); - let result = verify_uma_lnurl_query_signature(query, &pk.serialize()); - assert!(result.is_err()); - } - - #[test] - fn test_sign_and_verify_lnurlp_response() { - let (sk1, _) = generate_keypair(); - let (sk2, pk2) = generate_keypair(); - - let request = get_signed_lnurlp_request_url( - &sk1.serialize(), - "$bob@vasp2.com", - "vasp1.com", - true, - None, - ) - .unwrap(); - let query = parse_lnurlp_request(&request).unwrap(); - - let metadata = create_metadata_for_bob().unwrap(); - - let currency_options = [Currency { - code: "USD".to_string(), - name: "US Doller".to_string(), - symbol: "$".to_string(), - millisatoshi_per_unit: 34150, - min_sendable: 1, - max_sendable: 10000000, - }]; - - let response = get_lnurlp_response( - &query, - &sk2.serialize(), - true, - "https://vasp2.com/api/lnurl/payreq/$bob", - metadata.as_str(), - 1, - 10_000_000, - &PayerDataOptions { - name_required: false, - email_required: false, - compliance_required: true, - }, - ¤cy_options, - crate::kyc_status::KycStatus::KycStatusVerified, - ) - .unwrap(); - - let response_json = serde_json::to_vec(&response).unwrap(); - let response = parse_lnurlp_response(&response_json).unwrap(); - - let result = verify_uma_lnurlp_response_signature(response, &pk2.serialize()); - assert!(result.is_ok()); - } - - #[test] - fn test_pay_req_creation_and_parsing() { - let (sk1, pk1) = generate_keypair(); - let (sk2, pk2) = generate_keypair(); - - let payreq = get_pay_request( - &pk1.serialize(), - &sk2.serialize(), - "USD", - 1000, - "$alice@vasp1.com", - None, - None, - Some("some TR info for VASP2"), - crate::kyc_status::KycStatus::KycStatusVerified, - &[], - None, - "/api/lnurl/utxocallback?txid=1234", - ) - .unwrap(); - - let payreq_json = serde_json::to_vec(&payreq).unwrap(); - - let payreq = parse_pay_request(&payreq_json).unwrap(); - - let result = verify_pay_req_signature(&payreq, &pk2.serialize()); - assert!(result.is_ok()); - - let cipher_text = hex::decode( - payreq - .payer_data - .compliance - .encrypted_travel_rule_info - .unwrap(), - ) - .unwrap(); - let plain_text = ecies::decrypt(&sk1.serialize(), &cipher_text).unwrap(); - assert_eq!(plain_text, b"some TR info for VASP2"); - } - - #[test] - fn test_pay_req_response_and_parsing() { - let (_, pk1) = generate_keypair(); - let (sk2, _) = generate_keypair(); - - let payreq = get_pay_request( - &pk1.serialize(), - &sk2.serialize(), - "USD", - 1000, - "$alice@vasp1.com", - None, - None, - Some("some TR info for VASP2"), - crate::kyc_status::KycStatus::KycStatusVerified, - &[], - None, - "/api/lnurl/utxocallback?txid=1234", - ) - .unwrap(); - - let client = FakeInvoiceCreator {}; - - let metadata = create_metadata_for_bob().unwrap(); - - let response = get_pay_req_response( - &payreq, - &client, - &metadata, - "USD", - 34150, - 100_000, - &["abcdef12345".to_owned()], - None, - "/api/lnurl/utxocallback?txid=1234", - ) - .unwrap(); - - let response_json = serde_json::to_vec(&response).unwrap(); - - let result = parse_pay_req_response(&response_json); - assert!(result.is_ok()); - } - - struct FakeInvoiceCreator {} - - impl UmaInvoiceCreator for FakeInvoiceCreator { - fn create_uma_invoice( - &self, - _amount_msat: i64, - _metadata: &str, - ) -> Result> { - Ok("lntb100n1p0z9j".to_owned()) - } - } - - fn create_metadata_for_bob() -> Result { - let metadata = vec![ - vec!["text/plain", "Pay to vasp2.com user $bob"], - vec!["text/identifier", "$bob@vasp2.com"], - ]; - - let json_metadata = serde_json::to_string(&metadata)?; - Ok(json_metadata) - } -} diff --git a/uma/src/version.rs b/uma/src/version.rs deleted file mode 100644 index b022e44..0000000 --- a/uma/src/version.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -use crate::uma; - -const MAJOR_VERSION: i32 = 0; -const MINOR_VERSION: i32 = 1; - -pub fn uma_protocol_version() -> String { - format!("{}.{}", MAJOR_VERSION, MINOR_VERSION) -} - -pub struct ParsedVersion { - pub major: i32, - pub minor: i32, -} - -impl ParsedVersion { - pub fn new(version: &str) -> Result { - let parts: Vec<&str> = version.split('.').collect(); - if parts.len() != 2 { - Err(uma::Error::InvalidVersion) - } else { - let major = parts[0] - .parse::() - .map_err(|_| uma::Error::InvalidVersion)?; - let minor = parts[1] - .parse::() - .map_err(|_| uma::Error::InvalidVersion)?; - Ok(Self { major, minor }) - } - } - - pub fn string_value(&self) -> String { - format!("{}.{}", self.major, self.minor) - } -} - -pub fn get_supported_major_version() -> Vec { - // NOTE: In the future, we may want to support multiple major versions in the same SDK, but for - // now, this keeps things simple. - vec![MAJOR_VERSION] -} - -pub fn get_highest_supported_version_for_major_version( - major_version: &i32, -) -> Option { - if *major_version != MAJOR_VERSION { - None - } else { - ParsedVersion::new(&uma_protocol_version()).ok() - } -} - -pub fn select_highest_supported_version( - other_vasp_supported_major_versions: &[i32], -) -> Option { - let supported_version = get_supported_major_version(); - let mut highest_version: Option = None; - for other_vasp_major_version in other_vasp_supported_major_versions { - if !supported_version.contains(other_vasp_major_version) { - continue; - } - - match highest_version { - Some(ref v) => { - if *other_vasp_major_version > v.major { - highest_version = Some( - get_highest_supported_version_for_major_version(other_vasp_major_version) - .unwrap(), - ); - } - } - None => { - highest_version = Some( - get_highest_supported_version_for_major_version(other_vasp_major_version) - .unwrap(), - ); - } - } - } - - highest_version.map(|v| v.string_value()) -} - -pub fn select_lower_version(version1: &str, version2: &str) -> Result { - let v1 = ParsedVersion::new(version1)?; - let v2 = ParsedVersion::new(version2)?; - - if v1.major > v2.major || (v1.major == v2.major && v1.minor > v2.minor) { - Ok(version2.to_string()) - } else { - Ok(version1.to_string()) - } -} - -pub fn is_version_supported(version: &str) -> bool { - let parsed_version = match ParsedVersion::new(version) { - Ok(parsed_version) => parsed_version, - Err(_) => return false, - }; - get_supported_major_version().contains(&parsed_version.major) -}