Fiber LogoFiber Docs
FFI

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

Reference Source Code

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 --release

The repository also provides mobile build entry points:

make build-android
make build-ios
make build-ios-sim

Integrators 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 start calls 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. Repeated stop calls should return already stopped at the upper layer.
  • Opening two Fiber nodes in the same process at the same time is not supported.
  • stop performs a graceful shutdown. Afterwards, the same configuration and the same data can be used to start again. 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_stop consumes and releases the incoming FiberHandle *. The caller must immediately clear the handle stored on the host side.
  • Using an old handle after stop is a use-after-free. Repeated stop should also not call into FFI again. The upper-level wrapper should return already stopped directly.
  • stop must 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 can start again, 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 after stop should 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;
FieldDescription
config_pathRequired. Path to the Fiber configuration file.
database_prefixOptional. Node data root directory. If omitted, the directory containing config_path is used.
log_levelOptional. For example, info or debug. Defaults to info.
event_callbackOptional. Fiber event callback.
event_callback_user_dataOptional. 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 sourceAllocatorReleaserLifetime
const char * passed by the callerCallerCallerOnly needs to remain valid during this FFI call.
Strings returned by FFI through char **out_*fiber-ffi dynamic libraryCaller, using fiber_string_freeRelease after the caller copies or parses it.
Return value of fiber_version()fiber-ffi static storageDo not releaseValid for the process lifetime.
event_json in an event callbackTemporarily created by fiber-ffiDo not releaseValid only during this callback invocation.
Buffer written by fiber_last_error_messageCallerCallerThe 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_json is 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_size must be set.
  • flags must currently be 0.
  • has_* indicates whether the corresponding field is enabled.
  • *_json fields must be valid JSON strings.
  • NULL means 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) | low

For 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_json
  • shutdown_script_json
  • funding_lock_script_json
  • close_script_json
  • trampoline_hops_json
  • custom_records_json
  • hop_hints_json
  • router_json

Rules:

  • NULL means 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 FiberFfiStatus into host language exceptions or Result values.
  • 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 FiberHandle should preferably be serialized.
  • fiber_stop must 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.h published with the dynamic library.
  • Use FIBER_*_OPTIONS_INIT when 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:

  1. Use fiber_start / fiber_stop to manage FiberHandle.
  2. Release all FFI-returned strings with fiber_string_free.
  3. Read error details with fiber_last_error_message after failures.
  4. Copy events in callbacks, then hand them to the host event loop.
  5. Secure storage, the threading model, and platform lifecycle are the responsibility of the host app.