From e3fda440f480bfc6ce599b6f6a7015ab40d53e5a Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Jan 2026 09:51:02 +0100 Subject: [PATCH 1/5] Switch from `reqwest` to `bitreq` HTTP client Replace the `reqwest` dependency with `bitreq`, a lighter HTTP(s) client that reduces code bloat and dependency overhead. Key changes: - Use `bitreq::Client` for connection pooling and reuse - Update all HTTP request handling to use bitreq's API - Remove `reqwest::header::HeaderMap` in favor of `HashMap` - Simplify `LnurlAuthToJwtProvider::new()` to no longer return a `Result` - Use `serde_json::from_slice()` directly for JSON parsing - Build script uses bitreq's blocking `send()` method Co-Authored-By: HAL 9000 Signed-off-by: Elias Rohrer --- Cargo.toml | 10 +++--- build.rs | 6 ++-- src/client.rs | 57 +++++++++++++++------------------ src/error.rs | 10 +++--- src/headers/lnurl_auth_jwt.rs | 60 +++++++++++++++++------------------ src/headers/mod.rs | 17 ---------- src/lib.rs | 2 +- tests/lnurl_auth_jwt_tests.rs | 6 ++-- tests/tests.rs | 14 ++++---- 9 files changed, 78 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2c2d48..a159908 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,20 +15,20 @@ build = "build.rs" [features] default = ["lnurl-auth", "sigs-auth"] -lnurl-auth = ["dep:bitcoin", "dep:url", "dep:serde", "dep:serde_json", "reqwest/json"] +lnurl-auth = ["dep:bitcoin", "dep:url", "dep:serde", "dep:serde_json"] sigs-auth = ["dep:bitcoin"] [dependencies] prost = "0.11.6" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } +bitreq = { version = "0.3", default-features = false, features = ["async-https"] } tokio = { version = "1", default-features = false, features = ["time"] } rand = "0.8.5" async-trait = "0.1.77" bitcoin = { version = "0.32.2", default-features = false, features = ["std", "rand-std"], optional = true } url = { version = "2.5.0", default-features = false, optional = true } -base64 = { version = "0.22", default-features = false} +base64 = { version = "0.22", default-features = false, features = ["std"]} serde = { version = "1.0.196", default-features = false, features = ["serde_derive"], optional = true } -serde_json = { version = "1.0.113", default-features = false, optional = true } +serde_json = { version = "1.0.113", default-features = false, features = ["std"], optional = true } bitcoin_hashes = "0.14.0" chacha20-poly1305 = "0.1.2" @@ -36,7 +36,7 @@ log = { version = "0.4.29", default-features = false, features = ["std"]} [target.'cfg(genproto)'.build-dependencies] prost-build = { version = "0.11.3" } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "blocking"] } +bitreq = { version = "0.3", default-features = false, features = ["std", "https"] } [dev-dependencies] mockito = "0.28.0" diff --git a/build.rs b/build.rs index 12db0a7..a94b5a1 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,8 @@ #[cfg(genproto)] extern crate prost_build; #[cfg(genproto)] +use std::io::Write; +#[cfg(genproto)] use std::{env, fs, fs::File, path::Path}; /// To generate updated proto objects: @@ -25,9 +27,9 @@ fn generate_protos() { #[cfg(genproto)] fn download_file(url: &str, save_to: &str) -> Result<(), Box> { - let mut response = reqwest::blocking::get(url)?; + let response = bitreq::get(url).send()?; fs::create_dir_all(Path::new(save_to).parent().unwrap())?; let mut out_file = File::create(save_to)?; - response.copy_to(&mut out_file)?; + out_file.write_all(&response.into_bytes())?; Ok(()) } diff --git a/src/client.rs b/src/client.rs index 9f6b04a..e7cf589 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,5 @@ +use bitreq::Client; use prost::Message; -use reqwest::header::CONTENT_TYPE; -use reqwest::Client; use std::collections::HashMap; use std::default::Default; use std::sync::Arc; @@ -8,7 +7,7 @@ use std::sync::Arc; use log::trace; use crate::error::VssError; -use crate::headers::{get_headermap, FixedHeaders, VssHeaderProvider}; +use crate::headers::{FixedHeaders, VssHeaderProvider}; use crate::types::{ DeleteObjectRequest, DeleteObjectResponse, GetObjectRequest, GetObjectResponse, ListKeyVersionsRequest, ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse, @@ -17,7 +16,9 @@ use crate::util::retry::{retry, RetryPolicy}; use crate::util::KeyValueVecKeyPrinter; const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; -const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); +const DEFAULT_TIMEOUT_SECS: u64 = 10; +const MAX_RESPONSE_BODY_SIZE: usize = 10 * 1024 * 1024; // 10 MiB +const DEFAULT_CLIENT_CAPACITY: usize = 10; /// Thin-client to access a hosted instance of Versioned Storage Service (VSS). /// The provided [`VssClient`] API is minimalistic and is congruent to the VSS server-side API. @@ -35,11 +36,11 @@ where impl> VssClient { /// Constructs a [`VssClient`] using `base_url` as the VSS server endpoint. pub fn new(base_url: String, retry_policy: R) -> Self { - let client = build_client(); + let client = Client::new(DEFAULT_CLIENT_CAPACITY); Self::from_client(base_url, client, retry_policy) } - /// Constructs a [`VssClient`] from a given [`reqwest::Client`], using `base_url` as the VSS server endpoint. + /// Constructs a [`VssClient`] from a given [`bitreq::Client`], using `base_url` as the VSS server endpoint. pub fn from_client(base_url: String, client: Client, retry_policy: R) -> Self { Self { base_url, @@ -49,7 +50,7 @@ impl> VssClient { } } - /// Constructs a [`VssClient`] from a given [`reqwest::Client`], using `base_url` as the VSS server endpoint. + /// Constructs a [`VssClient`] from a given [`bitreq::Client`], using `base_url` as the VSS server endpoint. /// /// HTTP headers will be provided by the given `header_provider`. pub fn from_client_and_headers( @@ -65,7 +66,7 @@ impl> VssClient { pub fn new_with_headers( base_url: String, retry_policy: R, header_provider: Arc, ) -> Self { - let client = build_client(); + let client = Client::new(DEFAULT_CLIENT_CAPACITY); Self { base_url, client, retry_policy, header_provider } } @@ -190,37 +191,29 @@ impl> VssClient { &self, request: &Rq, url: &str, ) -> Result { let request_body = request.encode_to_vec(); - let headermap = self + let headers = self .header_provider .get_headers(&request_body) .await - .and_then(|h| get_headermap(&h)) .map_err(|e| VssError::AuthError(e.to_string()))?; - let response_raw = self - .client - .post(url) - .header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) - .headers(headermap) - .body(request_body) - .send() - .await?; - let status = response_raw.status(); - let payload = response_raw.bytes().await?; - - if status.is_success() { + + let http_request = bitreq::post(url) + .with_header("content-type", APPLICATION_OCTET_STREAM) + .with_headers(headers) + .with_body(request_body) + .with_timeout(DEFAULT_TIMEOUT_SECS) + .with_max_body_size(Some(MAX_RESPONSE_BODY_SIZE)); + + let response = self.client.send_async(http_request).await?; + + let status_code = response.status_code; + let payload = response.into_bytes(); + + if (200..300).contains(&status_code) { let response = Rs::decode(&payload[..])?; Ok(response) } else { - Err(VssError::new(status, payload)) + Err(VssError::new(status_code, payload)) } } } - -fn build_client() -> Client { - Client::builder() - .timeout(DEFAULT_TIMEOUT) - .connect_timeout(DEFAULT_TIMEOUT) - .read_timeout(DEFAULT_TIMEOUT) - .build() - .unwrap() -} diff --git a/src/error.rs b/src/error.rs index 5955e6a..a301211 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,5 @@ use crate::types::{ErrorCode, ErrorResponse}; -use prost::bytes::Bytes; use prost::{DecodeError, Message}; -use reqwest::StatusCode; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -32,13 +30,13 @@ pub enum VssError { impl VssError { /// Create new instance of `VssError` - pub fn new(status: StatusCode, payload: Bytes) -> VssError { + pub fn new(status_code: i32, payload: Vec) -> VssError { match ErrorResponse::decode(&payload[..]) { Ok(error_response) => VssError::from(error_response), Err(e) => { let message = format!( "Unable to decode ErrorResponse from server, HttpStatusCode: {}, DecodeErr: {}", - status, e + status_code, e ); VssError::InternalError(message) }, @@ -99,8 +97,8 @@ impl From for VssError { } } -impl From for VssError { - fn from(err: reqwest::Error) -> Self { +impl From for VssError { + fn from(err: bitreq::Error) -> Self { VssError::InternalError(err.to_string()) } } diff --git a/src/headers/lnurl_auth_jwt.rs b/src/headers/lnurl_auth_jwt.rs index 9d7c7fa..1bc0242 100644 --- a/src/headers/lnurl_auth_jwt.rs +++ b/src/headers/lnurl_auth_jwt.rs @@ -1,4 +1,4 @@ -use crate::headers::{get_headermap, VssHeaderProvider, VssHeaderProviderError}; +use crate::headers::{VssHeaderProvider, VssHeaderProviderError}; use async_trait::async_trait; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; @@ -45,13 +45,14 @@ impl JwtToken { } } +const DEFAULT_TIMEOUT_SECS: u64 = 10; + /// Provides a JWT token based on LNURL Auth. pub struct LnurlAuthToJwtProvider { engine: Secp256k1, parent_key: Xpriv, url: String, default_headers: HashMap, - client: reqwest::Client, cached_jwt_token: RwLock>, } @@ -70,47 +71,44 @@ impl LnurlAuthToJwtProvider { /// with the JWT authorization header for VSS requests. pub fn new( parent_key: Xpriv, url: String, default_headers: HashMap, - ) -> Result { + ) -> LnurlAuthToJwtProvider { let engine = Secp256k1::signing_only(); - let default_headermap = get_headermap(&default_headers)?; - let client = reqwest::Client::builder() - .default_headers(default_headermap) - .build() - .map_err(VssHeaderProviderError::from)?; - Ok(LnurlAuthToJwtProvider { + LnurlAuthToJwtProvider { engine, parent_key, url, default_headers, - client, cached_jwt_token: RwLock::new(None), - }) + } } async fn fetch_jwt_token(&self) -> Result { // Fetch the LNURL. - let lnurl_str = self - .client - .get(&self.url) - .send() - .await - .map_err(VssHeaderProviderError::from)? - .text() - .await - .map_err(VssHeaderProviderError::from)?; + let lnurl_request = bitreq::get(&self.url) + .with_headers(self.default_headers.clone()) + .with_timeout(DEFAULT_TIMEOUT_SECS); + let lnurl_response = + lnurl_request.send_async().await.map_err(VssHeaderProviderError::from)?; + let lnurl_str = String::from_utf8(lnurl_response.into_bytes()).map_err(|e| { + VssHeaderProviderError::InvalidData { + error: format!("LNURL response is not valid UTF-8: {}", e), + } + })?; // Sign the LNURL and perform the request. let signed_lnurl = sign_lnurl(&self.engine, &self.parent_key, &lnurl_str)?; - let lnurl_auth_response: LnurlAuthResponse = self - .client - .get(&signed_lnurl) - .send() - .await - .map_err(VssHeaderProviderError::from)? - .json() - .await - .map_err(VssHeaderProviderError::from)?; + let auth_request = bitreq::get(&signed_lnurl) + .with_headers(self.default_headers.clone()) + .with_timeout(DEFAULT_TIMEOUT_SECS); + let auth_response = + auth_request.send_async().await.map_err(VssHeaderProviderError::from)?; + let lnurl_auth_response: LnurlAuthResponse = + serde_json::from_slice(&auth_response.into_bytes()).map_err(|e| { + VssHeaderProviderError::InvalidData { + error: format!("Failed to parse LNURL Auth response as JSON: {}", e), + } + })?; let untrusted_token = match lnurl_auth_response { LnurlAuthResponse { token: Some(token), .. } => token, @@ -256,8 +254,8 @@ impl From for VssHeaderProviderError { } } -impl From for VssHeaderProviderError { - fn from(e: reqwest::Error) -> VssHeaderProviderError { +impl From for VssHeaderProviderError { + fn from(e: bitreq::Error) -> VssHeaderProviderError { VssHeaderProviderError::RequestError { error: e.to_string() } } } diff --git a/src/headers/mod.rs b/src/headers/mod.rs index 752b948..692adf8 100644 --- a/src/headers/mod.rs +++ b/src/headers/mod.rs @@ -1,10 +1,8 @@ use async_trait::async_trait; -use reqwest::header::HeaderMap; use std::collections::HashMap; use std::error::Error; use std::fmt::Display; use std::fmt::Formatter; -use std::str::FromStr; #[cfg(feature = "lnurl-auth")] mod lnurl_auth_jwt; @@ -94,18 +92,3 @@ impl VssHeaderProvider for FixedHeaders { Ok(self.headers.clone()) } } - -pub(crate) fn get_headermap( - headers: &HashMap, -) -> Result { - let mut headermap = HeaderMap::new(); - for (name, value) in headers { - headermap.insert( - reqwest::header::HeaderName::from_str(&name) - .map_err(|e| VssHeaderProviderError::InvalidData { error: e.to_string() })?, - reqwest::header::HeaderValue::from_str(&value) - .map_err(|e| VssHeaderProviderError::InvalidData { error: e.to_string() })?, - ); - } - Ok(headermap) -} diff --git a/src/lib.rs b/src/lib.rs index 1946d1f..dd47474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,8 @@ #![deny(missing_docs)] // Crate re-exports +pub use bitreq; pub use prost; -pub use reqwest; /// Implements a thin-client ([`client::VssClient`]) to access a hosted instance of Versioned Storage Service (VSS). pub mod client; diff --git a/tests/lnurl_auth_jwt_tests.rs b/tests/lnurl_auth_jwt_tests.rs index e9a0a54..ca1a6d3 100644 --- a/tests/lnurl_auth_jwt_tests.rs +++ b/tests/lnurl_auth_jwt_tests.rs @@ -38,7 +38,7 @@ mod lnurl_auth_jwt_tests { let base_url = format!("http://localhost:{}", addr.port()); let parent_key = Xpriv::new_master(Network::Testnet, &[0; 32]).unwrap(); let lnurl_auth_jwt = - LnurlAuthToJwtProvider::new(parent_key, base_url.clone(), HashMap::new()).unwrap(); + LnurlAuthToJwtProvider::new(parent_key, base_url.clone(), HashMap::new()); { // First request will be provided with an expired JWT token. let k1 = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -56,7 +56,7 @@ mod lnurl_auth_jwt_tests { ])) .expect(1) .with_status(200) - .with_header(reqwest::header::CONTENT_TYPE.as_str(), APPLICATION_JSON) + .with_header("content-type", APPLICATION_JSON) .with_body(lnurl_auth_response(&expired_jwt)) .create(); assert_eq!( @@ -87,7 +87,7 @@ mod lnurl_auth_jwt_tests { ])) .expect(1) .with_status(200) - .with_header(reqwest::header::CONTENT_TYPE.as_str(), APPLICATION_JSON) + .with_header("content-type", APPLICATION_JSON) .with_body(lnurl_auth_response(&valid_jwt)) .create(); assert_eq!( diff --git a/tests/tests.rs b/tests/tests.rs index 68e22c8..986f886 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -3,7 +3,6 @@ mod tests { use async_trait::async_trait; use mockito::{self, Matcher}; use prost::Message; - use reqwest::header::CONTENT_TYPE; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -20,7 +19,8 @@ mod tests { }; use vss_client_ng::util::retry::{ExponentialBackoffRetryPolicy, RetryPolicy}; - const APPLICATION_OCTET_STREAM: &'static str = "application/octet-stream"; + const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; + const CONTENT_TYPE: &str = "content-type"; const GET_OBJECT_ENDPOINT: &'static str = "/getObject"; const PUT_OBJECT_ENDPOINT: &'static str = "/putObjects"; @@ -41,7 +41,7 @@ mod tests { // Register the mock endpoint with the mockito server. let mock_server = mockito::mock("POST", GET_OBJECT_ENDPOINT) - .match_header(CONTENT_TYPE.as_str(), APPLICATION_OCTET_STREAM) + .match_header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) .match_body(get_request.encode_to_vec()) .with_status(200) .with_body(mock_response.encode_to_vec()) @@ -73,7 +73,7 @@ mod tests { // Register the mock endpoint with the mockito server and provide expected headers. let mock_server = mockito::mock("POST", GET_OBJECT_ENDPOINT) - .match_header(CONTENT_TYPE.as_str(), APPLICATION_OCTET_STREAM) + .match_header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) .match_header("headerkey", "headervalue") .match_body(get_request.encode_to_vec()) .with_status(200) @@ -116,7 +116,7 @@ mod tests { // Register the mock endpoint with the mockito server. let mock_server = mockito::mock("POST", PUT_OBJECT_ENDPOINT) - .match_header(CONTENT_TYPE.as_str(), APPLICATION_OCTET_STREAM) + .match_header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) .match_body(request.encode_to_vec()) .with_status(200) .with_body(mock_response.encode_to_vec()) @@ -151,7 +151,7 @@ mod tests { // Register the mock endpoint with the mockito server. let mock_server = mockito::mock("POST", DELETE_OBJECT_ENDPOINT) - .match_header(CONTENT_TYPE.as_str(), APPLICATION_OCTET_STREAM) + .match_header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) .match_body(request.encode_to_vec()) .with_status(200) .with_body(mock_response.encode_to_vec()) @@ -192,7 +192,7 @@ mod tests { // Register the mock endpoint with the mockito server. let mock_server = mockito::mock("POST", LIST_KEY_VERSIONS_ENDPOINT) - .match_header(CONTENT_TYPE.as_str(), APPLICATION_OCTET_STREAM) + .match_header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) .match_body(request.encode_to_vec()) .with_status(200) .with_body(mock_response.encode_to_vec()) From eed3592aa5ae765b31cb43b88e071af55b12be93 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Jan 2026 13:24:25 +0100 Subject: [PATCH 2/5] f Bump MAX_RESPONSE_BODY_SIZE As 10 MB is def to small --- src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index e7cf589..fe0eac0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -17,7 +17,7 @@ use crate::util::KeyValueVecKeyPrinter; const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; const DEFAULT_TIMEOUT_SECS: u64 = 10; -const MAX_RESPONSE_BODY_SIZE: usize = 10 * 1024 * 1024; // 10 MiB +const MAX_RESPONSE_BODY_SIZE: usize = 500 * 1024 * 1024; // 500 MiB const DEFAULT_CLIENT_CAPACITY: usize = 10; /// Thin-client to access a hosted instance of Versioned Storage Service (VSS). From a36650313382ec2231d2e070cbe843a722a694aa Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Jan 2026 13:29:15 +0100 Subject: [PATCH 3/5] f Enable pipelining --- src/client.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index fe0eac0..c5f21c3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -202,7 +202,8 @@ impl> VssClient { .with_headers(headers) .with_body(request_body) .with_timeout(DEFAULT_TIMEOUT_SECS) - .with_max_body_size(Some(MAX_RESPONSE_BODY_SIZE)); + .with_max_body_size(Some(MAX_RESPONSE_BODY_SIZE)) + .with_pipelining(); let response = self.client.send_async(http_request).await?; From c9d7764c62fab59883a92ea3e4c7fa0103c21862 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Jan 2026 10:07:52 +0100 Subject: [PATCH 4/5] Update proto file link to the current location --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index a94b5a1..e3254cd 100644 --- a/build.rs +++ b/build.rs @@ -16,7 +16,7 @@ fn main() { #[cfg(genproto)] fn generate_protos() { download_file( - "https://raw.githubusercontent.com/lightningdevkit/vss-server/7f492fcac0c561b212f49ca40f7d16075822440f/app/src/main/proto/vss.proto", + "https://raw.githubusercontent.com/lightningdevkit/vss-server/022ee5e92debb60516438af0a369966495bfe595/proto/vss.proto", "src/proto/vss.proto", ).unwrap(); From ef17d83f5cff53862da4c966dd36b2b800ce5538 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Jan 2026 10:30:21 +0100 Subject: [PATCH 5/5] Bump MSRV to 1.85.0. Signed-off-by: Elias Rohrer --- .github/workflows/build.yml | 9 ++------- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ccc9e2..96bcaf3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,11 +6,11 @@ jobs: build: strategy: matrix: - toolchain: [ stable, beta, 1.75.0 ] # 1.75.0 is current MSRV for vss-client-ng + toolchain: [ stable, beta, 1.85.0 ] # 1.85.0 is current MSRV for vss-client-ng include: - toolchain: stable check-fmt: true - - toolchain: 1.75.0 + - toolchain: 1.85.0 msrv: true runs-on: ubuntu-latest steps: @@ -22,11 +22,6 @@ jobs: run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ matrix.toolchain }} rustup override set ${{ matrix.toolchain }} - - name: Pin packages to allow for MSRV - if: matrix.msrv - run: | - cargo update -p idna_adapter --precise 1.1.0 # This has us use `unicode-normalization` which has a more conservative MSRV - cargo update -p proptest --precise "1.8.0" --verbose # proptest 1.9.0 requires rustc 1.82.0 - name: Build on Rust ${{ matrix.toolchain }} run: cargo build --verbose --color always - name: Check formatting diff --git a/Cargo.toml b/Cargo.toml index a159908..c9fdd42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "vss-client-ng" version = "0.4.1" authors = ["Leo Nash ", "Elias Rohrer "] -rust-version = "1.75.0" +rust-version = "1.85.0" license = "MIT OR Apache-2.0" edition = "2021" homepage = "https://lightningdevkit.org/" diff --git a/README.md b/README.md index 36b7b07..edfb1e7 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,4 @@ and manage the essential state required for Lightning Network (LN) operations. Learn more [here](https://github.com/lightningdevkit/vss-server/blob/main/README.md). ## MSRV -The Minimum Supported Rust Version (MSRV) is currently 1.75.0. +The Minimum Supported Rust Version (MSRV) is currently 1.85.0.