Background
Fiber currently retrieves on-chain data through the RPC interface of a CKB full node. The default testnet configuration uses a public RPC, which makes Fiber easy to deploy and try. For third parties that want to integrate Fiber into their own products, however, this means either relying on a public RPC service over the long term or deploying and maintaining their own CKB full node.
Both choices have clear costs. A public RPC creates a dependency on a particular service provider and may also raise concerns about availability, rate limits, and privacy. Running a full node requires block synchronization, storage, and ongoing maintenance. When Fiber is integrated into a desktop application, mobile application, or local wallet, expecting every user to run a CKB full node is even less practical. At the same time, permanent reliance on a centralized remote service falls short of the independent verification and self-hosting that blockchains are intended to provide.
CKB Light Client offers another option. With relatively modest computing and storage resources, a client can locally synchronize and verify the on-chain data that Fiber needs. If Fiber can use Light Client as its on-chain data source, third-party integrators can reduce their long-term reliance on public RPC services without running a full node.
The goal of this work is therefore to let applications that integrate Fiber run CKB Light Client locally, allowing it to synchronize and verify the required on-chain data directly through the P2P network. This document describes the implementation, usage, and current limitations of this approach.
Overview
This solution links CKB Light Client (ckb-light-client-lib) and fiber-lib into the same process and connects them through a local RPC gateway. Both Fiber and Light Client are managed by fiber-ffi. No upstream Fiber or CKB Light Client source code is modified; all new integration and adapter code lives in fiber-ffi.
This is still an experimental solution rather than a production-ready one. Its API is incomplete, and it has not undergone rigorous testing. Nevertheless, the basic workflows—including Light Client preparation, Fiber startup, channel operations, and payments—already work. The implementation is more than a design or proof of concept.
The overall process has two stages: preparation and startup.
- During preparation, CKB Light Client synchronizes on-chain data until the startup conditions are met.
- Once those conditions are met, Fiber starts while Light Client continues catching up in the background.
Light Client connects to the CKB P2P network, synchronizes and verifies block headers, and then uses filter scripts to scan for the Cells and transactions required by Fiber. This process can take some time, so fiber-ffi continuously reports peer connection, block-header synchronization, and filter-script synchronization progress to the application through a callback.
Once Light Client is prepared, fiber-ffi starts a CKB RPC gateway on an OS-assigned loopback port and configures Fiber to use it as the CKB RPC endpoint. Fiber continues to call CKB JSON-RPC in the same way as before. The adapter converts these calls into queries to the embedded Light Client or P2P data requests. Existing Fiber call paths remain in use for on-chain queries, transaction tracking, watchtower operations, and related tasks. When submitting a transaction, the adapter first uses Light Client to retrieve the required data and validate the transaction locally, then passes it to Light Client for broadcast over the CKB P2P network.
Mobile / desktop application
│
│ 1. Prepare CKB
â–Ľ
fiber-ffi ──────▶ Embedded CKB Light Client ── CKB P2P ──▶ CKB network
│ ▲
│ 2. Start Fiber │
▼ │
Fiber Node ── CKB JSON-RPC ──▶ Local RPC gateway
In this setup, the embedded Light Client handles most CKB data access required by Fiber. An external CKB RPC endpoint may be used only in the following two cases:
- When an existing wallet is imported for the first time, it can help determine where Light Client should begin scanning the funding address’s history.
- When validating peer funding inputs, it can help determine whether an old input that the embedded Light Client does not track is still live.
These uses are narrowly scoped. Requests are not automatically forwarded to an external service whenever a local query fails. Their behavior and limitations are described separately below.
To verify the full workflow, the project includes a Rust native API and fiber-demo-cli, a demo that links fiber-ffi as an rlib. Together they demonstrate Light Client preparation, Fiber startup, and basic channel and payment operations.
Usage
To use the embedded Light Client, enable the Cargo feature disable-ckb-rpc. Fiber still reads its existing YAML configuration. At startup, the value of ckb.rpc_url is replaced in memory with the local RPC gateway address. The two optional operations described above may still access an external RPC endpoint: the caller supplies an RPC address separately when determining where to begin scanning an existing wallet’s history, while peer funding validation uses ckb_light_client.peer_funding_liveness_rpc_url only when the related inputs are not tracked locally.
Preparation and Startup
On the first run, place the CKB funding private key at <database_prefix>/ckb/key.
See the Fiber documentation for instructions on exporting a key.
When importing an existing wallet or restoring a previous node, first call fiber_ckb_funding_address to derive the funding address. Then call fiber_ckb_discover_history_start_block to obtain a suggested block from which Light Client can begin scanning the wallet’s history. Once the block number is available, start the components in this order:
- Call
fiber_prepare_ckb_with_history_start_blockto start Light Client and wait until the block headers and required filter scripts meet the startup conditions. If a starting block has already been saved, callfiber_prepare_ckbinstead. - After receiving
ready, callfiber_startwith the same configuration path and data directory.
The preparation callback reports initializing, wallet_birthday, connecting, syncing_headers, and syncing_scripts progress, and eventually returns either ready or failed. Calling fiber_start before synchronization has reached the required point causes startup to fail.
A ready result from preparation means that block-header and filter-script synchronization have met the startup conditions. It does not guarantee that Light Client has already caught up to the latest chain tip. startup_script_lag_tolerance allows the slowest required script to be a specified number of blocks behind the block height recorded before script scanning begins. Even when this value is zero, the chain tip may advance during the scan. After Fiber starts, continue to call fiber_ckb_readiness to check tip_block_number, indexed_block_number, lag, and wait_estimate. Users should be allowed to open channels only when its ready field is true; otherwise, an attempt to open a channel returns a not-ready error.
Stopping and Restarting
When fiber_stop is called, it stops Fiber and the watchtower first, then shuts down the local RPC gateway, and finally stops the Light Client P2P tasks. Synchronized data and the saved starting block for the funding address remain in the data directory.
The current Fiber FFI allows only one Light Client instance in a process. Stopping the embedded Light Client broadcasts CKB’s process-wide stop signal. This signal cannot be cleared, so the embedded Light Client cannot be started again in the same process. Restart the application process before starting it again.
The cleanup process may also broadcast this process-wide stop signal if the Light Client network has started but preparation subsequently fails—for example, because it cannot connect to enough peers or synchronize to a sufficiently recent block header within the allowed time. After receiving failed in this situation, do not retry in the same process; restart the application process first.
Command-Line Demo
tools/fiber-demo-cli is the Rust demo program for this solution. It links fiber-ffi as an rlib and directly uses the safe, typed fiber_ffi::native::FiberNode API. It can therefore use typed Rust requests and responses instead of assembling JSON payloads internally. The demo shares the same startup and Light Client adapter code as the public C FFI.
Run the following commands from the repository root:
make -C tools/fiber-demo-cli
make -C tools/fiber-demo-cli run
The first command only builds the program; it does not create a wallet private key. The Makefile builds a release version with the sqlite,ckb-light-client-portable Cargo features.
Before the first run, place the private key for a funded CKB testnet wallet at tools/fiber-demo-cli/data/ckb/key. The file must contain exactly 64 hexadecimal characters without a 0x prefix, and its access should be restricted with a command such as chmod 600. The default configuration and password are intended only for the testnet demo and must not be used in production. To change the data directory, log level, or RPC endpoint used to determine where Light Client should begin scanning the wallet, pass options through CLI_ARGS:
make -C tools/fiber-demo-cli run \
CLI_ARGS='--data ./tools/fiber-demo-cli/data \
--log-level info,fiber_ffi=debug \
--ckb-discovery-rpc <https://testnet.ckbapp.dev/>'
The demo configuration deliberately sets ckb.rpc_url to the unreachable address 127.0.0.1:1. At startup, this value is replaced in memory with the local RPC gateway address. The ckb_light_client section provides the additional settings required by Light Client:
ckb:
rpc_url:"<http://127.0.0.1:1>"
ckb_light_client:
peer_funding_liveness_rpc_url:"<https://testnet.ckbapp.dev/>"
startup_min_peers:2
startup_script_lag_tolerance:0
operational_lag_tolerance:6
The scope of peer_funding_liveness_rpc_url is described in Validating Peer Funding Inputs. It cannot serve as a fallback RPC endpoint for other queries. The demo configuration also includes several preferred_peers. Light Client actively maintains connections to these peers while continuing to find independent peers through the bundled bootnodes and peer discovery.
The demo displays the numbered startup steps as follows:
[startup/0] Determining the wallet history start block...
[startup/1] Synchronizing the built-in CKB Light Client...
[startup/2] Initializing and starting Fiber...
[startup/3] Querying the funding wallet balance through the CKB Light Client...
On the first run, the command-line demo must determine the block from which Light Client should begin searching for Cells belonging to the funding address. Through the CKB RPC/Indexer endpoint specified by --ckb-discovery-rpc, it looks for the earliest live Cell under the exact funding lock that has no type script and has empty data. If no such Cell exists, it uses the CKB Indexer tip. To avoid missing blocks near the starting point, the program subtracts a 1,000-block safety window and passes the resulting earlier height to native::prepare_ckb.
This result is only a suggestion derived from currently live Cells that have no type script and have empty data; it is not necessarily the block height at which the wallet was first used. For example, the result may be too late if all such early Cells have already been spent, or if the wallet holds only UDT Cells created earlier. When restoring an existing wallet or node, provide an earlier block if you know that the wallet was used at an earlier height. If there is no reliable way to determine it, start scanning from block 0.
-ckb-discovery-rpcmust connect to the same CKB network specified byfiber.chain. The code that finds the starting block checks only the CKB node and Indexer tips; it does not compare the genesis block hash. If an RPC endpoint for another network is supplied, an incorrect starting block may still be saved.
The starting block for scanning the wallet, data downloaded by Light Client, and filter-script synchronization progress are all stored in the directory specified by --data. Later runs with the same funding address and configuration resume from the saved progress and do not need an external CKB RPC endpoint to find the starting block again.
After Fiber starts, the command-line demo displays node information and the amount of CKB in the funding wallet that is available for channel funding, then opens the Peer, Channel, and Pay menus. A Rust program using the native API can query Light Client readiness, the funding wallet balance, and Fiber node information separately:
let readiness = node.ckb_readiness();
let balance = node.ckb_balance().await?;
let info = node.node_info().await?;
Implementation Details
Running Light Client In-Process
The implementation depends directly on ckb-light-client-lib. It uses the library’s public types to assemble Storage, Consensus, Peers, PendingTxs, the P2P protocols, and NetworkService. It does not start a light-client-bin child process.
These resources are owned by LocalCkbNodeHandle. After preparation completes, the instance is held temporarily. When fiber_start is called, the Fiber node takes over that prepared instance. The Light Client P2P network makes outbound connections to remote peers but does not listen for inbound connections. The local RPC gateway binds to 127.0.0.1:0, allowing the operating system to assign a port, and registers only the methods that Fiber actually needs.
As shown in the configuration example above, the local RPC gateway address is replaced only in memory. Fiber then follows its existing startup path, all other settings remain unchanged, and the YAML file on disk is not modified.
Storage
Fiber and Light Client use different versions of libsqlite3-sys. These versions conflict at build time, so SQLite cannot currently be enabled for both in the same build. The command-line demo uses SQLite for Fiber and RocksDB for Light Client. If both components need to use SQLite in the future, Light Client’s rusqlite dependency could be upgraded.
Even when both components use SQLite, they use separate database files. Fiber’s database is stored at <database_prefix>/fiber/store/data.sqlite, and Light Client’s database is stored at <database_prefix>/ckb-light-client/store/db.sqlite. An existing Light Client RocksDB database cannot be opened directly as SQLite. Without a data conversion step, switching to SQLite requires a full resynchronization.
Filter Scripts
The integration registers filter scripts with Light Client before starting Fiber. The funding lock script is registered with lock as its script_type. Every type_id found in the CellDeps configured under fiber.scripts and ckb.udt_whitelist is registered with type as its script_type. history_start_block specifies the first block that these filter scripts must cover. The preparation stage only accepts and persists this value; it does not access an external RPC endpoint to discover it. Subsequent runs continue to use the persisted value.
The integration checks the progress of every filter script and uses the lowest processed height as indexed_block_number. fiber_ckb_readiness returns the newest verified Light Client header height as tip_block_number, together with lag, the number of blocks between it and indexed_block_number. The embedded RPC gateway exposes the fully indexed snapshot at indexed_block_number consistently as both its chain and Indexer tip, ensuring that Fiber queries only fully processed data. If the newest verified header is more than two hours old, or if lag exceeds operational_lag_tolerance, ready is false.
Light Client first synchronizes and verifies block headers, then checks block filters against the registered filter scripts. It downloads the corresponding full block only when a block filter matches, after which it stores the matching Cells and transactions locally. Light Client reports filter script status through get_scripts, where ScriptStatus.block_number is the filtered block number.
Block-header progress and filter-script progress must be evaluated separately. Reaching the CKB chain tip does not mean that the data required by get_cells and get_transactions has been fully indexed. Taking the minimum ScriptStatus.block_number across all scripts ensures that every filter script has processed the height represented by indexed_block_number. If history_start_block is set too late, earlier Cells and transactions will not enter the local index. If the filter scripts have not yet processed the block required by a query, the request returns a not-ready error rather than an empty result that falsely suggests the Cell or transaction does not exist.
RPC Gateway
The local RPC gateway does not implement every RPC method offered by a CKB full node. It implements only the methods actually called by Fiber and the CKB SDK used by Fiber. These currently include Cell and transaction queries, chain and Indexer tip queries, consensus parameters, selected epoch queries, transaction and block-header retrieval, get_live_cell, and send_transaction. Results are converted to the JSON or Molecule-encoded formats expected by Fiber’s existing CKB RPC client.
If a block header or transaction is not yet available locally, the RPC gateway can request it through the Light Client P2P network, but the request must complete before Fiber’s fixed 10-second RPC timeout. The gateway currently waits up to eight seconds for such data. On timeout, it returns a not-ready error rather than a misleading empty result. Unsupported methods return an explicit error and are never forwarded to a full node after local failure.
send_transaction is not a simple HTTP forward either. The gateway first uses Light Client to fetch and prove all referenced chain data, including inputs, CellDeps, Dep Group members, and HeaderDeps. It checks input liveness and conflicts with locally pending transactions. Light Client then performs non-contextual and contextual verification, including since rules, capacity, minimum fee rate, DAO script size, and script execution. Success means only that the transaction passed local verification and entered Light Client’s PendingTxs queue to await relay. It does not mean that a full node has accepted the transaction into its transaction pool, much less that the transaction has been confirmed. Fiber and the watchtower continue tracking its status through get_transaction and retry according to their existing logic.
Validating Peer Funding Inputs
When opening a dual-funded channel, in which both parties contribute funds, the local node must verify that the peer funding inputs are still live. These inputs may come from old transactions whose Cell scripts have never been tracked by the embedded Light Client. Registering those scripts temporarily and scanning their complete history would allow independent verification, but it is rarely possible to complete that work within a single 10-second RPC request.
An optional peer_funding_liveness_rpc_url can therefore be configured with a CKB full-node RPC endpoint on the same network. The gateway first uses Light Client to retrieve and verify the producing transaction. The Cell’s output and data come only from this verified transaction. The external RPC endpoint is used only to check whether the out_point is currently live. Its Cell contents are ignored, and it is not used for CellDeps, transaction verification, broadcasting, or final funding-transaction confirmation.
An incorrect dead result would cause a valid channel-opening attempt to fail. An incorrect live result could, at most, allow an already-spent input to proceed to local verification or P2P broadcast. A full node would reject the invalid transaction, and Light Client would not consider it confirmed. The external result cannot change the Cell contents on which the local signature is based. Without this external RPC endpoint, an old peer funding input that is not tracked by the embedded Light Client returns a not-ready error.
Limitations
- Light Client still needs time to synchronize. It synchronizes less data than a CKB full node, but it must still synchronize and verify block headers and then scan the filter scripts required by Fiber. Compared with using an already-synchronized CKB RPC endpoint, users must wait longer before they can use Fiber.
- Some operations may still use an external RPC endpoint to avoid excessive waiting. When importing an existing wallet for the first time, an RPC endpoint can suggest where Light Client should begin scanning the wallet’s history. When validating a peer funding input not tracked by Light Client, an RPC endpoint can report whether its Cell is still live. The suggested starting block is estimated only from currently live Cells that have no type script and have empty data, and the caller must ensure that the RPC endpoint belongs to the target network. See Preparation and Startup and Validating Peer Funding Inputs for the exact scope.
- The current command-line demo does not use SQLite for both components. Fiber and Light Client use different versions of
libsqlite3-sys, so the demo uses SQLite for Fiber and RocksDB for Light Client. To use SQLite for both in the future, Light Client’srusqlitecan be upgraded and the Cargo features infiber-ffiadjusted. - Fiber and Light Client still communicate through a local HTTP RPC gateway. Although both run in the same dynamic library and process, Fiber accesses Light Client through the existing CKB RPC interface. This requires a local HTTP server and JSON conversion instead of direct calls to internal Rust interfaces.
- Only one embedded Light Client instance can run, and it cannot be restarted in the same process after it stops. The current implementation limits the number of simultaneous instances and broadcasts CKB’s process-wide stop signal when the instance stops. Cleanup after a preparation failure may also broadcast this signal if the Light Client network has already started. Restarting Light Client requires restarting the application process.