From 91b4162f3ae9003a788f6fdc368b38ab0c6412eb Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 May 2026 18:05:50 +0200 Subject: [PATCH 1/5] sdk: Make resolve_input synchronous, remove cpp-bindings feature Replace the two conditional resolve_input implementations (async for non-CPP, sync for cpp-bindings) with a single synchronous version that wraps async internals via util::exec(). The entire public API exposed by the SDK through UniFFI is now synchronous. Functions that need async work (network I/O, gRPC) block the calling thread while an internal Tokio runtime executes the underlying operations. This gives every target language (Python, Kotlin, Swift, Ruby, C++) the same blocking API without requiring an async runtime or special feature flags on the caller side. - Remove `#[cfg(feature = "cpp-bindings")]` conditional compilation - Remove `#[uniffi::export(async_runtime = "tokio")]` attribute - Remove `cpp-bindings` feature from Cargo.toml - Drop uniffi `tokio` feature (no longer needed) --- Cargo.lock | 14 -------------- libs/gl-sdk/Cargo.toml | 5 +---- libs/gl-sdk/src/lib.rs | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bbbf55a5..aac5c1eab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,19 +252,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "async-compat" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -4785,7 +4772,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98" dependencies = [ "anyhow", - "async-compat", "bytes", "once_cell", "static_assertions", diff --git a/libs/gl-sdk/Cargo.toml b/libs/gl-sdk/Cargo.toml index d41bae38e..d252317a6 100644 --- a/libs/gl-sdk/Cargo.toml +++ b/libs/gl-sdk/Cargo.toml @@ -24,11 +24,8 @@ thiserror = "2.0.17" tokio = { version = "1", features = ["sync"] } tonic.workspace = true tracing = { version = "0.1.43", features = ["async-await", "log"] } -uniffi = { version = "0.29.4", features = ["tokio"] } +uniffi = { version = "0.29.4" } url = "2" [build-dependencies] uniffi = { version = "0.29.4", features = [ "build" ] } - -[features] -cpp-bindings = [] diff --git a/libs/gl-sdk/src/lib.rs b/libs/gl-sdk/src/lib.rs index 7542ee08b..1e3d51a7d 100644 --- a/libs/gl-sdk/src/lib.rs +++ b/libs/gl-sdk/src/lib.rs @@ -250,7 +250,7 @@ pub fn parse_input(input: String) -> Result { input::parse_input(input) } -/// Asynchronously classify and resolve the input. +/// Classify and resolve the input. /// /// Internally calls `parse_input` for offline classification, then /// for LNURL bech32 strings and Lightning Addresses performs the @@ -259,15 +259,15 @@ pub fn parse_input(input: String) -> Result { /// immediately without I/O. /// /// Strips `lightning:` / `LIGHTNING:` prefixes automatically. -#[cfg(not(feature = "cpp-bindings"))] -#[uniffi::export(async_runtime = "tokio")] -pub async fn resolve_input(input: String) -> Result { - input::resolve_input(input).await -} - -/// Synchronously classify and resolve the input (C++ bindings). -/// Note: This blocks the current thread. Use in a background thread if needed. -#[cfg(feature = "cpp-bindings")] +/// +/// # Blocking +/// +/// This function blocks the calling thread while any network I/O +/// completes. The SDK exposes a **synchronous-only** public API so +/// that every language binding (Python, Kotlin, Swift, Ruby, C++) +/// works without requiring an async runtime on the caller side. +/// Async work is executed internally on a shared Tokio runtime +/// managed by the SDK. #[uniffi::export] pub fn resolve_input(input: String) -> Result { util::exec(async { input::resolve_input(input).await }) From b325e64ece46713409be6c770037523917b925bc Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 May 2026 18:05:57 +0200 Subject: [PATCH 2/5] sdk: Remove --features cpp-bindings from bindings-cpp task The cpp-bindings feature flag no longer exists; the standard build now produces a library that works for all bindings including C++. --- libs/gl-sdk/.tasks.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/gl-sdk/.tasks.yml b/libs/gl-sdk/.tasks.yml index 8752fa91a..d00d10dab 100644 --- a/libs/gl-sdk/.tasks.yml +++ b/libs/gl-sdk/.tasks.yml @@ -98,8 +98,7 @@ tasks: desc: "Generate C++ bindings (requires uniffi-bindgen-cpp)" dir: "{{.TASKFILE_DIR}}/../.." deps: - - task: build - vars: { CARGO_FLAGS: '{{if eq .PROFILE "release"}}--release{{else}}{{end}} --features cpp-bindings' } + - build cmds: - | cp ${CARGO_TARGET_DIR:-target}/{{.PROFILE_DIR}}/libglsdk.{{.LIB_EXT}} ./libs/gl-sdk/glsdk/libglsdk.{{.LIB_EXT}}; From d1e87135ed18a30a549540bb1423eaf781e1213f Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 May 2026 18:06:05 +0200 Subject: [PATCH 3/5] sdk: Document synchronous-only API design choice Add a Synchronous API Design section to README.md explaining the rationale: uniform bindings across all languages, simpler caller-side integration, and no conditional compilation. Remove references to the now-deleted cpp-bindings feature from the C++ bindings instructions. --- libs/gl-sdk/README.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/libs/gl-sdk/README.md b/libs/gl-sdk/README.md index fcc9e8704..1ede38580 100644 --- a/libs/gl-sdk/README.md +++ b/libs/gl-sdk/README.md @@ -87,10 +87,10 @@ C++ bindings use [uniffi-bindgen-cpp](https://github.com/NordSecurity/uniffi-bin cargo install uniffi-bindgen-cpp --git https://github.com/NordSecurity/uniffi-bindgen-cpp --tag v0.8.1+v0.29.4 ``` -Then build with the `cpp-bindings` feature and generate bindings: +Then build and generate bindings: ```bash -cargo build --release -p gl-sdk --features cpp-bindings +cargo build --release -p gl-sdk uniffi-bindgen-cpp --library target/release/libglsdk.dylib --out-dir libs/gl-sdk/bindings ``` @@ -105,6 +105,30 @@ perl -pi -e 's/NodeBuilder::register\(/NodeBuilder::register_node\(/g; s/Schedul The `task sdk:bindings-cpp` command handles all of the above automatically, including platform detection. +## Synchronous API Design + +The entire public API exposed by the SDK through UniFFI is +**synchronous**. Functions that need to perform async work (network +I/O, gRPC calls) block the calling thread while an internal Tokio +runtime executes the underlying async operations. + +This is a deliberate design choice: + +- **Uniform bindings** -- every target language (Python, Kotlin, + Swift, Ruby, C++) gets the same blocking API. No language needs + an async runtime, coroutine support, or special feature flags on + the caller side. +- **Simpler integration** -- callers that need concurrency can use + their language's native threading primitives (e.g. `threading` in + Python, `std::thread` in C++, coroutines in Kotlin) to call SDK + methods off the main thread. +- **No conditional compilation** -- a single build of the shared + library works for all bindings, avoiding feature-flag divergence + between languages. + +The internal Tokio runtime is created lazily on the first call and +lives for the lifetime of the process (see `src/util.rs`). + ## Files - `src/sdk.udl` - UniFFI interface definition From d7f0c84072bf0559b4a9aca51ffc148318b7d82f Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 May 2026 18:06:46 +0200 Subject: [PATCH 4/5] sdk: Update CHANGELOG for sync-only API change Document the resolve_input() synchronous switch and the removal of the cpp-bindings feature flag in the Unreleased section. --- libs/gl-sdk/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/gl-sdk/CHANGELOG.md b/libs/gl-sdk/CHANGELOG.md index 6c2998a62..09428c826 100644 --- a/libs/gl-sdk/CHANGELOG.md +++ b/libs/gl-sdk/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +### Changed + +- `resolve_input()` is now **synchronous**, matching every other public SDK function. Async work (LNURL HTTP fetches) is executed internally on a shared Tokio runtime. Callers no longer need an async runtime or coroutine support; use native threading primitives to call off the main thread if needed. +- Removed the `cpp-bindings` Cargo feature flag. A single build of the shared library now works for all language bindings (Python, Kotlin, Swift, Ruby, C++) without conditional compilation. + ## [0.2.1] - 2026-04-30 ### Added From 3830a717c4e4b150931cb314b1731ea30704f25c Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 May 2026 18:51:05 +0200 Subject: [PATCH 5/5] napi: Adapt resolve_input binding to synchronous SDK API glsdk::resolve_input is now synchronous (blocks internally via util::exec). The NAPI binding was still .await-ing the Result, causing a compile error: error[E0277]: Result is not a future Wrap the call in tokio::task::spawn_blocking, consistent with every other blocking SDK call in the NAPI bindings. --- libs/gl-sdk-napi/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/gl-sdk-napi/src/lib.rs b/libs/gl-sdk-napi/src/lib.rs index 3e0749cff..5acb26d08 100644 --- a/libs/gl-sdk-napi/src/lib.rs +++ b/libs/gl-sdk-napi/src/lib.rs @@ -1226,7 +1226,7 @@ pub fn parse_input(input: String) -> Result { Ok(napi_parsed_input_from_gl(parsed)) } -/// Asynchronously classify and resolve the input. +/// Classify and resolve the input. /// /// Internally calls `parseInput`. For LNURL bech32 strings and /// Lightning Addresses performs the HTTP GET to the endpoint and @@ -1234,9 +1234,11 @@ pub fn parse_input(input: String) -> Result { /// and node IDs returns immediately without I/O. #[napi] pub async fn resolve_input(input: String) -> Result { - let resolved = glsdk::resolve_input(input) - .await - .map_err(|e| Error::from_reason(e.to_string()))?; + let resolved = tokio::task::spawn_blocking(move || { + glsdk::resolve_input(input).map_err(|e| Error::from_reason(e.to_string())) + }) + .await + .map_err(|e| Error::from_reason(e.to_string()))??; Ok(napi_resolved_input_from_gl(resolved)) }