Skip to content

Add bindings/c-ffi: C / C++ bindings alongside uniffi#25

Open
Jainakin wants to merge 1 commit intoUTEXO-Protocol:devfrom
Jainakin:bindings/c-ffi
Open

Add bindings/c-ffi: C / C++ bindings alongside uniffi#25
Jainakin wants to merge 1 commit intoUTEXO-Protocol:devfrom
Jainakin:bindings/c-ffi

Conversation

@Jainakin
Copy link
Copy Markdown

Summary

Adds a new bindings/c-ffi/ sub-crate that exposes the SdkNode API over a
C ABI. It is a thin extern-"C" shim on top of the existing UniFFI-exposed API
in src/uniffi_api/ — same async-bridge (block_on_sdk in
src/uniffi_api/state.rs), same SdkNode struct, no changes to src/.

This unblocks consumers that don't have a UniFFI runtime: Node.js / Bun via
N-API, Bare native addons,
Python via ctypes, etc. The intended first consumer is a bare-runtime
native addon mirroring the rgb-lib-bare
pattern.

What's exposed

61 extern "C" functions covering the full SdkNode surface plus lifecycle,
namespace-level helpers, and dropping primitives:

  • Lifecycle (5): rln_sdk_node_new, rln_sdk_node_init,
    rln_sdk_node_unlock, rln_sdk_node_shutdown, free_sdk_node
  • Channels / peers (7): rln_open_channel, rln_close_channel,
    rln_list_channels, rln_get_channel_id, rln_connect_peer,
    rln_disconnect_peer, rln_list_peers
  • LN payments / invoices (10): rln_send_payment, rln_keysend,
    rln_ln_invoice, rln_invoice_status, rln_cancel_hodl_invoice,
    rln_claim_hodl_invoice, rln_decode_ln_invoice, rln_decode_rgb_invoice,
    rln_get_payment, rln_list_payments
  • RGB (10): rln_rgb_invoice, rln_send_rgb, rln_refresh_transfers,
    rln_fail_transfers, rln_inflate, rln_list_transfers,
    rln_list_unspents, rln_post_asset_media, rln_get_asset_media,
    rln_check_proxy_endpoint
  • Asset issuance / metadata (7): rln_issue_asset_{nia,cfa,ifa,uda},
    rln_list_assets, rln_asset_balance, rln_asset_metadata
  • BTC / node info / utxos / sync (11): rln_node_info, rln_network_info,
    rln_address, rln_btc_balance, rln_sign_message, rln_estimate_fee,
    rln_check_indexer_url, rln_send_btc, rln_create_utxos,
    rln_list_transactions, rln_sync
  • Swaps / onion (6): rln_maker_init, rln_maker_execute, rln_taker,
    rln_send_onion_message, rln_get_swap, rln_list_swaps
  • Module-level helpers (4): rln_uniffi_healthcheck,
    rln_uniffi_is_initialized, rln_sdk_initialize, rln_sdk_shutdown
  • String free (1): rln_free_string

ABI / wire format

Mirrors the
rgb-lib c-ffi pattern:

  • COpaqueStruct opaque handle for SdkNode.
  • CResult / CResultString tagged-union returns; on Err, inner carries
    the formatted error message — caller frees via rln_free_string.
  • All complex inputs and outputs cross the boundary as JSON strings.
    Typed wrappers (PublicKey, Txid, ContractId, ChannelId,
    PaymentHash, Bolt11Invoice) are passed as their canonical string forms
    (hex / bech32 / BIP-21) inside the JSON or as bare string args.
  • cbindgen generates rln.h and rln.hpp at build time. Both are
    committed to keep first-time consumers (and the included example.c)
    buildable without running build.rs first.

JSON-friendly mirror request/response types live in src/json_types.rs
(~1900 LoC, all mechanical) with TryFrom / From conversions to and from
the UniFFI typed wrappers. This avoids requiring serde::{Serialize, Deserialize} derives on the existing types in src/uniffi_api/types.rs.

File layout

```
bindings/c-ffi/
Cargo.toml # rln-c-ffi crate, [workspace] block, [patch.crates-io]
Cargo.lock # committed — pins rgb-lib to the same rev as parent
build.rs # cbindgen → rln.h / rln.hpp
cbindgen.toml
Makefile # rust-build + example wiring (mirrors rgb-lib c-ffi)
README.md
rln.h # generated, committed
rln.hpp # generated, committed
example.c # smoke test (healthcheck + is_initialized)
src/
lib.rs # 61 extern "C" wrappers
api.rs # handler functions (parse JSON → call SdkNode → JSON)
json_types.rs # mirror request/response structs + converters
utils.rs # COpaqueStruct, CResult helpers, ptr helpers
```

No changes to existing files outside bindings/c-ffi/.

Build & verify

```sh
cd bindings/c-ffi
cargo build # libs + headers
make build-example # gcc -lrlncffi
DYLD_LIBRARY_PATH=target/debug ./example

rgb-lightning-node c-ffi smoke test

[healthcheck] OK: rgb_lightning_node_uniffi_ready

[is_initialized] OK: false

```

Outputs: target/debug/librlncffi.{a,dylib} (≈170 MB dylib, ≈1.6 GB static
unstripped on darwin; release builds are far smaller).

Notes on workspace / dep resolution

The c-ffi sub-crate is its own Cargo workspace ([workspace] block). It
mirrors the parent crate's [patch.crates-io] table to keep lightning /
lightning-background-processor unified onto the in-tree rust-lightning
submodule — without that, registry pulls of lightning-net-tokio etc.
trigger trait-bound mismatches with the path-deped lightning. The
committed Cargo.lock pins rgb-lib to the same revision the parent
Cargo.lock uses, so a fresh clone + cargo build --locked reproduces
without cargo update --precise gymnastics.

A direct lightning / lightning-invoice dep is added so the sub-crate
can construct PaymentHash / ChannelId by their real crate paths
(rgb_lightning_node::PaymentHash is a type alias; Rust 2021 doesn't allow
tuple-struct construction through aliases).

Out of scope

  • Prebuilt static libs / .so / .dylib distribution (will live in the
    consuming rgb-lightning-node-bare package).
  • VSS / backup helpers — the existing UniFFI surface doesn't expose them on
    SdkNode; can be added later if/when the SDK does.
  • Async / event-stream callbacks. The SdkNode surface is sync today; HOLD
    invoice notifications etc. are not exposed by UniFFI either.

Thin extern-"C" shim on top of the existing UniFFI-exposed SdkNode API
(reuses block_on_sdk; no changes under src/). Mirrors the rgb-lib c-ffi
pattern: opaque COpaqueStruct handle, CResult / CResultString tagged
unions, and JSON strings for all complex inputs/outputs. Serde-friendly
mirror types in json_types.rs convert to/from the UniFFI typed wrappers
so no Serialize/Deserialize derives are needed upstream in
src/uniffi_api/types.rs.

Exposes 61 extern "C" functions covering the full SdkNode surface plus
lifecycle, namespace-level helpers, and free primitives. Build emits
librlncffi.{a,dylib} and a committed cbindgen-generated rln.h / rln.hpp
so consumers (Bare native addons, N-API wrappers, ctypes, etc.) can
embed without running build.rs. Includes a smoke-test example.c.

The sub-crate is its own Cargo workspace and mirrors the parent's
[patch.crates-io] table to keep registry lightning crates unified onto
the in-tree rust-lightning submodule. Cargo.lock is committed and pins
rgb-lib to the same revision as the parent's Cargo.lock so a fresh
checkout builds with --locked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant