Fiber FFI User Guide
Embed Fiber nodes through a C ABI for mobile, desktop, and other native runtimes
Background
About fiber-ffi
fiber-ffi currently lives in a personal repository. It is used here as an exploratory reference for Fiber native/mobile integration, not as an official Fiber release artifact, supported SDK, or compatibility commitment.
This FFI layer originally came from an exploration of mobile support. The community hoped that Fiber could support mobile platforms, so we tried to run the existing Rust Fiber node inside Android/iOS app processes instead of reimplementing the node logic on mobile.
While integrating the existing Fiber implementation into mobile apps, we found that we first needed a clear FFI boundary: the app side should only handle platform integration such as JNI bridges and Swift bridging headers, while the node logic should still be carried by the Rust implementation. fiber-ffi was therefore extracted from the mobile exploration.
After this boundary was extracted, it no longer served only Android/iOS. C++, desktop apps, server processes, scripting language bindings, plugin systems, and other scenarios can also wrap their own runtimes around the same header files and dynamic libraries. The rest of this document therefore focuses on the general FFI calling rules. Mobile platforms appear mainly as the motivation and typical use cases.
Introduction
The linked fiber-ffi repository is an exploratory FFI layer for Fiber nodes. It does not reimplement node logic. Instead, it wraps Rust fiber-lib as a C ABI, allowing non-Rust programs to embed a Fiber node in their own process through a dynamic library.
It provides a basic set of node capabilities: starting or stopping a node, reading node information, connecting peers, managing channels, handling invoices and payments, and receiving node events through callbacks.
The call chain is short: the host language wrapper calls functions declared in the C header, those functions enter the dynamic library, and then dispatch to Rust fiber-lib. The public surface mainly exposes FiberHandle, Fiber...Options, status codes, JSON return values, and error messages. Callers must release returned strings according to the FFI rules.
fiber-ffi is still in an exploratory stage and currently lives in a personal repository. It is suitable for validating the call path, writing upper-level language bindings, and discussing SDK shape. Before using it in production, code review and sufficient testing are recommended.
The overall structure can be understood as the following layers:
+----------------------------------------------------------+
| App project code |
+---------------------------^------------------------------+
| App projects integrate here
+---------------------------+------------------------------+
| fiber-ffi project scope |
| |
| +------------------------------------------------------+ |
| | Android JNI bridge / iOS Swift bridge | |
| +------------------------------------------------------+ |
| | C header: include/fiber_ffi.h | | <- generated by fiber-ffi
| +------------------------------------------------------+ |
| | fiber-ffi Rust dynamic library | | <- exports a C ABI
| +------------------------------------------------------+ |
+---------------------------^------------------------------+
|
+---------------------------+------------------------------+
| fiber-lib | <- Fiber node core logic
+----------------------------------------------------------+Build
Regular local build:
cargo build --releaseThe repository also provides mobile build entry points:
make build-android
make build-ios
make build-ios-simIntegrators need to distribute both the dynamic library and the header file:
include/fiber_ffi.h
libfiber_ffi.*The header file is available at include/fiber_ffi.h.
Dynamic libraries are currently the preferred distribution format. The goal is to keep the integration boundary at the C ABI: as long as the target platform, CPU architecture, and exported ABI match, the host program can load and call fiber-ffi at runtime. This means integrators only need to handle the header file, dynamic library file, and platform loading path. They do not need to bring Rust build artifacts into their own link flow.
Static libraries do have some advantages, but they push more build details onto the integrator: Rust dependencies, system libraries, link order, symbol exports, runtime initialization, whole-archive/dead strip configuration, and the platform-specific linking rules for Android, iOS, and desktop platforms. For a cross-language SDK, these factors can easily make integration more complex than the FFI itself. Therefore, the current documentation and build scripts primarily target dynamic library distribution.
Calling Details
Lifecycle
Recommended lifecycle rules:
- The current FFI usage model is a single-node lifecycle. A process should maintain only one valid node at a time.
- Repeated
startcalls should return already running at the upper layer instead of creating a second node. - After
stop, immediately clear the handle on the host side. The old handle must no longer be used. Repeatedstopcalls should return already stopped at the upper layer. - Opening two Fiber nodes in the same process at the same time is not supported.
stopperforms a graceful shutdown. Afterwards, the same configuration and the same data can be used tostartagain. If you need to switch from testnet to mainnet, or to another custom network configuration, restart the host process.
At the implementation level, note the following details:
After a successful start, the dynamic library creates a group of background tasks that handle on-chain interaction, network connections, channel state, and event dispatch. Calling fiber_stop notifies these background tasks to exit in order, waits for them to finish cleanup, and writes the relevant channel state back to local data. During shutdown, network connections are closed and a NetworkStopped event is emitted through the callback.
This means fiber_stop is not a simple thread kill. When the node is started again after shutdown, channels in the same local data are handled by Fiber's existing recovery logic. If stop happens while channel/payment negotiation is in progress, do not treat it as canceling a business operation. The recovery result still depends on the state already written to local data and on whether the peer reconnects later.
Why switching network configuration requires restarting the process: fiber_start loads the testnet, mainnet, or custom network configuration and initializes process-level global state. This kind of state is similar to global variables. Once it is set for the first time, it is not cleared by fiber_stop. fiber-ffi also remembers the network used for the first start, and later starts with a different network configuration will fail.
After starting and then stopping, keep these points in mind:
fiber_stopconsumes and releases the incomingFiberHandle *. The caller must immediately clear the handle stored on the host side.- Using an old handle after
stopis a use-after-free. Repeatedstopshould also not call into FFI again. The upper-level wrapper should return already stopped directly. stopmust be mutually exclusive with other calls on the same handle. Do not close the same node concurrently while business APIs are running.- After
stop, you canstartagain, but do not switch network configuration inside the same process. For example, starting with testnet first and then starting with mainnet or another custom network configuration afterstopshould be handled by starting a new host process.
The current FFI does not support opening two node instances at the same time. fiber-ffi does not block a second start with a global lock at the fiber_start entry point, so a second start may continue in some configurations. However, process-level global state exists, and listening ports, local data directories, private key directories, network configuration, and contract-related caches may conflict with each other. The upper-level runtime should treat the node as a singleton and must not create a second node.
Start Options
typedef struct FiberStartOptions {
const char *config_path;
const char *database_prefix;
const char *log_level;
fiber_event_callback event_callback;
void *event_callback_user_data;
} FiberStartOptions;| Field | Description |
|---|---|
config_path | Required. Path to the Fiber configuration file. |
database_prefix | Optional. Node data root directory. If omitted, the directory containing config_path is used. |
log_level | Optional. For example, info or debug. Defaults to info. |
event_callback | Optional. Fiber event callback. |
event_callback_user_data | Optional. Passed back to event_callback unchanged. |
Note that config_path must be a configuration file path that the current process can read directly. It is not the configuration file contents. During startup, fiber-ffi opens the file at this path and parses YAML. If the configuration comes from Android assets, an iOS app bundle, or another packaged resource, first copy it to a real file path accessible by the app, then pass that path to fiber_start.
config_path and other string parameters must be UTF-8 C strings and must remain valid for the duration of the call. Related information can be checked through Fiber's API documentation.
It is recommended to pass database_prefix explicitly. Point it to a private and stable data root directory owned by the host app, and isolate it by user, account, or node identity. FFI overrides fiber.base_dir to <database_prefix>/fiber and ckb.base_dir to <database_prefix>/ckb. If it is not passed, the directory containing config_path is used as the data root directory.
Do not let different Fiber node private keys, CKB private keys, or network configurations reuse the same directory. Otherwise, the node may read old fiber/sk, ckb/key, and store data, causing identity confusion, key decryption failure, or startup/recovery failure. If old channel state and on-chain funds do not match the current private key, user assets may be lost in severe cases. When switching the CKB key or Fiber node identity, use a new database_prefix, or explicitly clean up/migrate the corresponding data after shutdown.
Error Handling
The main APIs return FiberFfiStatus:
typedef enum FiberFfiStatus {
FIBER_FFI_STATUS_OK = 0,
FIBER_FFI_STATUS_NULL_POINTER = 1,
FIBER_FFI_STATUS_INVALID_ARGUMENT = 2,
FIBER_FFI_STATUS_STARTUP_FAILED = 3,
FIBER_FFI_STATUS_ALREADY_STOPPED = 4,
FIBER_FFI_STATUS_PANIC = 5,
} FiberFfiStatus;After a failure, read the error details:
char buffer[4096];
size_t len = fiber_last_error_message(buffer, sizeof(buffer));
if (len > 0) {
fprintf(stderr, "%s\n", buffer);
}Notes:
- Error messages are thread-local.
- Read them on the same thread immediately after the failed call.
- Successful calls clear the current thread's error message.
- The return value is the full error length, excluding the trailing
\0.
String Memory
Follow the common ownership convention: whoever allocates releases. Strings allocated by the caller are released by the caller. Strings allocated by the fiber-ffi dynamic library must be returned to the dynamic library for release. The rules below are this convention applied specifically to the FFI boundary.
Several kinds of char * appear across the FFI boundary. Their ownership differs, so distinguish them by source during integration:
| String source | Allocator | Releaser | Lifetime |
|---|---|---|---|
const char * passed by the caller | Caller | Caller | Only needs to remain valid during this FFI call. |
Strings returned by FFI through char **out_* | fiber-ffi dynamic library | Caller, using fiber_string_free | Release after the caller copies or parses it. |
Return value of fiber_version() | fiber-ffi static storage | Do not release | Valid for the process lifetime. |
event_json in an event callback | Temporarily created by fiber-ffi | Do not release | Valid only during this callback invocation. |
Buffer written by fiber_last_error_message | Caller | Caller | The buffer is provided by the caller. FFI does not allocate memory. |
Strings passed by the caller must be UTF-8, NUL-terminated C strings. For optional fields, pass NULL to mean unset. For required fields, passing NULL returns FIBER_FFI_STATUS_NULL_POINTER. During the call, FFI copies any information it needs to keep into Rust-owned objects. Therefore, Swift withCString, JNI GetStringUTFChars, and C++ std::string::c_str() can all be used, as long as the corresponding memory is not destroyed or modified before the function returns.
Any parameter shaped as char **out_* means the result string is allocated by the dynamic library. For example:
FiberFfiStatus fiber_node_info(FiberHandle *handle, char **out_json);
FiberFfiStatus fiber_new_invoice(FiberHandle *handle,
const FiberNewInvoiceOptions *options,
char **out_invoice_address);These return values must first be copied into the host language's own string object, then released with fiber_string_free:
void fiber_string_free(char *string);Do not use free, delete, JNI, Swift, or another host language allocator to release strings returned by FFI. This memory is allocated by the Rust dynamic library and can only be released by fiber_string_free exported from the same dynamic library. Do not continue reading the original pointer after fiber_string_free. fiber_string_free(NULL) is safe, but the same non-null pointer may only be released once.
Event Callback
Callback type:
typedef void (*fiber_event_callback)(const char *event_json, void *user_data);event_json is event JSON, for example:
{
"kind": "PeerConnected",
"pubkey": "02...",
"addr": "/ip4/127.0.0.1/tcp/8228"
}Callback rules:
event_jsonis valid only while the callback is being invoked.- If asynchronous processing is needed, copy the string immediately.
- The callback may run on a background thread owned by the dynamic library, not necessarily on the host main thread.
- Do not perform long-running work in the callback.
- Avoid calling other FFI functions directly from the callback.
Common events include NetworkStarted, NetworkStopped, PeerConnected, ChannelCreated, ChannelReady, ChannelClosed, PreimageCreated, and so on.
Parameter Struct Rules
Most business parameter structs contain:
uint32_t struct_size;
uint32_t flags;Use the initialization macro before calling:
FiberListChannelsOptions options = FIBER_LIST_CHANNELS_OPTIONS_INIT;Rules:
struct_sizemust be set.flagsmust currently be0.has_*indicates whether the corresponding field is enabled.*_jsonfields must be valid JSON strings.NULLmeans unset.
128-bit Integers and Amounts
Unsigned 128-bit integers are represented in the C ABI as:
typedef struct FiberU128 {
uint64_t low;
uint64_t high;
} FiberU128;Full value:
(high << 64) | lowFor amounts smaller than uint64_t, only low needs to be set:
FiberU128 amount = {0};
amount.low = 100000000;
amount.high = 0;Amount fields in the FFI layer always use integer smallest units. Do not pass display amounts with decimal points. CKB amounts use shannons, where 1 CKB = 100000000 shannons. If the business layer displays or accepts decimal CKB values, the upper-level wrapper should convert them to shannons before calling FFI.
UDT amounts use the integer smallest unit of that UDT. No CKB/shannons conversion is applied. In other words, when UDT parameters such as funding_udt_type_script_json and udt_type_script_json are passed, amount / funding_amount should be handled as the UDT's raw integer amount, and must not apply the CKB amount * 100000000 conversion.
JSON Parameters
Complex objects are still passed as JSON strings. For example:
funding_udt_type_script_jsonshutdown_script_jsonfunding_lock_script_jsonclose_script_jsontrampoline_hops_jsoncustom_records_jsonhop_hints_jsonrouter_json
Rules:
NULLmeans unset.- Non-null values must be valid JSON.
- JSON contents must match the corresponding Fiber RPC parameter structure.
- Invalid JSON returns
FIBER_FFI_STATUS_INVALID_ARGUMENT.
Language Binding Recommendations
It is recommended not to expose the C ABI directly to the business layer. Wrap it in a host language runtime:
FiberRuntime
- start(configPath, dataDir, logLevel)
- stop()
- nodeInfo()
- listPeers()
- connectPeer(...)
- listChannels(...)
- openChannel(...)
- newInvoice(...)
- sendPayment(...)
- onEvent(callback)The wrapper should be responsible for:
- Loading the dynamic library.
- Holding and releasing
FiberHandle *. - Serializing FFI calls.
- Converting
FiberFfiStatusinto host language exceptions orResultvalues. - Reading
fiber_last_error_message. - Copying output strings and calling
fiber_string_free. - Parsing JSON.
- Dispatching event callbacks back to the host thread or event loop.
Threads and Safety
Recommended handling:
- Do not execute potentially blocking FFI calls directly on the UI/main thread.
- Calls on the same
FiberHandleshould preferably be serialized. fiber_stopmust be mutually exclusive with other calls.- In callbacks, only copy the event and post it to the host event loop.
- Real applications must design their own private key, password, data directory, and log redaction strategies.
The private key file and fixed password scheme in the exploratory demo are only for validating the call path. Do not copy them into production integrations.
ABI Compatibility Recommendations
The current ABI is still exploratory. To reduce upgrade cost:
- Always use the
fiber_ffi.hpublished with the dynamic library. - Use
FIBER_*_OPTIONS_INITwhen initializing options. - Parse returned JSON leniently and ignore unknown fields.
- Do not rely on the order of fields in event JSON.
- Do not treat error strings as a stable protocol.
- Upgrade the header file together with the dynamic library.
Summary
The linked fiber-ffi reference provides a C ABI that lets non-Rust applications embed a Fiber node through a dynamic library. During integration, focus on five points:
- Use
fiber_start/fiber_stopto manageFiberHandle. - Release all FFI-returned strings with
fiber_string_free. - Read error details with
fiber_last_error_messageafter failures. - Copy events in callbacks, then hand them to the host event loop.
- Secure storage, the threading model, and platform lifecycle are the responsibility of the host app.