ldk-server-client: Add uniffi bindings#194
ldk-server-client: Add uniffi bindings#194tnull wants to merge 7 commits intolightningdevkit:mainfrom
ldk-server-client: Add uniffi bindings#194Conversation
Adds opt-in `uniffi` and `uniffi-cli` features so foreign-language consumers (starting with Kotlin for an upcoming Android remote-control app) can call this crate directly instead of reimplementing HMAC-SHA256 auth and gRPC framing. Uses UniFFI's single-crate pattern from the official guide: the bindgen CLI is a [[bin]] target inside this crate, gated on the `uniffi-cli` feature, avoiding the complexity of a separate workspace crate. Wrapper types and the client object are added in follow-up commits. Co-Authored-By: HAL 9000
Defines flat, Kotlin/Swift-friendly analogues for every prost type that the upcoming FFI-exposed client methods will surface: node info, balances, payments (with a flattened `PaymentKindInfo` collapsing the prost `oneof`), channels, peers, send/receive results, decoded invoices and offers, and pagination tokens. Prost types can't be exported directly — they carry `#[derive(::prost::Message)]`, use nested `oneof` submodules, and reach for `prost::bytes::Bytes`. Each wrapper comes with the `From<ProstType>` (and, where both directions apply, `Into<ProstType>`) conversions the exported client will use. Detail deliberately dropped for the MVP scope: - `lightning_balances` and `pending_balances_from_channel_closures` (deep oneof nesting) - `OfferAmount::CurrencyAmount` — flatten to `amount_msat: Option<u64>` - `features` maps and `route_hints` on decoded invoice/offer - Byte-typed `secret` fields on payment kinds Co-Authored-By: HAL 9000
Adds the UniFFI-exported `LdkServerClientUni` object and its async constructor + read-only query methods: get_node_info, get_balances, list_channels, list_peers, list_payments (with optional page_token), get_payment_details. Why a wrapper rather than exporting LdkServerClient directly: the inner reqwest/hyper clients aren't UniFFI-exportable types, but the whole client is `Clone + Send + Sync`, so wrapping it in a `uniffi::Object` satisfies both UniFFI's trait bounds and the Kotlin/Swift `suspend fun` async model (via `async_runtime = "tokio"`). The uniffi dep now enables its `tokio` feature so the generated FFI scaffolding picks up the tokio-aware `#[uniffi::export]` expansion. Co-Authored-By: HAL 9000
Rounds out `LdkServerClientUni` with the full set of methods a wallet UI needs: the unified + per-protocol send flows (unified_send, bolt11_send, bolt12_send, onchain_send), receive flows (bolt11_receive, bolt12_receive, onchain_receive), channel lifecycle (open/close/force-close), peer connect/disconnect, and invoice/offer decoding. UniFFI-side API surface is intentionally simpler than the underlying prost requests: the `route_parameters` configuration on send methods and the `channel_config` on open_channel are hidden (server defaults are what we want); BOLT11 `description` is exposed as `Option<String>` and always attached as a direct description — the description-hash variant isn't useful to a mobile wallet. Co-Authored-By: HAL 9000
Covers the From/Into conversions for every wrapper type: node info (with + without a best block), balances, all PaymentKind oneof variants, channels, peers, all UnifiedSendResult variants (including the protocol-violation error path when the oneof is empty), decoded invoices, and decoded offers (including the flatten-to-None behavior for currency-denominated amounts). Also pins down the graceful-degradation guarantee for unknown protobuf enum values: if the server returns a direction/status code the client doesn't recognize, we fall back to safe defaults (Outbound/Pending) rather than panicking. Co-Authored-By: HAL 9000
Verified end-to-end:
* cross-compile produces ~3.3-4.7 MB libldk_server_client.so per ABI
* generated ldk_server_client.kt has `suspend fun` stubs for every
exported method and data classes/enums for every wrapper type
* jniLibs folder layout matches what the Android Gradle plugin picks up.
Co-Authored-By: HAL 9000
UniFFI's Kotlin generator emits struct-variant errors as subclasses of Throwable, and a constructor property named `message` collides with Throwable.message, producing a compile error on the generated ldk_server_client.kt. Renaming the field to `reason` sidesteps the clash without changing the FFI surface meaningfully. No behavior change; Display output now reads "Invalid request: <reason>" etc. All existing unit tests still pass. Co-Authored-By: HAL 9000
|
👋 Hi! This PR is now in draft status. |
|
idk if this really makes sense, grpc already kinda gives us this for free. Main benefit would be not having to implement auth but that shouldn't be very hard |
Hmm, well, even the current very basic scheme kept me (and my Claude instance at the time) from re-implementing the client-side logic in Kotlin, and I assume that will just get more of a lift when we switch to anything beyond (macaroons, etc). Yes, gRPC/protobuf is a pretty open format with good platform support, but IMO offering bindings would make it much more simple for people to integrate some kind of client-side code. Note that with Note that (in contrast to LDK Node's bindings) I spent no time to even attempt to DRY them up/reduce the changeset here. So I expect the diff could be quite a bit smaller if we tried. |
Not quite sure we want to add this at this point, but I needed it for a personal project, so vibecoded it. Draft for now.