# Fiber Network Documentation (Full) ## What is Fiber Network? Source: https://www.fiber.world/docs Fiber Network is a peer-to-peer payment and swap network built on Nervos CKB. Similar to the Lightning Network, it uses payment channels to move repeated transfers off-chain and reduce the need to submit every payment to Layer 1. Unlike single-asset payment networks, Fiber is designed to support multi-asset payments, stablecoin transfers, asset swaps, and Bitcoin-aligned financial applications. Fiber Network addresses the limitations of using Layer 1 transactions for small, frequent, or time-sensitive payments. While blockchains provide strong settlement guarantees, submitting every payment or balance update on-chain can introduce latency, cost, and scalability constraints. By using CKB as the settlement and enforcement layer, Fiber allows participants to lock assets when opening a channel, exchange signed state updates directly during the channel’s lifetime, and return to Layer 1 only when closing or disputing the channel. This makes Fiber suitable for applications that require low-cost payments, fast settlement experiences, and flexible asset movement. Fiber Network Node (FNN) is the reference node implementation of the Fiber Network Protocol (FNP). ## Features - Multi-asset payments and swaps: Fiber supports multiple asset types, including stablecoins, RGB++ assets issued on the Bitcoin ledger, and UDT assets issued on the CKB ledger. Users can transfer supported assets through payment channels and perform swaps when a channel path with sufficient liquidity is available. - Cross-network interoperability: Fiber is designed to support payments and swaps across compatible payment channel networks, including flows between the Lightning Network and Fiber Network. - Low-cost, low-latency transfers: Fiber enables low-cost micropayments by moving repeated transfers off-chain. Payments are processed directly between involved peers, allowing transfers to complete with low latency and without requiring network-wide consensus for each update. - Multi-hop routing: Payments do not require a direct channel between sender and receiver. If a route with sufficient liquidity exists, payments can be forwarded through intermediate nodes. Node operators can participate in routing and earn fees for forwarding payments. - Privacy-by-default design: Fiber transactions are only visible to the peers involved in the selected payment path. Because transfers do not require global broadcast or network-wide consensus, Fiber can provide improved transaction privacy compared with fully on-chain payment flows. - Watchtower support: Fiber supports watchtower services to help monitor channels and protect users when they are offline. This improves operational safety for users and node operators. - HTLC-based payment security: Fiber currently uses HTLC-based mechanisms to secure conditional payments and maintain compatibility with Lightning-style flows. Future protocol work may introduce PTLC-based mechanisms to improve privacy and security. - CKB Script composability: Fiber channels are built on CKB Scripts, allowing payment channel logic to compose with CKB’s programmable contract model. ## What Can You Build With Fiber? Fiber is useful for applications where payments need to be fast, frequent, low-cost, or programmable. The examples below highlight community-built demos that explore different ways to use Fiber in real applications. ### Usage-Based Payments Fiber can support applications where users pay according to actual usage, such as per second, per page, per request, or per unit consumed. - Fiber Audio Player demonstrates per-second micropayments for self-hosted audio and podcast services. - EV Charging applies usage-based micropayments to an EV charging simulation. ### Checkout and Access Control Fiber can be used to accept low-fee payments and unlock digital goods, services, protected resources, or application features. - Fiber Checkout provides a Stripe-style payment experience for React and Next.js applications. - Fiber L402 explores payment-based access control for online content and services. ### Community and Creator Payments Fiber can support small social payments such as tips, rewards, creator support, memberships, and community incentives. - Fiber Link demonstrates tipping and micropayments inside community platforms. ### Interactive and Autonomous Payments Fiber can support payment flows triggered by games, software agents, connected devices, or other application logic. - Micro-payment Game shows how Fiber can support instant in-game micropayments. - Fiber Pay provides an AI-friendly payment toolchain. - FiberAgentPay explores agent-to-agent commerce over Fiber. - Fiber402 explores machine-driven payment flows for paid data access. ### Network Operations and Developer Tools Fiber also creates opportunities for tools that help developers and node operators understand, integrate, and manage the network. Useful directions include dashboards, node setup tools, liquidity management interfaces, routing monitors, watchtower services, SDKs, testing tools, and local development environments. - Fiber Dashboard provides a real-time view into Fiber network structure and activity. - Fiber Studio wraps the official FNN binary in a guided desktop UI, allowing users to run a Nervos CKB payment-channel node without touching the terminal. For more community-built demos and examples, see the Showcase. ## Roadmap - [x] Connect with other Fiber nodes - [x] Create and close Fiber channels - [x] Send payments over Fiber channels (via fiber-scripts) - [x] Support cross-network asset transfers - [ ] Web-browser-friendly runtime - [ ] Programmable conditional payments - [ ] Advanced channel liquidity management - [ ] Atomic multi-path payments --- ## How Fiber Network Works Source: https://www.fiber.world/docs/how-it-works Fiber Network uses payment channels to move frequent transfers off-chain while using CKB for settlement and enforcement. Participants lock assets on-chain when opening a channel, exchange signed state updates directly while the channel is active, and return to Layer 1 when closing or disputing the channel. This page explains the core mechanisms behind Fiber: payment channels, multi-hop routing, and the difference between off-chain transfers and on-chain settlement. ## Payment Channels A payment channel is a direct relationship between two participants. To open a channel, both participants lock assets into a shared on-chain Script. This funding transaction defines the initial state of the channel. After the channel is opened, participants can start exchanging payments off-chain. Instead of sending every payment to Layer 1, they keep track of the latest balance between themselves and only submit the final result when the channel is closed. When both participants agree to close the channel, the latest mutually signed state is submitted on-chain for settlement. If one participant attempts to close with an outdated state, the on-chain Script enforces the dispute rules and allows the counterparty to challenge it. The simulator below illustrates the lifecycle of a payment channel. Note: For visual clarity, this demo allows up to 7 channels between Node 1 and Node 2. In practice, multiple channels can exist between the same pair of nodes. Use the simulator to observe the channel lifecycle: 1. Select Node 1 and Node 2. 2. Click "Open Channel" to simulate a Layer 1 funding transaction. 3. Send off-chain updates and observe the Layer 2 transaction count. 4. Select a channel and click "Close Channel" to simulate Layer 1 settlement. ## Multi-Hop Routing While a payment channel enables transfers between two participants, Fiber operates as a network of interconnected channels. A direct channel between sender and receiver is not required; if a path with sufficient liquidity exists, a payment can be forwarded through intermediate participants. For example, Alice does not need a direct channel with Carol. If Alice has a channel with Bob, and Bob has a channel with Carol, Bob can forward the payment as an intermediate node. In a multi-hop payment, each intermediary forwards a conditional transfer to the next hop along the route. These transfers are locked under cryptographic conditions and can only be claimed if a required secret is revealed within a defined time window. This ensures that all hops along the path either complete successfully or are safely reverted. Only the nodes involved in the selected path participate in the transaction, and no network-wide consensus is required. Payments therefore traverse the network off-chain while remaining cryptographically enforceable. The simulator below demonstrates multi-hop routing. By clicking "Trigger Sample Transaction," you can observe a transaction being forwarded across intermediate nodes without interacting with Layer 1. ## Interactive Network Simulation This simulator combines the previous concepts in one view. You can open and close channels, trigger routed payments, and observe when activity happens off-chain versus when it requires Layer 1 settlement. Note: For visual clarity, this demo displays only one channel between two nodes. In practice, multiple channels can exist between the same pair of nodes. Experiment by: - Opening channels between unconnected nodes - Triggering transactions across connected paths - Closing channels and observing Layer 1 settlement Notice that: - Layer 2 transaction count increases during off-chain transfers. - Layer 1 operations change only when channels are opened or closed. --- ## Overview Source: https://www.fiber.world/docs/quick-start/run-a-node A Fiber node is a running instance of the Fiber Network Node (FNN) — the reference implementation of the Fiber Network Protocol (FNP). In practical terms, it is the software that connects you to the Fiber peer-to-peer payment network, manages payment channels, routes multi-hop payments, and signs the on-chain transactions that anchor channel state to CKB. If you want to send or receive payments over Fiber, build a wallet that speaks the protocol, or earn routing fees by forwarding payments for others, you need a node. FNN is still under active development. Before upgrading, stop the node and back up its complete data directory. v0.9.0 includes built-in migrations, backup, and restore support, but protocol and storage formats can still change between releases. ## What a Fiber Node Does Running a node gives you a local view of the Fiber network and the ability to: - Maintain payment channels — open, update, and close channels with other nodes. - Send and receive payments — both direct peer payments and multi-hop routed payments. - Swap assets — move value across CKB-native assets (CKB and whitelisted UDTs such as RUSD) and across networks (e.g., between Lightning and Fiber via cross-chain constructs). - Route payments — forward payments for others and collect routing fees if you run a well-connected public node. - Gossip with the network — discover peers, channels, and fee policies through the P2P protocol. All of this happens through a JSON-RPC interface (and an optional CLI wrapper). The node itself is responsible for channel state management, HTLC/PTLC-style locking, and publishing the correct on-chain settlement transaction when a channel closes. ## Node Forms Fiber ships in two forms. The right one depends on where you want to run it and who controls the runtime. ### Native Node (fnn) The native Rust binary is the full production node. It runs as a standalone process on a server or local machine, stores channel state locally (RocksDB or SQLite), and exposes JSON-RPC on 127.0.0.1:8227 by default. | Aspect | Details | |--------|---------| | Runtime | Native binary (Linux, macOS, Windows) | | Best for | Routing nodes, production services, server deployments | | Storage | Local RocksDB/SQLite database | | Network exposure | Public IP recommended for routing; private nodes can connect through public relay nodes | | Interaction | fnn-cli or raw JSON-RPC | Use the native node when you need full control, persistent storage, and the ability to accept inbound channel requests from the public internet. → Run a Native Node ### Deploy the Native Node with Docker If you prefer not to install the Rust toolchain or compile from source, you can deploy the same native fnn binary using the official Docker image. A single docker run command gives you a fully functional native node — it mounts a local data directory for configuration, keys, and channel state, and exposes the P2P and RPC ports just like a locally built binary. | Aspect | Details | |--------|--------| | Runtime | Docker container (Linux, macOS, Windows) | | Best for | Quick native node deployment without build tools, CI/CD pipelines | | Storage | Mounted local volume (RocksDB/SQLite) | | Network exposure | Public IP recommended for routing; private nodes can connect through public relay nodes | | Interaction | docker exec fnn-cli or raw JSON-RPC | Use Docker when you want a fast, reproducible native node deployment without installing build dependencies — ideal for server setups and automated environments. → Run a Native Node (Docker) ### WASM Node (fiber-js) fiber-js compiles the same node logic to WebAssembly so it can run inside a browser. It stores data in IndexedDB (via a WASM worker) and is designed for client-side applications that do not want to operate a backend server. | Aspect | Details | |--------|---------| | Runtime | Browser | | Best for | Browser wallets, web games, client-side dApps | | Storage | IndexedDB | | Network exposure | No public IP required; can connect through relay nodes | | Interaction | JavaScript/TypeScript API (fiber-js package) | Use the WASM node when you want users to bring their own node inside a web app, without asking them to install or host server software. → Run a WASM Node ## Deployment Scenarios ### Local / Testnet Node The fastest way to experiment. You run fnn locally against Fiber Testnet, fund the address from a testnet faucet, and open channels with the public Testnet relay nodes. No public IP is required because you initiate outbound connections to the public nodes. Typical flow: 1. Download or build the fnn binary. 2. Generate or export a CKB private key. 3. Copy config/testnet/config.yml and set FIBERSECRETKEY_PASSWORD. 4. Start the node and connect to a public Testnet node. 5. Open a channel and try Basic Transfer. ### Production Public Node A public node has a reachable P2P address (default port 8228) and is advertised in the network graph. Other nodes can connect to it and open channels. This is the form you want if you intend to: - Accept inbound channels from users. - Route multi-hop payments and earn fees. - Provide liquidity as a service. You will need: - A stable server with a public IP. - A trusted CKB RPC endpoint (especially important on Mainnet). - Sufficient CKB/UDT liquidity to fund and maintain channels. - (Optional) A TLS proxy if you also want browser/WASM clients to reach you over WSS. → See WSS Configuration for browser-facing deployments. ### Private Node Behind Relay Not every node has to be public. A private node can open outbound channels to public relay nodes and then send or receive payments through those relays. This is the easiest way to participate without exposing a public IP or running server infrastructure. This pattern is covered in the Public Nodes User Manual and is the default mental model for the WASM node. ### Browser-Embedded Node With fiber-js, a Fiber node can live inside a web page. Application code creates a Fiber instance and starts it with fiber.start(config, fiberKeyPair, ckbSecretKey, ...); the node can then connect to Testnet bootnodes over WebSocket. This is ideal for wallets, games, and demos where installation friction must be minimal. → Run a WASM Node ## Mainnet vs. Testnet Fiber supports both Mainnet and Testnet deployments. The network is selected in config.yml via the fiber.chain field. | Network | Purpose | CKB RPC Default | Notes | |---------|---------|-----------------|-------| | Testnet | Development and experimentation | https://testnet.ckbapp.dev/ | Free testnet CKB and RUSD available from faucets | | Mainnet | Production value transfer | Your own trusted CKB node or a trusted RPC provider | Public nodes exist, but always verify liquidity and policies | Testnet is the recommended place to start. Once you are comfortable with channel management, payment flows, and backup procedures, move the same setup to Mainnet. ## Before You Start At a minimum, you need a CKB private key. You can generate one with ckb-cli or export it from a CKB wallet. The detailed prerequisites — CKB RPC endpoints, config.yml, how much CKB to reserve, and whether a one-way channel lets the other side join without depositing CKB — are covered in the native (build from source or Docker) and WASM guides. Keep your node data directory, especially fiber/store and the ckb/key file, backed up and secure. Losing channel state can mean losing funds. ## Choose Your Path | I want to... | Recommended node | |--------------|------------------| | Run a routing node and earn fees | Native Node (or via Docker) | | Build a server-side payment service | Native Node (or via Docker) | | Add Fiber to a browser wallet or web game | WASM Node | | Try Fiber without hosting infrastructure | WASM Node or Native Node behind public relays | | Learn the protocol locally | Native Node on Testnet | After you have a node running, continue with Basic Transfer to send your first payment. --- ## Run a Native Node Source: https://www.fiber.world/docs/quick-start/run-a-node/rust ## TL;DR Download the FNN binary, set up a private key, configure config.yml, and start the node. In 5 minutes you'll have a Fiber node running on Testnet. ## Prerequisites - Git (if building from source) - Rust and Cargo (if building from source) - Basic understanding of command line operations - ckb-cli tool for key management ## Building and Setting Up Your Node ### 1. Obtain the FNN Binary Download the archive for your operating system and CPU from the v0.9.0 release. The portable archives are the easiest option because they include fnn, fnn-cli, and the Mainnet and Testnet configuration templates. For example, on an Apple Silicon Mac: [sh code block] The expected versions are fnn Fiber v0.9.0 and fnn-cli 0.9.0. Replace aarch64-darwin with the target matching your system; the release also provides archives for Intel macOS, x86-64 and ARM64 Linux, and x86-64 Windows. Alternatively, build the tagged source release: [sh code block] If you're using macOS, the downloaded binary may be blocked by Gatekeeper. To resolve this, remove the quarantine attribute: [sh code block] ### 2. Create Node Directory From the extracted release directory, create a dedicated directory for your node and copy the included binaries and Testnet configuration: [sh code block] If you built from source, copy target/release/fnn, target/release/fnn-cli, and config/testnet/config.yml from the cloned repository instead. ### 3. Set Up Node Keys FNN includes built-in wallet functionality for signing funding transactions. You'll need to create or import a private key: create a new ckb account and get the lockarg of the new account [sh code block] Then export the existing key [sh code block] The key file must contain one raw 64-character hex private key without a 0x prefix. Keep the file private and never reuse a documentation or Testnet key on Mainnet. ### 4. Start the Node Launch your node with logging enabled: Set FIBERSECRETKEYPASSWORD to a strong password. On the first start, FNN encrypts the plaintext ckb/key file in place. [sh code block] > Expected Output: You should see the node start, migrate the key to its encrypted format, listen on P2P port 8228, connect to the Testnet bootnodes, and create an initial backup under fiber/backups. The RPC server will be available at http://127.0.0.1:8227. ## Interacting with Your Node Once your node is running, you can interact with it using the RPC interface or the CLI tool. ### Using the CLI Tool (v0.8.0+) v0.8.0 introduces fnn-cli, a command-line interface that provides a convenient way to manage your node without writing raw JSON-RPC requests: [sh code block] > Expected Output: ./fnn-cli info should return your node's pubkey, alias, and chain info. If you see a connection error, ensure the node is running and NO_PROXY is set for local addresses. The CLI connects to the node's RPC endpoint (default: http://127.0.0.1:8227). If you encounter a 503 Service Unavailable error, check if your system has an HTTP proxy configured. The CLI may attempt to route requests through the proxy, which can fail for local addresses. Solution: Set NOPROXY to exclude local addresses: [sh code block] ### Using JSON-RPC Directly You can also interact with the node using any HTTP client: [sh code block] ## Version Compatibility and Upgrades FNN is under active development, and protocol/storage format changes may occur between versions. Here's how to handle upgrades: v0.8.0 introduces several RPC changes that may affect existing integrations: | Change | v0.7.1 | v0.8.0 | |--------|--------|--------| | Node identifier | peerid (base58) | pubkey (hex-encoded secp256k1) | | nodeinfo response field | nodeid | pubkey | | JSON enum format | PascalCase | snakecase (e.g., CkbHash → ckbhash) | | ChannelState format | Regular | SCREAMINGSNAKECASE with pipe separator | Affected RPC methods: openchannel, listchannels, disconnectpeer, connectpeer, nodeinfo. New RPC methods in v0.8.0: - openchannelwithexternalfunding - For hardware wallet/external signing - submitsignedfundingtx - Submit externally signed transactions - list_payments - Query payment sessions ### Safe Upgrade Process 1. Stop the old node cleanly. Never run two FNN processes against the same data directory. 2. Back up the complete node directory, including ckb, fiber/store, fiber/sk, and config.yml. 3. Replace both fnn and fnn-cli with the v0.9.0 binaries. 4. Start FNN against the existing directory and review any migration confirmation before proceeding. Do not delete fiber/store during an upgrade. It contains channel state, and losing it can put funds at risk. See Backup and Restore for backup contents, validation, and recovery steps. ### Storage Migration Starting from v0.9.0, storage migration is built into FNN. A v0.8.x database can be migrated when the new node starts; databases older than v0.8.x must first be upgraded with the matching v0.8.x fnn-migrate tool. Do not skip the backup, and do not bypass an unexpected migration error by deleting the store. ## Next Steps - Basic Transfer — send your first payment - Config Reference — full configuration options --- ## Run a Native Node (Docker) Source: https://www.fiber.world/docs/quick-start/run-a-node/docker ## TL;DR Pull the image, mount a data directory with your CKB private key, and start the container. In under a minute you'll have a Fiber node running on Testnet — no Rust toolchain required. [bash code block] ## Prerequisites - Docker (v20.10 or newer) installed and running - A CKB private key (generate one with ckb-cli or export from an existing wallet) - Basic understanding of command line operations ## Running Your Node ### 1. Pull the Docker Image The Fiber image is published to two registries: | Registry | Image | | |----------|-------|-| | Docker Hub | nervos/fiber: | Default, widely used | | GitHub Container Registry | ghcr.io/nervosnetwork/fiber: | Alternative if Docker Hub is unavailable | [bash code block] Check Docker Hub or GHCR for available tags. For production, pin a specific version tag (for example, 0.9.0) to avoid unexpected upgrades. ### 2. Prepare the Data Directory Create a local directory to hold the node's configuration, database, and key material: [bash code block] Place your CKB private key at ./fiber-node/ckb/key. If you already have a key exported by ckb-cli, copy the first line of the extended privkey file: [bash code block] The key file must contain a raw 64-character hex string WITHOUT the 0x prefix. If you are using a key exported by ckb-cli, make sure to remove the 0x prefix before saving it to ./fiber-node/ckb/key. If no config.yml exists in the data directory on first start, the container automatically copies the bundled Testnet configuration template. You can then edit ./fiber-node/config.yml and restart the container to apply changes. ### 3. Run the Container Start the node with the following command: [bash code block] | Port | Purpose | Exposure | |------|---------|----------| | 8228 | P2P networking | Mapped to the host; must be reachable for public nodes | | 8227 | JSON-RPC | Listens on 127.0.0.1 inside the container by default (not mapped) | > Expected Output: You should see log lines indicating the node is starting, connecting to bootnodes, and syncing with the Testnet. Press Ctrl+C to stop the node (add -d instead of -it to run in detached mode). On first run, the node will automatically encrypt the plaintext key file using the password provided via FIBERSECRETKEYPASSWORD. After this, the ckb/key file will be in encrypted binary format. This is normal and expected behavior. ### 4. Environment Variables The container reads its configuration through the following environment variables: | Variable | Description | Required | |----------|-------------|----------| | FIBERSECRETKEYPASSWORD | Password to encrypt/decrypt the CKB private key | Yes | | RUSTLOG | Log level (e.g. info, debug) | No | | FIBERCONFIGTEMPLATE | Path to config template inside container | No | | FIBERCONFIG | Path to custom config file | No | | FIBERHOME | Base directory (default: /fiber) | No | ### 5. Use Mainnet Configuration By default the container starts on Testnet. To run on Mainnet, point FIBERCONFIGTEMPLATE to the bundled mainnet template: [bash code block] On first run with the mainnet template, a config.yml is generated from the template in the data directory. Review and adjust the CKB RPC endpoint and other settings before funding channels with real value. ### 6. Interact with the Node Use docker exec to run fnn-cli commands inside the running container: [bash code block] ### 7. Expose RPC (Advanced) By default the RPC endpoint listens on 127.0.0.1:8227 inside the container and is not mapped to the host. If you need external access, edit config.yml in your data directory to set rpc.listeningaddr to 0.0.0.0:8227, then map the port when starting the container: [bash code block] Once the RPC port is mapped, you can call the JSON-RPC interface directly from the host: [bash code block] Exposing the RPC port to the public internet allows remote callers to control your node, including opening channels and sending payments. Keep the host-side mapping on 127.0.0.1 unless you have configured v0.9.0 RPC authentication and a secure access layer. Do not publish an unauthenticated RPC endpoint. ### 8. Build the Image Locally If you want to build from source — for example to test an unreleased branch — clone the repository and build the image: [bash code block] Then run the locally built image using fiber:local instead of nervos/fiber:0.9.0. ### 9. Upgrade an Existing Data Directory Stop the old container and back up the complete bind-mounted directory before changing image tags: [bash code block] Then start nervos/fiber:0.9.0 with the same /fiber mount. v0.9.0 has migration support built into fnn; do not delete fiber/store and do not run two versions against the same directory. v0.9.0 cannot directly migrate a pre-v0.8.x database. First use the matching v0.8.x fnn-migrate tool, then start v0.9.0. Follow Backup and Restore rather than guessing a migration command for valuable channel data. ## Next Steps - Basic Transfer — send your first payment - Config Reference — full configuration options --- ## Run a WASM Node Source: https://www.fiber.world/docs/quick-start/run-a-node/fiberjs ## TL;DR Use @nervosnetwork/fiber-js to run a Fiber node inside a browser app. Install the package, serve a Fiber YAML config, create a Fiber instance on a cross-origin isolated page, and call start(); application code should not depend on the internal fiber-wasm package directly. Interactive Quick Start Run a real Fiber node in this browser Start Fiber WASM, complete a real WSS peer handshake, derive this browser's Testnet CKB address, open a channel, and send a keysend payment. The lab is isolated from the docs page because Fiber WASM requires SharedArrayBuffer. Open Browser Node Lab → ## Try It Before You Integrate It The lab runs the node locally in your browser; it is not a UI that remotely controls a hosted Fiber node. Browsers cannot open raw TCP sockets, so the WASM node connects outbound to a native public node through WSS: [text code block] The interactive flow uses fiber-testnet-public-bottle from Network Resources. It verifies each stage with live SDK state: 1. Start the WASM node and wait until listPeers() confirms the WSS handshake. 2. Display the browser node's CKB address and query its Testnet balance. 3. Fund that address and open a real Testnet channel with the public node. 4. Enter an amount and send an invoice-free keysend payment after the channel reaches Ready. The lab stores one Testnet identity in the current browser so the funding address survives refreshes. Different browser profiles receive different addresses, and clearing site data creates a new one. Do not send Mainnet funds to the lab address. The public node's current channel threshold is listed in Network Resources, and additional CKB is required for transaction fees. ## What is fiber-js? fiber-js is a JavaScript/TypeScript wrapper around the Fiber WebAssembly (WASM) node. It runs in browser-based applications and provides common Fiber node operations without running a separate backend service. | | Native Node | WASM Node | |---|---|---| | Runtime | Native binary | Browser | | Deployment | Server or local machine | Embedded in web app | | Public IP required | Yes | No, use outbound /ws/ or /wss/ peers | | Storage | File system (SQLite/RocksDB) | IndexedDB (via WASM worker) | | Use case | Production node operators | Browser wallets, web games, client-side dApps | ## Prerequisites You need a JavaScript development environment before using fiber-js: - Node.js with npm - A browser-oriented build tool or framework, such as Vite, Next.js, Webpack 5, or an equivalent ESM toolchain The examples below use npm. If your project already uses pnpm, use the equivalent pnpm command. ## Installation [bash code block] Download the v0.9.0 Testnet config (the same template included in the native release archives) and serve it from your app: [bash code block] The native template contains TCP bootnodes, which browsers cannot reach. Edit the existing fiber fields in testnet.yml to use browser-reachable WSS peers and avoid announcing a browser-local address. For example: [yaml code block] Keep the chain, scripts, ckb, and services sections from the downloaded template. See Network Resources for the current browser-reachable public node list. An address such as /ip4/.../tcp/8228/p2p/... works for native FNN but not for browser WASM. Browser peers must expose /ws/ or /wss/; use WSS on HTTPS pages to avoid mixed-content blocking. new Fiber() creates SharedArrayBuffer objects immediately. Only create a Fiber instance on a cross-origin isolated page, and make sure your deployment serves WASM and worker assets with the required CORS and isolation headers. ## Quick Start [ts code block] > Expected Output: The node initializes, connects to a configured /ws/ or /wss/ peer, and starts syncing. nodeInfo() returns your node's pubkey, version: "0.9.0", and chain info; listPeers() should report at least one peer when the selected public node is reachable. ## Common Workflows ### Open a Channel and Send a Payment Replace peerPubkey and peerAddress with values for the same target peer. openChannel() requires the CKB secret key passed to start() to control spendable CKB. Browser wallet integrations usually use openChannelWithExternalFunding() instead. [ts code block] ### Keysend (No Invoice) [ts code block] ## Error Handling [ts code block] Common errors: - fiber-js requires a cross-origin isolated page: serve the page with isolation headers before calling new Fiber(). - Fiber is not started: await fiber.start(...) before invoking node methods. - Connection failures: browser nodes usually need remote /ws/ or /wss/ peer addresses, not plain /tcp/ addresses. - Configuration errors: check that the loaded YAML config matches the target network and includes browser-reachable bootnodes. ## API Reference @nervosnetwork/fiber-js exports the Fiber class. Its methods are camelCase wrappers around Fiber RPC commands, and async methods return promises that reject on command errors. | Area | Key Methods | |--------|--------------| | Lifecycle | start, stop, invokeCommand | | Channel | openChannel, openChannelWithExternalFunding, submitSignedFundingTx, listChannels, shutdownChannel, updateChannel | | Payment | sendPayment, getPayment, buildRouter, sendPaymentWithRouter | | Invoice | newInvoice, parseInvoice, getInvoice, cancelInvoice | | Peer | connectPeer, disconnectPeer, listPeers | | Graph | graphNodes, graphChannels | | Node | nodeInfo | For the full API reference see Build -> js. ## Next Steps - Open Browser Node Lab — run the complete browser flow - Basic Transfer — learn the channel and payment flow - JavaScript Overview — browser runtime, security headers, and API details - Build a Game with Fiber — end-to-end example with Fiber payments --- ## Basic Transfer Example Source: https://www.fiber.world/docs/quick-start/basic-transfer ## TL;DR Set up two local nodes, open a channel between them, and send a CKB payment from one to the other. The whole process takes about 10 minutes. ## Overview This guide walks you through setting up and executing a basic token (CKB) transfer between two nodes on the Fiber Testnet. ## Prerequisites - Git (if building from source) - Rust and Cargo (if building from source) - Basic understanding of command line operations - curl for making RPC calls - ckb-cli for generating keys ## Setting Up Your Nodes ### 1. Prepare Fiber Binary Download and extract the v0.9.0 portable archive as described in Run a Native Node. The extracted directory contains fnn, fnn-cli, and config/testnet/config.yml. Alternatively, build the tagged source release: [sh code block] If you're using macOS, remove the quarantine attribute: [sh code block] If you encounter 503 errors when using fnn-cli, run: [sh code block] ### 2. Create Data Directories [sh code block] If you built from source, copy target/release/fnn and target/release/fnn-cli instead. ### 3. Configure Node Keys Each node needs its own private key. Create two separate CKB accounts: [sh code block] Export the keys: [sh code block] The ckb/key file must contain only the 64-character hex private key (first line), without 0x prefix. Get Testnet funds from https://faucet.nervos.org for both nodes. ### 4. Configure Ports Edit config.yml for each node: - Node 1: RPC Port 8227, P2P Port 8228 - Node 2: RPC Port 8237, P2P Port 8238 Keep Node 1's release config unchanged. Change only Node 2's listening ports: [sh code block] The expandable block below shows the essential Testnet settings for reference. Prefer the release config as your source so deployment scripts and dependencies stay current. View complete config.yml example [yaml code block] ## Step-by-Step Transfer Process ### 1. Start Both Nodes [sh code block] ### 2. Connect the Nodes First get Node 2's pubkey, then connect from Node 1: [sh code block] [sh code block] [typescript code block] ### 3. Open a Payment Channel [sh code block] [sh code block] [typescript code block] Check channel status — wait until statename becomes ChannelReady: [sh code block] [sh code block] [typescript code block] ### 4. Generate an Invoice Create a payment invoice on Node 2 for 100 CKB: [sh code block] [sh code block] [typescript code block] ### 5. Make the Payment [sh code block] [sh code block] [typescript code block] sendpayment can return Created or Inflight before the transfer finishes. Treat the payment as complete only after getpayment reports Success; inspect failederror when it reports Failed. ### 6. Verify the Transfer Check channel balances to confirm the transfer: [sh code block] [sh code block] [typescript code block] After sending 100 CKB from Node 1, Node 1's localbalance decreases by 100 CKB and remotebalance increases by 100 CKB. ## Closing the Channel [sh code block] [sh code block] [typescript code block] Get the complete closescript from fnn-cli info under defaultfundinglockscript. A cooperative close (force: false) is fast and requires both peers to be online. Use force: true only when the peer is unavailable; a force close uses the channel's saved close parameters and locks funds until the commitment delay passes. ## Next Steps - Transfer Stablecoins — open a RUSD channel and make a direct stablecoin payment - Network Resources — public nodes, faucets, explorers, and more --- ## Transfer Stablecoins Source: https://www.fiber.world/docs/quick-start/transfer-stablecoin ## TL;DR Set up two nodes, open a UDT (stablecoin) channel, and send RUSD directly between them. Builds on the Basic Transfer example. ## Overview This guide demonstrates a direct UDT transfer using Testnet RUSD. For routing through relay nodes, continue with Multi-hop Transfers after completing this guide. ## Prerequisites - Completed Basic Transfer - curl for RPC calls - ckb-cli for key management ## Setting Up Your Environment ### 1. Prepare Fiber Binary Download the v0.9.0 package for your platform from GitHub Releases. For example, on an Apple Silicon Mac: [sh code block] The final command should report Fiber v0.9.0. To build instead, check out the matching tag with git clone --branch v0.9.0 --depth 1 https://github.com/nervosnetwork/fiber.git before running cargo build --release. [sh code block] ### 2. Configure Two Nodes [sh code block] If you built from source, copy target/release/fnn and target/release/fnn-cli instead of the release-package paths shown above. Create a CKB account for each node and export the keys: [sh code block] ckb/key must contain only the 64-character hex private key, no 0x prefix. Get Testnet funds: - CKB: https://faucet.nervos.org - RUSD: https://testnet0815.stablepp.xyz/faucet (claim through a wallet such as JoyID Testnet, then transfer it to the node address shown by ckb-cli util key-info --privkey-path ./ckb/key) ### 3. Configure Ports - Node 1: RPC 8227, P2P 8228 - Node 2: RPC 8237, P2P 8238 Keep Node 1's release config unchanged. Change only Node 2's listening ports: [sh code block] The v0.9.0 Testnet config already contains the current RUSD type script and cell dependency. Its local autoacceptamount is 10 RUSD; this guide funds 20 RUSD to match the minimum accepted by the official Testnet public nodes. ### 4. Start Both Nodes [sh code block] ## Creating Stablecoin Payment Channels ### 1. Connect Node 1 and Node 2 [sh code block] [sh code block] [typescript code block] Get Node 2's pubkey: cd node2 && ./fnn-cli --url http://127.0.0.1:8237 info. save: false avoids persisting a local address that includes no routable /p2p/... suffix. ### 2. Open a Stablecoin Channel (Node 1 → Node 2) The fundingudttypescript identifies the RUSD token: [sh code block] [sh code block] [typescript code block] Opening a UDT channel reserves about 99 CKB from each participant for on-chain cell capacity and shutdown fees. Fund both node accounts with CKB, and fund Node 1 with at least 20 RUSD. The 20 RUSD amount also meets the official Testnet public nodes' current auto-accept threshold. Fiber passes UDT amounts through as raw unsigned integers and does not apply the token's display decimals. Therefore, --funding-amount 10 is valid, but for this 8-decimal Testnet RUSD deployment it funds 10 base units (0.00000010 RUSD), not 10 RUSD. ### 3. Monitor Channel Status Wait until state.statename becomes "ChannelReady": [sh code block] [sh code block] [typescript code block] ## Generating Invoices and Making Payments ### 1. Generate a Stablecoin Invoice on Node 2 [sh code block] [sh code block] [typescript code block] The paymentpreimage is auto-generated by both CLI and RPC. You can optionally provide your own with "paymentpreimage": "" in the RPC params if needed. ### 2. Send the Stablecoin Payment from Node 1 [sh code block] [sh code block] [typescript code block] After a successful 1 RUSD payment, Node 1's local RUSD balance decreases by 100000000 base units and Node 2's local RUSD balance increases by the same amount. sendpayment can initially return Created or Inflight. Poll getpayment with the returned paymenthash until the status is Success or Failed before treating the transfer as complete. ### 3. Check Channel Balance [sh code block] [sh code block] [typescript code block] ## Closing the Channel [sh code block] [sh code block] [typescript code block] Get args from fnn-cli info under defaultfundinglockscript.args. The feerate (shannons/KW) is optional and defaults to 1000. You can also use --force (CLI) or "force": true (RPC) to force-close a channel unilaterally, in which case closescript and fee_rate are ignored. ## Important Notes - RUSD amounts: This Testnet RUSD has 8 display decimals. CLI uses decimal base units and RPC/SDK examples use hex quantities: 100000000 = "0x5f5e100" = 1 RUSD, while 2000000000 = "0x77359400" = 20 RUSD. - CKB and shannons: CKB independently uses 8 decimal places (1 CKB = 100000000 shannons). The numeric multipliers happen to match, but RUSD base units are not shannons. - Invoice currency: Use "Fibt" for testnet, not "RUSD" - **autoacceptamount: This field is also expressed in raw UDT base units. The official Testnet public-node value is 2000000000 ("0x77359400" over RPC), which represents 20 RUSD. Setting it to decimal 20 would mean only 20 base units (0.00000020 RUSD). Channels below the configured raw value require manual acceptance. - Channel state format**: The listchannels response returns state as a nested object: state.statename (e.g. "ChannelReady") and state.state_flags (e.g. ["PublicChannel"]) ## Next Steps - Multi-hop Transfers — route a payment through public relay nodes - Network Resources — find public nodes, faucets, and explorers --- ## Multi-hop Transfers Source: https://www.fiber.world/docs/quick-start/multi-hop-transfer ## TL;DR Run two local v0.9.0 nodes, connect each one to a different public Testnet relay, open two public channels, and pay an invoice from Node A to Node B. Neither local node needs a public IP. ~~~text ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ Node A │ ────▶ │ bottle │ ────▶ │ bracer │ ────▶ │ Node B │ │ :8227 │ │ public │ │ public │ │ :8237 │ └────────┘ └────────┘ └────────┘ └────────┘ sender relay 1 relay 2 receiver ~~~ Fiber supports two routing modes: - Gossip routing: Node A learns the network graph and builds the route. - Trampoline routing: Node A delegates route selection to a relay, useful for clients without a complete graph. ## Prerequisites - Complete Basic Transfer first. - Use the v0.9.0 fnn, fnn-cli, and config/testnet/config.yml files from the same release package. - Fund each local node with at least 561 Testnet CKB: 499 CKB for the channel, about 61 CKB for a change cell, and a small transaction-fee margin. Testnet CKB has no real-world value, but opening a channel still creates an on-chain transaction and temporarily locks the funds. Use throwaway Testnet accounts and close both channels when the test is complete. ## Public Testnet Relays | Relay | Pubkey | Native TCP address | |------|--------|--------------------| | bottle | 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 | /dns4/bottle.fiber.channel/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo | | bracer | 0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc | /dns4/bracer.fiber.channel/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89 | Browser/WASM clients must use the corresponding /tcp/443/wss/ addresses listed in Network Resources. Native fnn uses the TCP addresses above. ## 1. Prepare Two Local Nodes Create Node A with the default ports. Create Node B from a second copy of the release config and change its ports: ~~~sh mkdir -p nodeA/ckb nodeB/ckb cp config/testnet/config.yml nodeA/config.yml cp config/testnet/config.yml nodeB/config.yml sed -i.bak 's|/ip4/0.0.0.0/tcp/8228|/ip4/127.0.0.1/tcp/8238|' nodeB/config.yml sed -i.bak 's|127.0.0.1:8227|127.0.0.1:8237|' nodeB/config.yml ~~~ Export a different CKB private key into nodeA/ckb/key and nodeB/ckb/key. Each file must contain exactly 64 hex characters without a 0x prefix and should be readable only by its owner: ~~~sh chmod 600 nodeA/ckb/key nodeB/ckb/key ~~~ Start the nodes in separate terminals: ~~~sh # Terminal 1 FIBERSECRETKEYPASSWORD='choose-a-strong-password-a' \ RUSTLOG=info ./fnn -c nodeA/config.yml -d nodeA # Terminal 2 FIBERSECRETKEYPASSWORD='choose-a-strong-password-b' \ RUSTLOG=info ./fnn -c nodeB/config.yml -d nodeB ~~~ If a system-wide proxy intercepts local RPC traffic, set NOPROXY=127.0.0.1,localhost. ## 2. Connect Each Node to a Relay Connect Node A to bottle and Node B to bracer. The CCC examples use one client for each local RPC endpoint: ~~~typescript const nodeA = new FiberSDK({ endpoint: "http://127.0.0.1:8227" }); const nodeB = new FiberSDK({ endpoint: "http://127.0.0.1:8237" }); ~~~ ~~~sh # Node A → bottle ./fnn-cli --url http://127.0.0.1:8227 peer connectpeer \ --address '/dns4/bottle.fiber.channel/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo' \ --pubkey 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 \ --save true # Node B → bracer ./fnn-cli --url http://127.0.0.1:8237 peer connectpeer \ --address '/dns4/bracer.fiber.channel/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89' \ --pubkey 0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc \ --save true ~~~ ~~~sh # Node A → bottle curl -s http://127.0.0.1:8227 \ -H 'Content-Type: application/json' \ -d '{ "id": 1, "jsonrpc": "2.0", "method": "connectpeer", "params": [{ "address": "/dns4/bottle.fiber.channel/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", "pubkey": "02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71", "save": true }] }' # Node B → bracer curl -s http://127.0.0.1:8237 \ -H 'Content-Type: application/json' \ -d '{ "id": 1, "jsonrpc": "2.0", "method": "connectpeer", "params": [{ "address": "/dns4/bracer.fiber.channel/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", "pubkey": "0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc", "save": true }] }' ~~~ ~~~typescript await nodeA.connectPeer({ address: "/dns4/bottle.fiber.channel/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo", pubkey: "02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71", save: true, }); await nodeB.connectPeer({ address: "/dns4/bracer.fiber.channel/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89", pubkey: "0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc", save: true, }); ~~~ Run peer listpeers on both local RPC endpoints and confirm that the expected relay pubkey appears. ## 3. Open Both Public Channels The documented public relays auto-accept a channel funded with at least 499 CKB. In shannons, 499 CKB is 49900000000; its RPC hex quantity is 0xb9e459300. ~~~sh # Node A ↔ bottle ./fnn-cli --url http://127.0.0.1:8227 channel openchannel \ --pubkey 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 \ --funding-amount 49900000000 \ --public true # Node B ↔ bracer ./fnn-cli --url http://127.0.0.1:8237 channel openchannel \ --pubkey 0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc \ --funding-amount 49900000000 \ --public true ~~~ ~~~sh # Node A ↔ bottle curl -s http://127.0.0.1:8227 \ -H 'Content-Type: application/json' \ -d '{ "id": 2, "jsonrpc": "2.0", "method": "openchannel", "params": [{ "pubkey": "02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71", "fundingamount": "0xb9e459300", "public": true }] }' # Node B ↔ bracer curl -s http://127.0.0.1:8237 \ -H 'Content-Type: application/json' \ -d '{ "id": 2, "jsonrpc": "2.0", "method": "openchannel", "params": [{ "pubkey": "0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc", "fundingamount": "0xb9e459300", "public": true }] }' ~~~ ~~~typescript await nodeA.openChannel({ pubkey: "02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71", fundingAmount: "0xb9e459300", public: true, }); await nodeB.openChannel({ pubkey: "0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc", fundingAmount: "0xb9e459300", public: true, }); ~~~ Poll both nodes until their channels report state.statename: "ChannelReady": ~~~sh ./fnn-cli --url http://127.0.0.1:8227 channel listchannels ./fnn-cli --url http://127.0.0.1:8237 channel listchannels ~~~ A channel can become ready before its announcement has propagated through the network. If the first payment reports Failed to build route, wait a few minutes and try again. ## 4. Create an Invoice on Node B Create a 1 CKB invoice on the receiver's local RPC endpoint. 100000000 shannons equals 1 CKB. ~~~sh ./fnn-cli --url http://127.0.0.1:8237 invoice newinvoice \ --amount 100000000 \ --currency Fibt \ --description "multi-hop test" \ --expiry 3600 ~~~ ~~~sh curl -s http://127.0.0.1:8237 \ -H 'Content-Type: application/json' \ -d '{ "id": 4, "jsonrpc": "2.0", "method": "newinvoice", "params": [{ "amount": "0x5f5e100", "currency": "Fibt", "description": "multi-hop test", "expiry": "0xe10" }] }' ~~~ ~~~typescript const { invoiceAddress } = await nodeB.newInvoice({ amount: "0x5f5e100", currency: "Fibt", description: "multi-hop test", expiry: "0xe10", paymentPreimage: "0x" + Array.from( crypto.getRandomValues(new Uint8Array(32)), (byte) => byte.toString(16).padStart(2, "0"), ).join(""), }); console.log("Invoice:", invoiceAddress); ~~~ The CLI and RPC generate a random payment preimage when neither paymentpreimage nor paymenthash is supplied. ## 5. Send and Verify the Payment ### Gossip routing No routing parameter is required: ~~~sh ./fnn-cli --url http://127.0.0.1:8227 payment sendpayment \ --invoice '' ~~~ ### Trampoline routing To delegate route finding to bottle, pass its pubkey as the trampoline hop: ~~~sh ./fnn-cli --url http://127.0.0.1:8227 payment sendpayment \ --invoice '' \ --trampoline-hops 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 ~~~ The equivalent RPC field is "trampolinehops": ["02b6...be71"]; the CCC field is trampolineHops. sendpayment may return Created or Inflight. Copy its paymenthash and poll until the status is Success or Failed. ~~~sh ./fnn-cli --url http://127.0.0.1:8227 payment getpayment \ --payment-hash ~~~ After Success, compare channel listchannels on Node A and Node B. Node A's local balance decreases by the invoice amount plus relay fees; Node B's local balance increases by the invoice amount. ## 6. Close Both Channels Get each channelid from listchannels and each complete defaultfundinglockscript from info. Request a cooperative close while both relay connections are online: ~~~sh # Close Node A's channel on port 8227. ./fnn-cli --url http://127.0.0.1:8227 channel shutdownchannel \ --channel-id \ --close-script '' \ --fee-rate 1000 \ --force false # Close Node B's channel on port 8237. ./fnn-cli --url http://127.0.0.1:8237 channel shutdown_channel \ --channel-id \ --close-script '' \ --fee-rate 1000 \ --force false ~~~ Wait for both closing transactions to confirm before deleting either data directory. Use force: true only if the peer is unavailable; a force close locks funds until the commitment delay passes. ## Next Steps - Network Resources — current relay addresses, faucets, explorers, and dashboards - Operate a Node — backups, monitoring, and production configuration --- ## Network Resources Source: https://www.fiber.world/docs/quick-start/network-resources This page collects the public nodes, faucets, explorers, and tools used by the v0.9.0 quick-start guides. The endpoints below were checked on 2026-08-17. Testnet resources may change without notice. This page is updated to reflect the current state. If you find outdated information, please open an issue. ## Public Nodes ### Mainnet | Name | Pubkey | WSS Address | Minimum channel funding | |------|--------|-------------|------------------| | fiber-mainnet-public-ca | 03a8d7da8d0934363dbc17f52c872e8d833016415266eabb3527439c5dd17adc6b | /dns4/ca.fiber.channel/tcp/443/wss/p2p/QmZCfzENZqWrWwifJj9BFDvxQWFyYw5GjdB4vN7Ynd4FxY | ≥ 499 CKB | | fiber-mainnet-public-tokyo | 033a69e5be369dab43aefa96fa729d83c571ccb066f312136c6ab2d354fcc028f9 | /dns4/tokyo.fiber.channel/tcp/443/wss/p2p/QmZ73KHvZ5GFxf6XhHZ3icPeKFo93rk86kZ8qauox3avJP | ≥ 499 CKB | ### Testnet | Name | Pubkey | Native TCP address | Browser WSS address | Minimum funding | |------|--------|--------------------|---------------------|-----------------| | fiber-testnet-public-bottle | 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 | /dns4/bottle.fiber.channel/tcp/8119/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo | /dns4/bottle.fiber.channel/tcp/443/wss/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo | 499 CKB or 20 RUSD | | fiber-testnet-public-bracer | 0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc | /dns4/bracer.fiber.channel/tcp/8119/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89 | /dns4/bracer.fiber.channel/tcp/443/wss/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89 | 499 CKB or 20 RUSD | Since v0.8.0, Fiber RPCs identify peers by pubkey (hex-encoded secp256k1). A synced node can resolve a pubkey from gossip, but quick-start commands pass both the pubkey and explicit address so the first connection does not depend on graph synchronization. Native nodes use TCP; browser/WASM nodes use WSS. ## Faucets | Resource | URL | Notes | |----------|-----|-------| | CKB Testnet Faucet | https://faucet.nervos.org | Get testnet CKB | | cWBTC Faucet | https://faucet-cwbtc.ckb.dev | Get testnet cWBTC for CCH testing | | RUSD (Stablecoin) | https://testnet0815.stablepp.xyz/faucet | Claim through a wallet, then transfer Testnet RUSD to the node address | ## Block Explorers | Resource | URL | |----------|-----| | CKB Testnet Explorer | https://explorer.nervos.org/aggron | | Fiber Network Dashboard | https://dashboard.fiber.channel/nodes | ## Network Topology The typical testnet topology for multi-hop payments: [code block] Local nodes (nodeA, nodeB) do not need to expose a public address — they connect through the public relay nodes. ## Channel Capacity When opening a channel, each side must reserve 99 CKB (98 CKB for commitment lock + 1 CKB for shutdown transaction fee). This reserved amount is not available for off-chain payments. Example: If you fund 499 CKB and the public node contributes 250 CKB: - Your available balance: 499 - 99 = 400 CKB - Public node available: 250 - 99 = 151 CKB ## Node Downloads Download the pinned Fiber Node v0.9.0 release. Choose the asset matching your operating system and CPU architecture; each archive includes fnn, fnn-cli, and network config files. ## Getting Help - GitHub Issues — bug reports and feature requests - Fiber Documentation — full documentation --- ## Toolchain Overview Source: https://www.fiber.world/docs/build/toolchain The Fiber ecosystem includes several tools and libraries that serve different roles. This page provides a complete map to help you understand what's available and choose the right tool for your use case. ## Ecosystem Map [text code block] ## Runtime Stacks ### fnn — Rust Native Node fnn is the reference implementation of the Fiber Network Node, written in Rust. It runs as a native binary on Linux, macOS, and Windows. In the Fiber source tree, the fnn binary is built from crates/fiber-bin, which depends on the shared crates/fiber-lib crate. Choose fnn if: - You want to run a production node on a server - You need the best performance and full feature set - You want to be a relay node or liquidity provider on the network ### fiber-js — Browser WASM Node fiber-js is the JavaScript/TypeScript API layer above Fiber WASM. It is bundled for browser runtime environments, starts Web Workers for the Fiber node and IndexedDB storage, and exposes common Fiber node operations through a JS API. It also targets modern mobile browsers that support WebAssembly, Web Workers, and IndexedDB, so browser wallets and dApps can offer Fiber functionality on mobile devices without a native app. The browser stack has one more layer than the native stack: application code calls @nervosnetwork/fiber-js, fiber-js loads crates/fiber-wasm through a worker, and fiber-wasm wraps the same shared fiber-lib node core with wasm-bindgen. fiber-js is close to the Fiber RPC shape for common channel, peer, invoice, payment, graph, and info workflows, but it is not a full replacement for fnn. The native fnn binary remains the more complete choice for long-running nodes, production operations, CCH, watchtower, profiling, and other advanced node scenarios. Choose fiber-js if: - You want to build a browser-based wallet or dApp - You need the same Fiber integration to work in desktop and mobile browsers - You don't want to run a separate server - You are building a web game with micro-payments ### fiber-ffi — Native Mobile / C ABI Node fiber-ffi currently lives in a personal repository. It is an exploratory reference for Fiber native/mobile integration, not an official Fiber SDK, release artifact, or compatibility commitment. fiber-ffi wraps the shared Fiber node core as a C ABI exposed through a dynamic library and fiber_ffi.h. It lets Android apps, iOS apps, C/C++ programs, desktop apps, and other native host runtimes embed a Fiber node in-process while keeping platform-specific work in JNI bridges, Swift bridging headers, or host-language wrappers. The FFI path is different from fiber-js: it targets native app runtimes rather than browser runtimes, and the host app is responsible for dynamic library packaging, platform lifecycle, threading, string ownership, private-key handling, and app sandbox paths. Choose fiber-ffi if: - You are exploring a native Android or iOS app integration - You need a C ABI boundary for a non-JavaScript runtime - You want to prototype language bindings or SDK shape above the Fiber node core ## Access Layer ### HTTP RPC The HTTP JSON-RPC interface is the standard way to interact with a running native fnn node. Any language with an HTTP client can use it. Choose HTTP RPC if: - You have a running fnn node and want to control it programmatically - You are building a backend service that integrates with Fiber - You prefer to use a language other than JavaScript/TypeScript See the API Reference for details. ### fiber-js API The @nervosnetwork/fiber-js API is the browser-facing integration layer. It does not talk to a local HTTP server. Instead, it sends commands to its Fiber Web Worker, and that worker invokes the functions exported by fiber-wasm. ### fiber-ffi C ABI The fiber-ffi C ABI is the native embedding boundary. Host applications call functions declared in fiber_ffi.h, receive status codes, returned data, and event callbacks, and wrap those C-level details in a platform runtime such as an Android JNI bridge, an iOS Swift runtime, or a C/C++ integration layer. ### Community application SDKs Community SDKs build higher-level application workflows on top of Fiber RPC or fiber-js. They are maintained independently from the official Fiber repository, so their supported Fiber versions and maintenance policies can differ from the core runtime. - fiber-pay combines typed RPC clients, a browser WASM and passkey layer, React components, and CLI automation. - fiber-checkout focuses on a reusable React checkout flow for creating invoices and observing payment status. See Community SDK for the current selection policy, status, and compatibility notes. ## Runtime-Specific Operations The native, WASM, and FFI runtimes have different operational surfaces. Native fnn exposes HTTP RPC and CLI-based node-maintenance tooling. fiber-js embeds the WASM node in browser workers, uses IndexedDB-backed storage through the WASM database worker, and presents a JS/TS API to application code. fiber-ffi exposes a C ABI through a dynamic library and leaves platform lifecycle, packaging, and host-language wrappers to the app. These paths share fiber-lib underneath, including the store version check used when each runtime opens its store. ### fnn-cli fnn-cli is the official command-line tool for managing a Fiber node. It supports both one-shot commands and an interactive REPL with tab completion. ### Storage Migration Storage version checking is part of the shared Fiber node startup path. Native fnn, fiber-wasm, and fiber-ffi all call the store opening path from crates/fiber-lib, which initializes the version for a new store and refuses to open an incompatible or older store that needs migration. Starting from v0.9.0, migration is built into the fnn binary and runs automatically on startup when needed (with user confirmation). The standalone fnn-migrate binary, previously built from the migrate directory, was deprecated and removed in v0.9.0. For databases created before v0.8.x (database version older than 20260302100001), you must first upgrade using the v0.8.x fnn-migrate tool before starting the current fnn binary. See the backup guide for details. ## Cryptography & On-Chain Scripts ### fiber-sphinx fiber-sphinx is the cryptography library implementing Sphinx-based onion routing for Fiber's payment privacy. It handles the construction and peeling of onion packets used in multi-hop payments. ### fiber-scripts fiber-scripts contains the CKB on-chain scripts that secure Fiber payment channels: - Funding Lock: Secures the multi-sig output that funds a channel - Commitment Lock: Enforces the revocation mechanism and dispute resolution ## Decision Guide | I want to... | Use | |---|---| | Run a production node | fnn + fnn-cli | | Control a node from my backend | HTTP RPC | | Build a browser wallet | fiber-js | | Add a browser node, passkeys, or payment components | fiber-pay | | Add an invoice checkout to a React app | fiber-checkout | | Embed Fiber in a non-JS native runtime | fiber-ffi | | Build a native Android app | fiber-ffi Android guide | | Build a native iOS app | fiber-ffi iOS guide | | Add payments to my web game | fiber-js or HTTP RPC | | Do cross-chain swaps (BTC ↔ CKB) | fnn with the CCH service configured | | Understand the protocol | Tech Explanation | --- ## JavaScript SDK Source: https://www.fiber.world/docs/build/sdk/js ## TL;DR The Fiber JavaScript SDK (@ckb-ccc/fiber) is a typed, camelCase client for Fiber payment channels. Every channel interaction is two-party: one side opens, the other accepts; one side invoices, the other pays; either side can close. This guide walks through each interaction from both perspectives using "Alice" and "Bob." ## Overview @ckb-ccc/fiber is a high-level TypeScript/JavaScript SDK for building on the Fiber Network — Nervos CKB's payment channel network. It wraps the Fiber node JSON-RPC with a typed, camelCase API and is part of the CCC (Common Chains Connector) stack. Fiber payment channels are inherently two-party: one side opens a channel, the other accepts it; one side creates an invoice, the other pays it; either side can close the channel. This guide walks through every interaction from both perspectives, using "Alice" and "Bob" as the two parties. ## Installation [bash code block] @ckb-ccc/fiber depends on @ckb-ccc/core. Both will be installed automatically. ## Quick Start Create a client pointing at your Fiber node's RPC endpoint and verify the connection: [typescript code block] From here, the same sdk handles everything — see the User Stories below. ## Architecture FiberSDK is the main class. It composes five API domains (Channel, Invoice, Payment, Info, Peer) into a single object. Most methods are available directly on the sdk instance, while a few are accessed through domain sub-objects: [typescript code block] acceptChannel and settleInvoice are on sub-objects because they represent the counterparty's side of a two-party handshake — sdk.channel.acceptChannel() and sdk.invoice.settleInvoice(). ## Two-Party Interaction Model Every channel interaction involves two nodes. Throughout this guide, we label them Alice and Bob for clarity. Each side has its own FiberSDK instance pointing at its own node's RPC. In all examples below, assume these two nodes: - Alice's node — RPC at http://127.0.0.1:8227 - Bob's node — RPC at http://127.0.0.1:8237 [typescript code block] In practice, Alice and Bob run on different machines (or processes). Each points their SDK at their own node's RPC endpoint. The code blocks below use alice and bob as shorthand for each party's SDK instance. For reference, here are the peer identities we assume throughout the examples (your actual keys and addresses will differ): | | Pubkey | P2P Address | | --------- | ---------------------------------------------------------------------- | -------------------------------------- | | Alice | 0x02aa3beb0d770fe835db99bf894fb2d9afaf4df0d5ec1871fad731d4fc6c90faed | /ip4/127.0.0.1/tcp/8228/p2p/QmdW4... | | Bob | 0x03827ccddf3fdf59808fa6baea647d93bd6f6105309d3b20e1fc0d9e40495865cc | /ip4/127.0.0.1/tcp/8238/p2p/QmcF... | ## User Stories ### 1. Check My Node's Health Goal: Verify the SDK is connected and inspect your node's current state. This is a single-party check — it works identically for either Alice or Bob. [typescript code block] ### 2. Establish a Payment Channel Goal: Alice wants to open a channel with Bob so they can send payments to each other. Flow: Alice connects to Bob's node → Alice opens a channel → Bob accepts the incoming channel → both wait for it to reach ChannelReady. [typescript code block] [typescript code block] openChannel returns a temporary channel ID. Once both parties accept and the funding transaction confirms on-chain, the temporary ID is replaced by a permanent channelId and the channel state becomes ChannelReady. If you want to cancel before the channel is ready, either party can call sdk.abandonChannel({ temporaryChannelId, reason: "..." }). ### 3. Receive a Payment Goal: Bob wants to receive a payment from Alice. He creates an invoice, Alice pays it, and Bob settles to finalize. Flow: Bob creates an invoice → Bob shares the invoiceAddress with Alice → Alice pays the invoice → Bob detects the payment → Bob settles with the preimage. [typescript code block] [typescript code block] How settlement works: When Alice pays, the funds flow through the channel but are not yet final — Bob's node holds them pending the preimage. When Bob calls settleInvoice, he reveals the preimage, which unlocks the funds cryptographically. This is the core Lightning-style trustless exchange: Alice knows Bob can only claim the funds if he knows the preimage, and Bob knows Alice can only reclaim after the expiry. ### 4. Send a Payment Goal: Alice receives an invoice address and wants to inspect it before paying, then verify the payment succeeded. This is the payer's perspective of the flow shown above. See Receive a Payment for the full two-party exchange. [typescript code block] The sendPayment result may show "Created" or "Inflight" — the payment routes through the network asynchronously. Use getPayment to confirm it reaches "Success". ### 5. Close the Channel Goal: Alice (or Bob) wants to close the channel and settle the final balance on-chain. Either party can initiate a close. The channel must be in ChannelReady state. [typescript code block] [typescript code block] | Close Type | When to Use | Speed | Counterparty Needed? | | ---------------------------- | ---------------------------- | ------------------------- | -------------------- | | Cooperative (force: false) | Both parties are online | Immediate | Yes | | Force (force: true) | Counterparty is unresponsive | After timelock (~4 hours) | No | Force-closing imposes a timelock before you can spend your funds. Always try a cooperative close first — it's faster and cheaper. --- ## WASM Node Source: https://www.fiber.world/docs/build/sdk/wasm-node Use Run a WASM Node to launch the isolated browser lab, connect to a public node over WSS, and inspect the real node state. This page remains the detailed integration reference. fiber-js is the JavaScript/TypeScript integration library for Fiber Network. It is mainly designed for browser-based web applications, browser wallets, and small games, and can also be used in mobile runtime environments with modern browser capabilities. The native Fiber client (fnn) mainly runs in desktop and server environments such as Linux, Windows, and macOS. fiber-js is built on Fiber WASM and targets web runtime environments. With fiber-js, a web application can start a Fiber node locally in the browser, connect to peers, open channels, create invoices, send payments, and query node state. This model is different from running a Fiber node on a server and remotely controlling it from the frontend. In API coverage, fiber-js provides common node capabilities close to Fiber RPC. fnn, as the native node implementation, is still more complete and better suited for long-running nodes, full operations, and advanced node scenarios. Because it runs in the browser, the lifecycle of a fiber-js node is affected by the page, browser process, and operating system background policies. Scenarios that require a node to stay online reliably for long periods should usually prefer fnn. ## Getting Started ### Setup Install the npm package: [sh code block] Then import from @nervosnetwork/fiber-js: [ts code block] @nervosnetwork/fiber-js is a browser wrapper over fiber-wasm. It starts two Web Workers: one runs the Fiber WASM node, and the other handles IndexedDB storage. It also creates SharedArrayBuffer instances and passes them to the workers so that WASM and the storage layer can exchange data. Therefore, do not create a Fiber instance on a non-isolated page, because new Fiber() creates SharedArrayBuffer immediately. Before using it, make sure your project environment meets these requirements: - Use an ESM build pipeline, such as Vite, Next.js, Webpack 5, or an equivalent tool. - Serve WASM files with the correct application/wasm MIME type in deployment. - Configure CORS, SSL/TLS, and browser isolation appropriately, as described later in this document. The install commands above intentionally use the package's default npm dist-tag. When connecting to public nodes or self-hosted peers, keep fiber-js, fnn, and the peer node release line aligned, since small RPC or protocol differences can otherwise surface during peer, channel, or payment flows. ### Basic Usage The minimum startup flow is: create a Fiber instance, prepare a YAML config, provide the Fiber node key and optional CKB secret key, call start(), and then verify node state with nodeInfo() or listPeers(). The example below is adapted from the upstream fiber-js/README.md and fiber-js/src/index.ts. Fiber, randomSecretKey(), start(), nodeInfo(), and listPeers() are all public exports from the current @nervosnetwork/fiber-js package. This is not a complete demo that runs out of the box on this site. Before running it, publish the YAML config for the target network at /fiber-config/testnet.yml, or change configPath to your own static asset path, and satisfy the browser isolation, CORS, CSP, and WSS peer requirements described later. [ts code block] This example uses a plaintext CKB key. Make sure it is handled securely and never exposed in production code, logs, or client-side bundles. ### Configuration The config argument of start(config, ...) uses a YAML configuration close to fnn. The default config files can be obtained from the config directory in the Fiber source code and selected for the target network: - testnet: config/testnet/config.yml - mainnet: config/mainnet/config.yml Use fnn --help to get detailed config argument information. The fiber config file and CLI arguments mostly come from the same config definitions. For deeper logic or implementation details behind a config option, see the config definitions in the Fiber source code: config.rs aggregates the fiber, rpc, ckb, and other config sections, while concrete fields are defined in fiber/config.rs, rpc/config.rs, and ckb/config.rs. When connecting to CKB testnet or mainnet, use the config for the corresponding network. Production environments must use the mainnet config. Unlike a native node, the browser WASM runtime does not expose TCP or HTTP listeners, so fiber.listeningaddr and rpc.listeningaddr are not used to open externally reachable ports in the browser. Browser nodes typically initiate connections to /ws/ or /wss/ peers, and applications usually control the node through the fiber-js API rather than an exposed HTTP RPC service. ### Key Management The key-related arguments of fiber.start() have different responsibilities: - fiberKeyPair: the Fiber node identity key, 32 bytes long. It determines the node pubkey and is the core of peer identification, channel state, and network identity. The same node should use the same key every time it starts. - ckbSecretKey: optional CKB secret key, 32 bytes long. It lets the node directly sign on-chain transactions such as funding, commitment, and shutdown transactions. This is suitable for native nodes, controlled runtime environments, or browser scenarios that truly need the Fiber node to manage on-chain funds directly. When undefined is passed, WASM generates an internal CKB key. This is not the same as giving the user's wallet to the Fiber node. - randomSecretKey(): generates a 32-byte key using browser-secure randomness. It is suitable for first-time initialization or tests. In production, the generated key must be persisted and should not be regenerated on every startup. Browser integrations usually need the page or application to own fiberKeyPair and persist it locally. Every later startup of the same Fiber node must pass the same key. If browser data is cleared, the profile is reset, or migration fails, the local key may be lost. The application needs fallback logic for recovery, node rebuilds, or channel state migration. Whether to pass ckbSecretKey depends on the product model. Browser applications are usually better off using an external wallet such as JoyID to custody funds, instead of giving the user's wallet private key to the Fiber WASM node. The application can inject funds through external funding when opening a channel and return funds to the external wallet after closing the channel. If there is a special need, the application can manage the CKB key itself, but it must also handle local encryption, backup, recovery, and fund security. ## External Funding External funding is suitable for browser wallets, hardware wallets, or any scenario where the CKB secret key should not be given to the Fiber WASM node. The flow is: 1. Call connectPeer() first. 2. Call openChannelWithExternalFunding() with the peer pubkey, fundingamount, shutdownscript, fundinglockscript, and the fundinglockscriptcelldeps required by a custom lock. 3. The Fiber node and peer negotiate channel parameters and produce the final unsignedfundingtx. 4. The wallet signs this transaction. 5. Submit the signed transaction with submitSignedFundingTx(). [ts code block] The key restriction is that the signer may only fill witnesses or signature fields. It must not change inputs, outputs, outputsdata, celldeps, capacity, lock/type scripts, or output order. Once the wallet reorders the transaction structure, adds inputs, or changes the change output, the channel funding that the peer already negotiated becomes invalid. If the browser page cannot complete signing directly, for example because the target wallet or signing page does not provide a usable CORS interface, the signing process can be split into a redirect flow. First call openChannelWithExternalFunding() on the current page and save the returned channelid and unsignedfundingtx. Then redirect to the wallet page to finish signing. After signing, return to the original page and call submitSignedFundingTx() with the same channelid and the signed transaction. As long as the signing page does not change the transaction structure and returns before the external funding timeout, this cross-page signing and resume-submit flow is valid. The default external funding timeout is 5 minutes and is controlled by fiber.externalfundingtimeoutseconds. For mobile wallet redirects, slow user confirmation, or longer signing flows, increase this value and show the remaining time clearly in the UI. ## API Model Methods on the Fiber class are mostly camelCase wrappers around Fiber RPC: | fiber-js method | RPC command | |---|---| | nodeInfo() | nodeinfo | | connectPeer(params) | connectpeer | | listPeers() | listpeers | | openChannel(params) | openchannel | | openChannelWithExternalFunding(params) | openchannelwithexternalfunding | | submitSignedFundingTx(params) | submitsignedfundingtx | | acceptChannel(params) | acceptchannel | | abandonChannel(params) | abandonchannel | | listChannels(params) | listchannels | | shutdownChannel(params) | shutdownchannel | | updateChannel(params) | updatechannel | | newInvoice(params) | newinvoice | | parseInvoice(params) | parseinvoice | | getInvoice(params) | getinvoice | | cancelInvoice(params) | cancelinvoice | | sendPayment(params) | sendpayment | | getPayment(params) | getpayment | | buildRouter(params) | buildrouter | | sendPaymentWithRouter(params) | sendpaymentwithrouter | | disconnectPeer(params) | disconnectpeer | | graphNodes(params) | graphnodes | | graphChannels(params) | graphchannels | For connectPeer({ pubkey }), Fiber can choose an address from graph or peer-store data. Browser/WASM builds prefer ws/wss addresses by default; native builds prefer tcp. You can pass addrtype: "wss" or an explicit WebSocket multiaddr when the peer advertises multiple transports. You can also call lower-level commands directly: [ts code block] Internally, command calls are serialized through a mutex to avoid multiple WASM async command calls overwriting each other's input and output buffers. Therefore, do not assume that issuing many concurrent RPC calls on the same Fiber instance will improve throughput. For UI code, handle queues, timeouts, and loading states at the application layer. ## Storage and Lifecycle fiber-js stores state in IndexedDB, including peer store, channel state, network graph, and payment/invoice state. databasePrefix isolates data for different node instances: [ts code block] Design the prefix around these dimensions: - network: testnet, mainnet, or a custom chain id. - wallet/account: the current wallet account or derivation path. - app namespace: prevents conflicts between multiple applications under the same origin. Lifecycle notes: - After a page refresh, if fiberKeyPair and databasePrefix stay the same, the node reuses state from IndexedDB. - stop() only terminates workers. It does not delete IndexedDB data. - When switching accounts, switching networks, or logging out, call await fiber.stop() before starting a new instance. - Do not open the same databasePrefix from two pages or two instances at the same time. This can cause state races. - When the browser enters the background, the mobile operating system enables power saving, or the page is closed, the Fiber node may be paused. Use an always-online fnn for high-availability receiving, route forwarding, or watchtower capabilities. - The default input/output buffer size is 50 MiB. On memory-constrained mobile devices, you can reduce it with new Fiber(inputBufferSize, outputBufferSize), but setting it too low can make large responses or complex transaction transfers fail. ## Browser Security Requirements - SSL/TLS: except for local development on localhost, the page must be loaded over HTTPS. When an HTTPS page connects to a Fiber peer, it must also use /wss/, and the peer certificate must be trusted by the browser. /ws/ should only be used in local development. - CORS and browser isolation: CKB RPC, wallet signing interfaces, worker scripts, and cross-origin resources referenced by the page all need CORS or isolation response headers according to browser requirements. CORS only determines whether a request can be sent and whether the response can be read. Whether the page can use SharedArrayBuffer also depends on whether it reaches the crossOriginIsolated state. Use separate entry pages to guarantee page isolation. The fiber-wallet example implements this by trying a DIP entry from the main entry first and falling back to a COOP/COEP entry if the conditions are not met. Browser handling can be split by capability: - Chromium-based browsers such as Chrome, Edge, and Android Chrome can try Document Isolation Policy (DIP) first. DIP can put the page into crossOriginIsolated while reducing the cost of adapting third-party resources for COOP/COEP. CKB RPC and wallet signing interfaces must still explicitly allow the current page origin. DIP does not bypass CORS checks for fetch/XHR. - Browsers that do not support DIP can fall back to COOP/COEP, such as Firefox or some desktop browser environments. In this mode, all cross-origin scripts, fonts, images, and worker resources loaded by the isolated page must satisfy CORS or Cross-Origin-Resource-Policy requirements, otherwise the page will not enter the crossOriginIsolated state. - Safari, iOS WebView, or other environments that cannot reliably satisfy isolation and CORS requirements should not call wallet signing interfaces directly inside the Fiber page. Use the redirect signing flow described in External Funding: the current page saves channelid and unsignedfundingtx, redirects to the wallet page for signing, and then returns to the original page to call submitSignedFundingTx(). Recommended DIP entry headers for Chromium-based browsers: [txt code block] Recommended COOP/COEP fallback entry headers: [txt code block] The HTML entry that actually loads the application bundle must set these response headers. The local dev server, preview server, and production deployment should remain consistent. Whether using DIP or COOP/COEP, check crossOriginIsolated before initializing Fiber; if it is not satisfied, do not create a Fiber instance. Browser nodes cannot accept inbound TCP connections like native fnn nodes. Under the WASM target, Fiber has no real listeningaddr it can listen on. The listeningaddr field in YAML mainly exists to reuse the native config structure. Browser integrations usually should not announce a local listening address: [yaml code block] Bootnode or peer addresses that only contain /tcp/ are better suited for native nodes. Browser nodes usually need the remote peer to provide a /ws/ or /wss/ address. Before creating a channel, call connectPeer() with an explicit WebSocket peer address, or call connectPeer({ pubkey, addr_type: "wss" }) after the peer's WSS address has propagated through gossip. Do not rely on the browser node being dialed from the public network. Node.js environments are only suitable for testing or special integration scenarios. Because this wrapper depends on Workers, IndexedDB-style storage, and the browser security model, production server-side nodes should still prefer fnn. ## Where to Look Next - API shape: fiber-js/src/index.ts and fiber-js/src/types are the direct sources for fiber-js methods and parameter types. - Runtime details: crates/fiber-wasm/src/lib.rs shows how WASM starts the Fiber actor, loads config, and initializes the store. - Browser WSS examples: fiber-js/README.md shows WSS bootnode config for browser use, and docs/network-nodes.md lists public node pubkeys. - External funding: External Funding explains the flow semantics. fiber-js/README.md records the current constraints of openChannelWithExternalFunding() and submitSignedFundingTx(). - RPC details: API Reference can be used to verify field names, state names, and error meanings. --- ## Overview Source: https://www.fiber.world/docs/build/community-sdk Community SDKs add higher-level application patterns on top of Fiber. They can shorten common integration work such as running a browser node, adding passkey authentication, or rendering a checkout flow. The projects in this section are maintained by their respective community authors. They are not official Fiber releases or support commitments. Review the upstream license, security model, release notes, and Fiber Node compatibility before using one in production. ## Available projects | Project | Best for | Current status | | --- | --- | --- | | fiber-pay | Typed RPC clients, browser WASM nodes, passkeys, React payment UI, and operator automation | Actively maintained; v0.3.1 targets stable Fiber v0.9.0 | | fiber-checkout | Adding an invoice-based checkout component or custom checkout hooks to a React app | Spark grant completed; stable maintenance | ## How projects are selected This section focuses on community projects that have: - a usable, published implementation rather than only a proposal; - public source code and documentation; - a clear Fiber-specific developer use case; - evidence of testing, releases, or ongoing maintenance; and - enough compatibility information for developers to evaluate adoption risk. Inclusion is a discovery aid, not an endorsement. Projects can be updated or removed when their maintenance or compatibility status changes. Building a reusable Fiber SDK? Share the repository, package, supported Fiber versions, and a reproducible example on the Nervos Talk forum so the community can review it. ## Which SDK should I choose? - Use the JavaScript SDK when you want the CCC-based, typed Fiber RPC interface documented by this site. - Use fiber-pay when you want a broader application toolchain, especially a browser-local Fiber node, passkey credentials, React components, or CLI automation. - Use fiber-checkout when your main requirement is an embeddable React checkout that creates invoices and observes their payment status. - Use the Fiber RPC reference directly when you need complete control or do not want another SDK dependency. --- ## fiber-pay Source: https://www.fiber.world/docs/build/community-sdk/fiber-pay fiber-pay is a community-maintained TypeScript toolchain for building and operating Fiber applications. It covers more than checkout: the project provides typed RPC clients, a browser-local Fiber node, passkey credentials, React payment and node-management components, local node lifecycle helpers, and an automation-friendly CLI. fiber-pay is maintained independently of the official Fiber repository. Version 0.3.1 declares compatibility with the stable Fiber WASM package @nervosnetwork/fiber-js ~0.9.0. Pin compatible package and node versions, and rerun integration tests before upgrading either side. ## Where it fits Use fiber-pay when an application needs one or more of these capabilities: - a typed client for an existing Fiber Node; - a Fiber WASM node that runs inside the browser; - passkey- or password-backed browser credentials; - React UI for connecting a node, opening channels, creating invoices, and sending payments; - repeatable node and payment operations from a terminal or automation agent; - server-side Biscuit authorization or payment-gated L402 endpoints. For a merchant page that only needs to create an invoice and wait for payment, fiber-checkout is a smaller, checkout-focused alternative. ## Choose an integration surface | Package or entrypoint | Primary role | Runtime | | --- | --- | --- | | @fiber-pay/sdk | Typed Fiber RPC, invoice, routing, policy, crypto, and UDT helpers | Browser or Node.js | | @fiber-pay/sdk/browser | Browser WASM node, credentials, CCC external funding, and browser-safe RPC | Browser | | @fiber-pay/sdk/node | Node-only exports, including L402 middleware | Node.js | | @fiber-pay/react | Hooks and UI for browser-node and payment flows | React 18 or 19 | | @fiber-pay/node | Download and manage a local fnn process | Node.js | | @fiber-pay/runtime | Polling, monitoring, retry, and background jobs | Node.js | | @fiber-pay/cli | Human- and machine-readable node, channel, invoice, and payment commands | Node.js 20+ | The separate @fiber-pay/agent package is experimental. Its maintainer does not recommend it for production use. ## Connect to an existing Fiber Node Install the core SDK: [bash code block] Create a typed RPC client that points to a node you control: [typescript code block] The client exposes typed methods for the main Fiber RPC groups: | Area | Representative operations | | --- | --- | | Peers | connect, disconnect, and list peers | | Channels | open, externally fund, accept, list, shut down, abandon, and update | | Payments | send, inspect, and list payments; build and use routes | | Invoices | create, parse, inspect, cancel, and settle hold invoices | | Graph | list graph nodes and channels | | Node | readiness checks and node information | Use client.call(method, params) only when an RPC method has not yet received a typed wrapper. Treat raw calls as a compatibility boundary and validate their responses in application code. A Biscuit token is sent as an authorization bearer credential. Do not embed a privileged token in a public browser bundle. Issue narrowly scoped tokens or proxy the required operations through your own backend. ## Run Fiber inside the browser The browser entrypoint wraps the Fiber WASM runtime and provides credential providers for passkeys, passwords, and raw keys. A minimal passkey-backed node looks like this: [typescript code block] Persist a stable walletId or credential identifier for the same user and network. Changing it creates a different local identity instead of reopening the previous node. ### Required browser headers The multithreaded WASM runtime uses SharedArrayBuffer. Serve every route that loads it with these response headers in development and production: [text code block] These policies also affect third-party scripts, iframes, images, and workers. Test analytics, wallet connectors, and other cross-origin resources after enabling them. The upstream v0.3.1 documentation measures the Fiber WASM payload at roughly 45.5 MB raw and 13.4 MB gzip. Lazy-load browser-node functionality behind a user action so unrelated pages do not pay its download and startup cost. ## Add React UI Install the React layer and Fiber WASM peer dependency: [bash code block] Choose the component level that matches the product: | API | Good starting point for | | --- | --- | | FiberPayQuickCard | A compact create-invoice and pay-invoice experience | | ConnectButton | App-owned UI that only needs browser-node lifecycle and credentials | | FiberNodeButton | A node workbench with peers, channels, invoices, payments, and diagnostics | | NodeInfoPanel | Read-only node metadata, live statistics, and deposit information | | useFiberNode, useFiberPayment | Fully custom UI and state handling | The smallest integration is one component: [tsx code block] In a real application, create one shared node session and pass it to every component. This prevents two widgets from starting separate browser nodes: [tsx code block] FiberNodeButton is the broader option when users also need peer connection, channel operations, graph diagnostics, CKB/UDT selection, or custom tabs. For a narrow payment surface, prefer the quick card or the hooks rather than exposing the full node workbench. ## Fund a channel with an external CKB wallet A browser Fiber node does not have to fund a channel from its own key. The browser SDK includes a resolver that lets a CCC signer build and sign the CKB funding transaction: [typescript code block] Pass the resolver to the node workbench: [tsx code block] The flow asks Fiber to prepare an externally funded channel, lets CCC resolve and sign the CKB transaction, and submits the signed transaction back to Fiber. Keep the signer network, Fiber network, address, and known-script configuration aligned; a mismatch should stop the flow before signing. ## Automate operator workflows with the CLI The CLI is useful for local development, CI, operational scripts, and agents that need structured output: [bash code block] Commands with --json return stable envelopes, while watch commands can emit newline-delimited JSON. Prefer these modes over scraping human-readable output. The @fiber-pay/node and @fiber-pay/runtime packages expose lower-level APIs when the same lifecycle and monitoring logic must live inside a Node.js service. ## Advanced server capabilities ### Scoped Biscuit authorization The SDK can render policy facts for the exact methods a service needs: [typescript code block] Use the generated facts when issuing a restricted Biscuit token. Scope by both method and deployment context; a read-only dashboard and a payment worker should not share the same authority. ### L402 payment gates The Node.js entrypoint exports createL402Middleware and MacaroonService for building HTTP endpoints that return an L402 payment challenge and verify the paid credential. These exports are intentionally absent from the root and browser entrypoints: [typescript code block] Use this only on a trusted server. Macaroon roots, invoice creation authority, and Fiber RPC credentials must never reach client code. ### UDT payments The SDK and React node workbench can resolve UDTs configured by the connected Fiber node and validate invoice type scripts before payment. UDT amounts are raw integer base units, not decimal display amounts. Always verify the type script, network deployment, symbol, and decimals against the issuer's official data; node-provided names alone are not a trust anchor. ## Production checklist - Pin fiber-pay, fiber-js, and Fiber Node versions and test them together. - Keep administrator RPC and broadly scoped Biscuit credentials off the client. - Lazy-load WASM and verify COOP/COEP behavior across every integrated resource. - Reuse one browser-node session and one persistent identity per user/network. - Handle node startup, channel readiness, invoice expiry, payment failure, and retry states explicitly. - Validate CKB/UDT network and type scripts before signing or sending payment. - Treat external funding as a transaction-signing flow and show users the final asset and amount before wallet approval. - Use structured CLI output and avoid making automation depend on display text. - Keep the experimental agent package outside production paths unless it has been independently reviewed for the deployment. ## Examples and resources - Documentation and examples - Source repository - Minimal React connection example - React node workbench example - Browser SDK playground - Node.js SDK recipes - Nervos Talk discussion - npm: @fiber-pay/sdk - npm: @fiber-pay/react --- ## fiber-checkout Source: https://www.fiber.world/docs/build/community-sdk/fiber-checkout fiber-checkout is a community-maintained React library for receiving invoice-based Fiber payments. It can generate an invoice, render a QR code, poll for settlement, detect expiry, and expose success or failure through a ready-made component or lower-level hooks. fiber-checkout is a community project delivered through the Spark Program and is now in stable maintenance. Version 0.1.2 declares an optional peer dependency on @nervosnetwork/fiber-js v0.7.x, while current Fiber releases have moved beyond that API line. Test the exact package and node versions you deploy, especially when using the browser WASM backend. ## Where it fits fiber-checkout is intentionally narrower than a wallet SDK. It is a good fit for a merchant or service that needs to receive a payment and show a compact checkout state. It does not provide peer discovery, channel management, routing controls, passkey wallets, or a general node dashboard. If the product needs those broader capabilities, use fiber-pay or the JavaScript SDK. ## Understand the payment lifecycle The default component coordinates this sequence: [text code block] After a successful invoice poll, the hook also attempts getpayment to obtain a routing fee. A receiver commonly has no outgoing payment session for that hash, so this lookup is optional and failure leaves feePaid as null. onSuccess is a client-side UI event, not a trusted receipt. Your backend must bind the order to its payment hash, expected amount, asset, and expiry, then verify settlement against a trusted Fiber Node before delivering goods or changing durable order state. ## Install [bash code block] The package supports React 18 and 19 and ships ESM and CommonJS builds. Choose a backend based on the application architecture: | Mode | Use it when | Main trade-off | | --- | --- | --- | | HTTPS RPC proxy | A web checkout talks to a server-managed Fiber Node | Recommended for production; requires a small backend route | | Direct RPC | Local development against a trusted node | Exposes the node address and must never be enabled for public production clients | | FiberWasmBackend | The app already owns and runs a compatible browser Fiber instance | No remote RPC, but adds a large WASM runtime and strict version coupling | ## Add the checkout component The amount is a 0x-prefixed integer in the asset's smallest unit. Use the provided conversion helper rather than writing hex literals by hand: [tsx code block] The component owns invoice creation, polling, QR rendering, copy, retry, and terminal-state UI. Its public props in v0.1.2 are: | Prop | Required | Meaning | | --- | --- | --- | | amount | Yes | Hex integer in the selected asset's smallest unit | | asset | Yes | Built-in or custom asset identifier | | nodeUrl | Yes | Same-origin proxy path, HTTPS proxy URL, or development RPC URL | | description | No | Text embedded in the Fiber invoice | | expirySeconds | No | Invoice lifetime; defaults to 3,600 seconds | | qrSize | No | QR size in pixels; defaults to 240 | | onSuccess | No | Called with the payment hash after Paid is observed | | onExpired | No | Called when node or client-side expiry is observed | | onError | No | Receives a typed FiberError | | customAssets | No | Additional asset definitions; see the v0.1.2 limitation below | | dangerouslyAllowDirectRpc | No | Permits a bare IP or localhost RPC URL for trusted development | ## Put a narrow RPC proxy in front of the node For production, point nodeUrl at a same-origin server route. The proxy keeps the Fiber Node location and credentials private and makes the public RPC surface explicit: [text code block] Here is a compact Next.js App Router example: [typescript code block] Set FIBERNODEURL only in the server environment, for example http://127.0.0.1:8227. Do not expose it through a NEXTPUBLIC variable. The component needs newinvoice and getinvoice. The hook attempts getpayment after success only to populate feePaid; because that failure is non-fatal, keep it blocked unless your custom UI really displays that value. If you allow it, validate the request shape and restrict access to payment hashes belonging to the authenticated merchant. The method allowlist is only the beginning. A production proxy should also: - authenticate the merchant or checkout session; - validate method-specific parameters, amount, asset, description, and expiry; - rate-limit invoice creation and polling independently; - cap body size and upstream response time; - associate each created payment hash with the server-side order; and - avoid returning sensitive upstream error details to untrusted clients. dangerouslyAllowDirectRpc bypasses the package's block on bare IP and localhost URLs. It does not make the endpoint safe, add authentication, or limit methods. Never enable it in a public production application. A local-only setup can opt in explicitly: [tsx code block] ## Build a custom checkout with hooks Use useFiberInvoice and useFiberPayment when the product owns its visual design or needs custom polling behavior: [tsx code block] useFiberPayment stops polling after success, failed, or expired. Passing expiresAt also lets it expire the UI locally without waiting for the next node status transition. ## Handle failures by category Every library error is a FiberError with a stable code: | Code | Typical meaning | Suggested response | | --- | --- | --- | | RPCERROR | Fiber Node returned a JSON-RPC error | Log rpcCode and method; show a safe retry or support message | | DIRECTRPCBLOCKED | A bare IP or localhost URL was used without explicit opt-in | Add a production proxy, or enable only for trusted local development | | NETWORKERROR | Proxy/node could not be reached | Retry with backoff and check service health | | INVALIDRESPONSE | Response was malformed or incomplete | Stop fulfillment and investigate proxy/node compatibility | | REQUESTTIMEOUT | The request exceeded the default 30-second limit | Retry cautiously; invoice creation may have reached the node | Invoice creation is not automatically idempotent. After a timeout, do not assume that no invoice was created; reconcile the order before blindly regenerating multiple invoices. ## Assets and amount safety Version 0.1.2 includes these registry entries: | Asset | Status in the package | | --- | --- | | CKB | Built in, 8 decimal places | | RUSD | Built-in testnet type script; verify it for the network you deploy | | SEAL | Declared but marked unsupported because its type script is unset | For CKB, ckbToShannonHex(1) returns 0x5f5e100, or 100,000,000 shannons. For UDTs, calculate the integer using the token's verified decimal count and pass it as hex. Never derive trust from a token symbol alone. The v0.1.2 public API exposes customAssets, but its invoice-generation path does not pass that registry into the asset lookup. Treat custom-token support as unavailable in this release unless you have reviewed and patched the upstream behavior. Even after that is fixed, verify the UDT type script and ensure the connected Fiber Node has the asset in its whitelist. ## Reuse an existing browser Fiber node The hooks accept a FiberBackend. If the application already starts a compatible Fiber WASM instance, wrap it instead of calling an HTTP proxy: [typescript code block] Pass the same backend to both hooks: [tsx code block] nodeUrl remains required by the v0.1.2 hook types but is ignored when a backend is provided. The high-level FiberCheckout component does not expose a backend prop, so WASM mode requires composing the hooks yourself. Because this mode relies on the optional fiber-js v0.7.x peer API, check browser isolation headers, bundle size, key persistence, and node version compatibility separately. If another library already owns a Fiber browser node, prefer adapting that same instance rather than starting a second node. ## Production checklist - Put a method- and parameter-restricted proxy in front of a server Fiber Node. - Store the expected amount, asset, payment hash, and expiry with the order. - Verify Paid server-side before fulfillment; never trust UI state alone. - Use integer base units and verify every UDT type script and network deployment. - Make invoice regeneration and order reconciliation explicit after timeouts. - Stop polling terminal orders and rate-limit active polling sessions. - Test React, package, fiber-js, and Fiber Node versions as one release unit. - Keep dangerouslyAllowDirectRpc and raw node URLs out of production bundles. ## Examples and resources - Source and complete API reference - Next.js proxy guide - Next.js App Router and Pages Router examples - npm package - Spark completion report - Maintenance updates --- ## Fiber FFI User Guide Source: https://www.fiber.world/docs/build/ffi/ffi-user-guide ## Background 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: [text code block] ## Build Regular local build: [sh code block] The repository also provides mobile build entry points: [sh code block] Integrators need to distribute both the dynamic library and the header file: [text code block] The header file is available at include/fiberffi.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 fiberstop 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 fiberstop 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: fiberstart 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 fiberstop. 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: - fiberstop 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 fiberstart 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 [c code block] | Field | Description | | --- | --- | | configpath | Required. Path to the Fiber configuration file. | | databaseprefix | Optional. Node data root directory. If omitted, the directory containing configpath is used. | | loglevel | Optional. For example, info or debug. Defaults to info. | | eventcallback | Optional. Fiber event callback. | | eventcallbackuserdata | Optional. Passed back to eventcallback unchanged. | Note that configpath 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 fiberstart. configpath 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 databaseprefix 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.basedir to /fiber and ckb.basedir to /ckb. If it is not passed, the directory containing configpath 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 databaseprefix, or explicitly clean up/migrate the corresponding data after shutdown. ### Error Handling The main APIs return FiberFfiStatus: [c code block] After a failure, read the error details: [c code block] 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 fiberstringfree | Release after the caller copies or parses it. | | Return value of fiberversion() | fiber-ffi static storage | Do not release | Valid for the process lifetime. | | eventjson in an event callback | Temporarily created by fiber-ffi | Do not release | Valid only during this callback invocation. | | Buffer written by fiberlasterrormessage | 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 FIBERFFISTATUSNULLPOINTER. During the call, FFI copies any information it needs to keep into Rust-owned objects. Therefore, Swift withCString, JNI GetStringUTFChars, and C++ std::string::cstr() 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: [c code block] These return values must first be copied into the host language's own string object, then released with fiberstringfree: [c code block] 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 fiberstringfree exported from the same dynamic library. Do not continue reading the original pointer after fiberstringfree. fiberstringfree(NULL) is safe, but the same non-null pointer may only be released once. ### Event Callback Callback type: [c code block] eventjson is event JSON, for example: [json code block] Callback rules: - eventjson 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: [c code block] Use the initialization macro before calling: [c code block] Rules: - structsize 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: [c code block] Full value: [text code block] For amounts smaller than uint64t, only low needs to be set: [c code block] 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 fundingudttypescriptjson and udttypescriptjson are passed, amount / fundingamount 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: - fundingudttypescriptjson - shutdownscriptjson - fundinglockscriptjson - closescriptjson - trampolinehopsjson - customrecordsjson - hophintsjson - routerjson Rules: - NULL means unset. - Non-null values must be valid JSON. - JSON contents must match the corresponding Fiber RPC parameter structure. - Invalid JSON returns FIBERFFISTATUSINVALIDARGUMENT. ## Language Binding Recommendations It is recommended not to expose the C ABI directly to the business layer. Wrap it in a host language runtime: [text code block] 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 fiberlasterrormessage. - Copying output strings and calling fiberstringfree. - 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. - fiberstop 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 fiberffi.h published with the dynamic library. - Use FIBER*OPTIONSINIT 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 fiberstart / fiberstop to manage FiberHandle. 2. Release all FFI-returned strings with fiberstringfree. 3. Read error details with fiberlasterror_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. --- ## Android Native Integration Source: https://www.fiber.world/docs/build/ffi/android-native-integration # Fiber: Android Native Integration Guide fiber-ffi currently lives in a personal repository. This page uses it as an exploratory reference for Android native integration, not as an official Fiber Android SDK or support commitment. This document uses fiber-ffi as a reference example to explain how the existing Rust Fiber node can be integrated into an Android app through JNI and the native C ABI. The repository provides a runnable demo: demos/android The demo covers starting and stopping a node, reading NodeInfo, receiving native events, connecting peers, creating and sending invoices, and listing, creating, and closing channels. ## Quick Start First build the Android native library from the fiber-ffi repository root: [sh code block] The current Makefile builds the following by default: [text code block] After the build completes, only two files need to be synced: [text code block] Then build the demo: [sh code block] You can also open demos/android directly in Android Studio and run app. When building the demo from the command line, run cd demos/android && ./gradlew clean assembleDebug; on Windows, use gradlew.bat clean assembleDebug. ## Integration Steps ### Prepare Files An Android project needs the following files. The header file is available at include/fiberffi.h: [text code block] fiberconfig.yml must include both fiber and ckb services. The demo uses a testnet configuration, which can be referenced directly at demos/android/app/src/main/assets/fiberconfig.yml. chain affects the invoice currency. The demo uses a testnet configuration, so when creating an invoice, the currency should be the testnet value Fibt (the FFI enum is FIBERINVOICECURRENCYFIBT). If the configuration is switched to mainnet or devnet, the currency must also be changed to Fibb or Fibd; otherwise newInvoice will be rejected by Fiber RPC. For the demo, fixing one network and its corresponding currency is enough. A production app should choose dynamically based on the current network. ### JNI Bridge fiberbridge.cpp is the Android adapter layer: Java/Kotlin calls FiberRuntime, and the bridge calls the C ABI in fiberffi.h. The app side usually only uses FiberRuntime; it should not directly handle FiberHandle , FiberOptions, or char **out_json. A bridge is needed because fiber-ffi exports a C ABI, while Android business code is better expressed with ordinary Java types. The bridge converts String, boolean, amounts, and other parameters into the format required by C, holds a single FiberHandle *, and converts returned JSON or error text into Java strings. When adding new capabilities, prefer adding methods to FiberRuntime and reusing the conversion and error handling patterns in fiber_bridge.cpp. Pay special attention to memory boundaries: - char * values returned by FFI are allocated by libfiberffi.so; the bridge must copy them into Java String values and then call fiberstringfree. - Java string pointers obtained with GetStringUTFChars must be released with ReleaseStringUTFChars before the current JNI call ends. - eventjson in event callbacks is only valid during the callback. Copy it immediately before entering Java. - fiberstop consumes the handle. After Stop, the old handle must not be passed back to FFI. CMake must let fiberbridge find the header file and link fiberffi. The demo's app/src/main/cpp/CMakeLists.txt already handles this. ### Load Native Libraries The Java/Kotlin layer must load fiberffi first, then fiberbridge: [java code block] ### Prepare Data Directories Before startup, copy the configuration from assets to a normal file path and prepare the data directory: [text code block] The CKB private key is stored at: [text code block] The demo writes this file through SetCKBKey: the Java layer normalizes the user-entered 32-byte hex private key and writes it to ckb/key, and the JNI layer sets FIBERSECRETKEYPASSWORD before startup. Distinguish the "plaintext file at import time" from the "persistent file after Fiber starts." Fiber's default logic reads FIBERSECRETKEYPASSWORD when loading the CKB private key. If it finds that ckb/key is still parseable plaintext hex, it encrypts it with this password and overwrites the file in place. Later it decrypts the file with the same password. Therefore, after the first SetCKBKey and before the first successful Start, ckb/key may briefly remain plaintext. After a successful start, the file should have been migrated to encrypted contents. If writing ckb/key manually, the contents should be a 32-byte hex string without the 0x prefix. The demo automatically removes a 0x prefix from user input. The plaintext migration logic in the Fiber source rejects key files with a 0x prefix. A production app should use its own wallet or key management process, and must not use the fixed FIBERSECRETKEYPASSWORD from the demo. This password and the encrypted ckb/key are both required to recover the ability to sign funds. If either one is lost, the CKB private key cannot be recovered from the encrypted file. ### Start and Stop Start the node by calling: [java code block] After startup succeeds, you can continue with: [java code block] Stop the node by calling: [java code block] Do not start multiple nodes in the same process. For repeated Start calls, you can return already running directly. After Stop, clear the native handle. The old handle must not be used again. ### Calling Features The current demo wraps the following Java methods: | Method | Purpose | | --- | --- | | start(context) | Start the node | | stop() | Stop the node | | nodeInfo() | Read node information | | listPeers() | List peers | | connectPeer(...) | Connect a peer | | listChannels() | List channels | | createChannel(...) | Create a channel | | shutdownChannel(...) | Close a channel | | newInvoice(...) | Create an invoice | | sendPayment(...) | Pay an invoice | Pay attention to amount units: - The demo's createChannel(...) accepts a decimal CKB string for display, such as 1.5. The Java/JNI bridge converts it to shannons before filling the FFI FiberU128. - The demo's newInvoice(...) accepts an integer shannons string and does not accept decimal CKB. - Amount fields actually passed into FFI always use integer smallest units: CKB uses shannons, where 1 CKB = 100000000 shannons; UDT uses the integer smallest unit of that UDT. - If UDT channels or UDT invoices are added later, pass UDT parameters such as fundingudttypescriptjson / udttypescript_json, and pass amount as the UDT's raw integer amount. Do not reuse the CKB amount * 100000000 conversion. More APIs can be extended following the JNI Bridge template above. ### Handling Events Pass the native event callback during startup. The Java layer listens like this: [java code block] Event contents are JSON strings. Business code should parse the JSON first, then dispatch based on the kind field. Common kind values include: [text code block] The list above is only a set of common event examples, not a complete enum. When consuming events, use the kind field in the callback JSON as the source of truth. If an event kind is not currently relevant or is added in the future, it can be logged and ignored. Do not update UI directly from the native callback thread. Switch to the main thread after receiving events. ### Lifecycle Recommended handling: - Do not execute potentially blocking native calls directly on the UI thread. - Start, Stop, and calls on the same handle must be serialized. - Execute Stop when the user logs out, switches accounts, or explicitly closes the node. - When switching network, account, or data directory, call Stop first, then restart the app process. - Use different databaseprefix values for different users, accounts, and networks. - The Android demo includes a minimal FiberNodeService foreground service to demonstrate letting a service help keep the process after the node starts. Background execution strategy, notification presentation, power consumption, and restrictions across system versions must be designed and verified by production app developers. The current document mentions the foreground service only as a demo reminder. It does not treat it as a complete persistent background node solution. ### Build - The current demo packages only arm64-v8a, and app/build.gradle.kts also configures only this ABI. To support more ABIs, build the corresponding targets separately and put each .so into the corresponding directory. - Android builds currently enable only the sqlite feature. If you want to enable watchtower, change the feature to sqlite,watchtower and re-verify size, linking, and runtime behavior. - Some Android 15+ devices use a 16 KB page size. The current Rust .so and JNI bridge both add -Wl,-z,max-page-size=16384; do not remove it. ## Demo Operations 1. SetCKBKey: enter the CKB private key. 2. Start: start the node. 3. NodeInfo: read the node address and pubkey. 4. Peers: view or connect peers. 5. Invoice: create an invoice or pay an invoice. 6. Channels: list, create, or close channels. 7. Stop: stop the node. ## Q&A ### System.loadLibrary("fiberffi") fails Check whether libfiberffi.so is under app/src/main/jniLibs//, whether the device ABI is included in abiFilters, whether the Rust target matches the Android ABI, and whether the .so has been relinked for 16 KB page size. ### Loading fiberbridge fails Load fiberffi first, then fiberbridge. Also check whether IMPORTEDLOCATION in CMake points to the .so for ${ANDROIDABI}, and whether fiberbridge correctly links fiberffi and the Android log library. ### Startup fails and says the configuration file is unreadable Do not pass the assets path directly to FFI. First copy fiberconfig.yml from assets to filesDir, then pass the real file path. ### Startup fails and says the CKB private key is not set The demo requires running SetCKBKey first. The key is a 32-byte hex string, with or without a 0x prefix. The demo normalizes it and writes it to filesDir/fiber-data/ckb/key. During the first successful Start, Fiber uses FIBERSECRETKEYPASSWORD to migrate this plaintext key into an encrypted file. Later starts must continue using the same password to decrypt it. --- ## iOS Native Integration Source: https://www.fiber.world/docs/build/ffi/ios-native-integration # Fiber: iOS Native Integration Guide fiber-ffi currently lives in a personal repository. This page uses it as an exploratory reference for iOS native integration, not as an official Fiber iOS SDK or support commitment. This document uses fiber-ffi as a reference example to explain how the existing Rust Fiber node can be integrated into an iOS app through Swift and an Objective-C bridging header. The repository provides a runnable demo: demos/ios The demo covers starting and stopping a node, reading NodeInfo, receiving native events, connecting peers, creating and sending invoices, and listing, creating, and closing channels. iOS integration differs from Android integration. Android needs a JNI bridge, while iOS can use an Objective-C bridging header to let Swift directly access the C ABI exposed by fiber_ffi.h. What iOS really needs is a Swift runtime layer that manages the dynamic library, FiberHandle *, threading, string memory, app sandbox paths, and scene lifecycle. ## Quick Start iOS builds must be performed in a macOS + Xcode environment. First build the iOS native library from the fiber-ffi repository root: [sh code block] The current Makefile builds the following by default: [text code block] After the build completes, sync the two dynamic libraries into the demo: [text code block] You can also use the demo's Makefile to build and copy them: [sh code block] Then open the Xcode project: demos/ios/FiberDemo.xcodeproj Select the FiberDemo target and run it. Running on a real device requires configuring the development team in the target signing settings, or passing it through the command line: [sh code block] If you need to specify your own bundle identifier: [sh code block] ## Integration Steps ### Prepare Files An iOS project needs the following files. The header file is available at include/fiberffi.h: [text code block] The corresponding paths in the demo are: - demos/ios/FiberDemo/FiberDemo-Bridging-Header.h - demos/ios/FiberDemo/Libs/iphoneos/libfiberffi.dylib - demos/ios/FiberDemo/Libs/iphonesimulator/libfiberffi.dylib - demos/ios/FiberDemo/Resources/fiberconfig.yml fiberconfig.yml must include both fiber and ckb services. The demo uses a testnet configuration, which can be referenced directly at demos/ios/FiberDemo/Resources/fiberconfig.yml. chain affects the invoice currency. The demo uses a testnet configuration, so when creating an invoice, the currency should be the testnet value Fibt (the FFI enum is FIBERINVOICECURRENCYFIBT, and in Swift it is FiberInvoiceCurrency(2)). If the configuration is switched to mainnet or devnet, the currency must also be changed to Fibb or Fibd; otherwise newInvoice will be rejected by Fiber RPC. For the demo, fixing one network and its corresponding currency is enough. A production app should choose dynamically based on the current network. ### Xcode Configuration An iOS project needs four pieces of configuration: 1. Let Swift find the C header. 2. Let the linker find the libfiberffi.dylib for the current platform. 3. Copy the dylib into the app bundle. 4. Code sign the dylib for real-device execution. The key settings in the demo target are: [text code block] $(PLATFORMNAME) expands to iphoneos or iphonesimulator during the Xcode build, so the same target can automatically link the dylib from the corresponding directory for the current SDK. The demo also adds an Embed Fiber FFI dylib build phase. It copies: [text code block] to: [text code block] If a signing identity is available for the current build, the script also runs: [sh code block] iOS cannot freely load dynamic libraries from arbitrary external paths like desktop programs can. libfiberffi.dylib must be bundled with the app at build time and placed where it can be found through @rpath at runtime. The dylib inside a real-device app bundle must also be signed. The current demo directly embeds a loose dylib to validate the integration path. A production SDK can further package it as an .xcframework, dynamic framework, or Swift Package to reduce manual search path and embed script configuration for integrators. ### Swift Bridging Header Swift imports the C ABI through a bridging header: [c code block] After configuration, Swift can directly use symbols such as FiberStartOptions, FiberFfiStatus, fiberstart, fiberstop, and fiberstringfree. Business code should not call these C functions in scattered places. It is recommended to wrap all FFI details in a Swift file as a FiberRuntime singleton, as the demo does. ### Swift Runtime FiberRuntime.swift is the iOS adapter layer: the UI calls FiberRuntime, and the runtime calls the C ABI in fiber_ffi.h. The app side usually only uses FiberRuntime; view controllers should not directly store FiberHandle , assemble FiberOptions, or handle char **out_json. A runtime is needed because fiber-ffi exports a C ABI, while iOS business code is better expressed with Swift types and iOS lifecycle concepts. The runtime converts String, Bool, amounts, and other parameters into the format required by C, holds a single FiberHandle , serializes calls on the same handle, and converts returned JSON or error text into Swift String values. Pay special attention to memory boundaries: - Strings passed from Swift to FFI can use withCString. The generated pointer only needs to remain valid during this FFI call. - Strings returned by FFI through char *out_* are allocated by libfiberffi.dylib; Swift must copy them into String and then call fiberstringfree. - eventjson in event callbacks is only valid during the callback. Copy it immediately after entering Swift. - fiberstop consumes the handle. After Stop, the old handle must not be passed back to FFI. The demo handles returned JSON as follows: [swift code block] After a failure, read the thread-local error message on the same thread immediately after the failed call: [swift code block] ### Prepare Data Directories Before startup, put the configuration file at a regular file path and prepare the data directory: [text code block] The demo packages fiberconfig.yml as a bundle resource and copies it to Documents before startup: [swift code block] Copying the bundle resource into the sandbox has two benefits: it gives you a normal readable file path, and it lets you reuse the same flow later if configuration needs to be generated per user, network, or environment. A production app can also choose Application Support as the data root directory. The key point is that configpath must be a file path that the current process can read directly, not the YAML contents themselves. The CKB private key is stored at: [text code block] The demo writes this file through setCkbPrivateKey: the Swift layer normalizes the user-entered 32-byte hex private key and writes it to ckb/key, then sets the following before startup: [swift code block] Distinguish the "plaintext file at import time" from the "persistent file after Fiber starts." Fiber's default logic reads FIBERSECRETKEYPASSWORD when loading the CKB private key. If it finds that ckb/key is still parseable plaintext hex, it encrypts it with this password and overwrites the file in place. Later it decrypts the file with the same password. Therefore, after the first SetCKBKey and before the first successful Start, ckb/key may briefly remain plaintext. After a successful start, the file should have been migrated to encrypted contents. If writing ckb/key manually, the contents should be a 32-byte hex string without the 0x prefix. The demo automatically removes a 0x prefix from user input. The plaintext migration logic in the Fiber source rejects key files with a 0x prefix. This is only a demo scheme. A production app should use its own wallet, Keychain, Secure Enclave, or key management process, and must not use the fixed FIBERSECRETKEYPASSWORD from the demo. This password and the encrypted ckb/key are both required to recover the ability to sign funds. If either one is lost, the CKB private key cannot be recovered from the encrypted file. ### Start and Stop Assemble FiberStartOptions when starting the node: [swift code block] After successful startup, save the handle: [swift code block] When stopping the node, call: [swift code block] Do not start multiple nodes in the same process. For repeated Start calls, you can return already running directly. Before Stop, clear the handle in the Swift runtime first, so callbacks or concurrent calls cannot get the old pointer again. After Stop, the old handle must not be used. ### Calling Features The current demo wraps the following Swift methods: | Method | Purpose | | --- | --- | | start() | Start the node | | stop() | Stop the node | | nodeInfo() | Read node information | | listPeers() | List peers | | connectPeer(...) | Connect a peer | | listChannels() | List channels | | createChannel(...) | Create a channel | | shutdownChannel(...) | Close a channel | | newInvoice(...) | Create an invoice | | sendPayment(...) | Pay an invoice | Pay attention to amount units: - The demo's createChannel(...) accepts a decimal CKB string for display, such as 1.5. The Swift runtime converts it to shannons before filling the FFI FiberU128. - The demo's newInvoice(...) accepts an integer shannons string and does not accept decimal CKB. - Amount fields actually passed into FFI always use integer smallest units: CKB uses shannons, where 1 CKB = 100000000 shannons; UDT uses the integer smallest unit of that UDT. - If UDT channels or UDT invoices are added later, pass UDT parameters such as fundingudttypescriptjson / udttypescriptjson, and pass amount as the UDT's raw integer amount. Do not reuse the CKB amount * 100000000 conversion. Swift has no built-in UInt128. The demo uses a small UInt128Value struct to store low and high, and finally converts it to the FFI type: [swift code block] When adding new capabilities, prefer adding methods to FiberRuntime and reusing the existing withHandle, withOptionalCString, jsonResult, amount parsing, and error handling patterns. ### Handling Events Pass the native event callback during startup: [swift code block] The UI layer listens for events: [swift code block] Event contents are JSON strings. Business code should parse the JSON first, then dispatch based on the kind field. Common kind values include: [text code block] The list above is only a set of common event examples, not a complete enum. When consuming events, use the kind field in the callback JSON as the source of truth. If an event kind is not currently relevant or is added in the future, it can be logged and ignored. Do not update UI directly from the native callback thread. Switch to the main thread after receiving events. Also avoid long-running work or direct reverse calls into other FFI APIs inside the callback. ### Lifecycle The iOS demo only performs minimal lifecycle cleanup: when a scene disconnects or the app terminates, it calls the same stop helper: [swift code block] Recommended handling: - Do not execute potentially blocking FFI calls directly on the UI main thread. - Start, Stop, and calls on the same handle must be serialized. - Execute Stop when the user logs out, switches accounts, or explicitly closes the node. - When switching network, account, or data directory, call Stop first, then restart the app process. - Use different databaseprefix values for different users, accounts, and networks. - Background execution, disconnection recovery, push, wallet-hosted nodes, or server-assisted designs must be designed by production app developers according to product shape and iOS background capabilities. The demo does not attempt to implement a persistent background node. A normal iOS app has limited execution time after entering the background. Long-lived P2P connections, channel negotiation, and on-chain monitoring may all be suspended by the system. The current demo is meant to validate the in-process embedding path while the app is active. It is not equivalent to a complete persistent background node. ## Build - The current demo prepares one dylib for iphoneos and one for iphonesimulator. Real devices use aarch64-apple-ios, and Apple Silicon simulators use aarch64-apple-ios-sim. - The demo's simulator build fixes ARCHS=arm64, so it needs the arm64 simulator dylib generated by make build-ios-sim. - iOS builds currently enable only the sqlite feature to avoid making RocksDB a default mobile storage dependency. - IOSDEPLOYMENTTARGET defaults to 15.0. If the Xcode target deployment target is changed to another version, pass the same value when building the Rust dylib. - IOSRUSTFLAGS sets -Wl,-installname,@rpath/libfiberffi.dylib. Do not change it to an absolute path, or the app may fail to find the embedded dylib at runtime. - The dylib in a real-device app bundle must be signed. The demo embed script re-signs the dylib with the current target's signing identity. Example for changing the deployment target: [sh code block] ## Demo Operations 1. SetCKBKey: enter the CKB private key. 2. Start: start the node. 3. NodeInfo: read the node address and pubkey. 4. Peers: view or connect peers. 5. Invoice: create an invoice or pay an invoice. 6. Channels: list, create, or close channels. 7. Stop: stop the node. ## Q&A ### Library not loaded: @rpath/libfiberffi.dylib Check whether libfiberffi.dylib is under FiberDemo/Libs//, whether the target links -lfiberffi, whether LDRUNPATHSEARCHPATHS contains @executablepath/Frameworks, and whether the embed script has copied the dylib into the app bundle's Frameworks directory. ### Real-device startup fails with a dylib signing error Check the development team, bundle identifier, and signing identity. libfiberffi.dylib inside the real-device app bundle must be signed together with the app. The demo embed script re-signs the dylib when EXPANDEDCODESIGNIDENTITY exists. ### Simulator link fails or runtime reports an architecture mismatch Confirm that the current simulator is arm64 and that you are using: [text code block] Do not put the real-device aarch64-apple-ios dylib into the iphonesimulator directory. ### Swift cannot find fiberstart or FiberStartOptions Check whether SWIFTOBJCBRIDGINGHEADER points to the correct bridging header, whether HEADERSEARCHPATHS includes the directory containing include/fiberffi.h, and whether the bridging header contains: [c code block] ### Startup fails and says the configuration file is unreadable Do not pass only YAML contents. fiberstart requires configpath to be a real file path. The demo copies fiberconfig.yml from the bundle into Documents before passing it in. ### Startup fails and says the CKB private key is not set The demo requires running SetCKBKey first. The key is a 32-byte hex string, with or without a 0x prefix. The demo normalizes it and writes it to Documents/fiber-data/ckb/key. During the first successful Start, Fiber uses FIBERSECRETKEYPASSWORD to migrate this plaintext key into an encrypted file. Later starts must continue using the same password to decrypt it. ### Can the node stay online after the app enters the background? The current demo does not guarantee persistent background execution. A normal iOS app is suspended by the system after entering the background, so P2P connections and event processing may pause. If background capabilities are needed, first clarify the product scenario, then evaluate Background Modes, push, wallet-hosted nodes, or server-assisted designs. --- ## Using fiber-js in a Browser Extension Source: https://www.fiber.world/docs/build/browser-extension ## Introduction This demo verifies whether @nervosnetwork/fiber-js can run inside a browser extension. ( fiber-js is a JavaScript wrapper around the Fiber WebAssembly runtime. It starts a Fiber node inside browser workers and exposes JavaScript methods for controlling that node.) Project repository: https://github.com/joii2020/fiber-browser-extension-demo Current support status: - Chrome / Chromium-based browsers: supported - Firefox: not supported for now - Safari (macOS): not supported for now The current demo is mainly designed for Chrome / Chromium Manifest V3 extensions. ## Overall Extension Structure In this demo, fiber-js runs inside a hidden Offscreen Document instead of the popup or a normal web page. The recommended structure is: [code block] In simple terms: - popup / extension page: handles the user interface; - background service worker: receives requests and forwards messages; - Offscreen Document: runs fiber-js; - fiber-js: starts the Fiber node and handles related operations. ## Why Use an Offscreen Document The main reason for using an Offscreen Document is to keep fiber-js running in the extension background. If fiber-js runs in the popup, a normal extension page, or a web page, it may be interrupted when that page is closed. For payments and channel operations, being interrupted midway may cause abnormal states and may even affect transaction results. An Offscreen Document is not shown to the user, but it can stay alive in the background for a relatively long time. This makes it a better container for running fiber-js. It also provides a normal page-like window / document environment, which makes it easier to load WASM, Workers, and other resources. ## What the Offscreen Document Does The Offscreen Document is mainly responsible for: - loading @nervosnetwork/fiber-js; - initializing WASM; - creating the Fiber instance; - calling fiber.start(); - storing Fiber runtime state; - receiving requests forwarded by the background; - returning execution results or error messages. You can think of it as the background runtime page for fiber-js inside the browser extension. ## Extension Call Flow The call flow for using fiber-js in the extension is roughly as follows: [code block] In other words, the popup does not directly hold the Fiber instance. The popup only sends requests, for example: - start Fiber; - query status; - connect to a peer. If invoice creation, invoice payment, channel opening, channel closing, and other operations are supported later, the same structure should be used: the popup sends messages, and the Offscreen Document handles the actual fiber-js calls. ## Required Configuration ### 1. Chrome Version and Manifest V3 Offscreen Document requires Chrome 109+ and Manifest V3. If you write the manifest directly, you can declare the minimum Chrome version: [json code block] The current demo only targets Chrome / Chromium MV3 extensions. It does not apply to Firefox or Safari. ### 2. Offscreen Permission Because an Offscreen Document is used, the following permission needs to be declared in the manifest: [json code block] ### 3. WASM and Worker Permissions fiber-js loads WASM and also uses Workers, so CSP needs to be configured: [json code block] Where: - wasm-unsafe-eval: allows WASM to run; - worker-src 'self': allows Workers inside the extension package to be loaded. ### 4. Cross-Origin Isolation Configuration fiber-js needs to use SharedArrayBuffer in the browser. In Chrome / Chromium extensions, the following fields need to be added to the manifest: [json code block] You can use the following code to check whether the environment meets the requirements: [js code block] If these two conditions are not met, fiber-js may fail to start properly. ### 5. Fiber Key Storage The current demo generates a Fiber key inside the Offscreen Document and stores it in that page's own localStorage. The storage key is: [code block] On subsequent startups, the demo reads this key first to avoid generating a new Fiber node identity every time the popup is opened. If this key changes on the next startup, fiber-js will treat it as a different node identity. Fiber state, channels, and other data associated with the old key may no longer be usable and may not be recoverable. ## Example Manifest Configuration Full configuration example: [json code block] ## Build Notes During packaging, make sure that: - the .wasm file has been included in the extension package; - the Worker file can be loaded by extension pages; - the manifest includes the offscreen permission; - the manifest includes the WASM, Worker, and cross-origin isolation configurations. If the .wasm file is missing, the extension may still be installed, but fiber-js may fail to start. When publishing the demo, it is recommended to clearly mark it as: [code block] Do not mark Firefox and Safari as supported targets. ## Memory Usage Use the browser's built-in memory tools to inspect the Offscreen Document page. Current simple test results: - initial memory usage is about 135 MB; - no obvious memory leak was observed in idle state. Only the idle scenario has been tested so far. Further testing is still needed for: - long-running usage; - repeated starts and stops; - repeated connections; - channel opening and closing; - recovery after abnormal disconnection. ## Firefox and Safari (macOS) Currently, fiber-js cannot run in Firefox or Safari as a browser extension. For normal web pages, if COOP/COEP can make crossOriginIsolated=true, then the SharedArrayBuffer requirement can be satisfied. The reason is that the browser implementation of fiber-js depends on SharedArrayBuffer to share memory between Workers, and uses Atomics for synchronization. The Web platform only exposes SharedArrayBuffer when crossOriginIsolated=true. In Chrome / Chromium extensions, cross-origin isolation can be enabled through the extra manifest fields crossoriginembedderpolicy and crossoriginopenerpolicy. MDN's WebExtension manifest key list does not include descriptions for crossoriginembedderpolicy / crossoriginopenerpolicy. Firefox and Safari also do not currently provide equivalent extension manifest fields like Chrome / Chromium does. Therefore, even if a normal web page can satisfy crossOriginIsolated=true, extension pages currently lack an equivalent configuration entry. Related Firefox community issues: - https://bugzilla.mozilla.org/showbug.cgi?id=1673477 - https://bugzilla.mozilla.org/showbug.cgi?id=1750654 No clearly corresponding Safari issue has been found yet. --- ## Tutorial: Build a Game Source: https://www.fiber.world/docs/build/gaming/simple-game ## Overview This tutorial will guide you through creating a simple Phaser.js game that integrates with the Fiber Testnet. You'll learn how to implement real-time token transfers within a game environment, enabling instant micro-payments based on in-game actions. This demonstrates how traditional game mechanics can be seamlessly enhanced with blockchain functionality. !Game Cover The full code of the game can be found in the github repo. ## Prerequisites Before getting started, make sure you have: - Git, Node.js and pnpm - Basic understanding of TypeScript and Fiber network - Two running Fiber nodes (see Running a Node) - An open payment channel between your two nodes (see Basic Transfer) - Some CKB Testnet tokens in your payment channel ## Project Setup ### 1. Prepare two Fiber Nodes You need to setup and running two fiber Testnet nodes locally, and make sure they have at least 500 CKB liquidity in their payment channels. It is highly recommended to follow the Run a Fiber Node and Basic Transfer Example guides to set up your nodes first. In this tutorial, we assume the info for the two nodes are: [sh code block] You can change the info to your own nodes in the following steps. ### 2. Create a New Phaser.js Project Since the game design is not the focus of this tutorial, we'll simply take a Phaser.js demo project and integrate the Fiber payment. The demo project can be found in the github repo. It is a simple game with Typescript support that lets you shoot the enemy ship and dodge its attacks to score as many points as possible in a short amount of time. [bash code block] ### 3. Set Up Vite Configuration Next, let's edit the vite.config.ts file for bundling our two fiber local nodes since the RPC of nodes is not cors-enabled. [typescript code block] The proxy configuration redirects API calls to your local Fiber nodes. Adjust the ports to match your node configuration. ## Implementing the Fiber Integration ### 1. Create the Fiber RPC Class Most interaction with the Fiber network is done through the RPC API. So let's create a wrapper for the Fiber RPC API in our typescript project. We'll use @ckb-ccc/fiber from @ckb-ccc/core to help us create the RPC client. First, install the dependencies: [bash code block] Fiber Node and @ckb-ccc/fiber use independent version numbers. This tutorial targets Fiber Node v0.9.0; the RPC SDK is currently published under the canary version shown above rather than as 0.9.0. ### 2. Create the FiberNode Class Next, create a helper class to manage Fiber node operations at src/fiber/node.ts: View full code of src/fiber/node.ts [typescript code block] ### 3. Create Fiber Integration Main Module Now, create the main Fiber integration file at src/fiber/index.ts: View full code of src/fiber/index.ts [typescript code block] Pay attention to the payPlayerPoints and payBossPoints functions — they handle CKB payments between players when the player hits the enemy ship or when the boss hits the player. We defined that payment rate as 1 CKB per point in amountPerPoint, meaning that if the player score 10 points, the boss will pay 10 CKB to the player and vice versa. All payments are made in real-time through the Fiber network. ## Integrating Micro-payment with Game Mechanics Now that the Fiber integration is set up, let's integrate it with our Phaser.js game. ### 1. Edit the Main Scene The main file to edit is src/scenes/MainScene.ts. We'll start by adding some properties in the MainScene class to host the Fiber nodes and track the score. [typescript code block] Next, we need to initialize the Fiber nodes and the score in the init function. Note that init needs to be changed to an async function so that we can await for the Fiber nodes initialization. [typescript code block] Next, let's look at the setupCollisions function. We need to pay CKB to the player when the player hits the enemy ship, and to the boss when the boss hits the player. [typescript code block] In case you need the full code of the MainScene: View full code of src/scenes/MainScene.ts [typescript code block] ### 2. Edit the GameOver Scene The GameOverScene is the scene that will be launched when the game is over. The original code only display the final scoring points of the player. We need to display the earn/lose CKB amount to the scene too. View full code of src/scenes/GameOverScene.ts [typescript code block] ## Running the Game All good now! Let's run your game! [sh code block] Before running your game, make sure your Fiber nodes are running and have an open payment channel between them. You can follow the Run a Fiber Node and Basic Transfer Example guides to set up your nodes. If everything is set up correctly, you should be able to click and play the game like this: !Game Running Open the browser console to view the payment logs. When the game is over, you'll see the final scores along with the payment info, like this: !Game Over ## Conclusion In this tutorial, you've learned how to integrate the Fiber network with a Phaser.js game to enable real-time token transfers based on in-game actions. This approach opens up new possibilities for blockchain-based gaming, including: - Real-time microtransactions without gas fees - Play-to-earn mechanics with instant payments - Token-based in-game economies - Single transactions take roughly 300-500 ms when both nodes run on the same machine By leveraging Fiber's Layer 2 scaling solution, you can build games with blockchain features that don't compromise on user experience or performance. For a production environment and more advanced use cases, consider implementing: - Channel opening logic with player matching - On-chain settlement of final scores when the game ends, including proper channel closure - Error handling for insufficient channel balance - Security measures for channel management - Multi-player token pools - Conditional payments based on game achievements - Assets trading through Fiber network channels - ... Happy coding, and enjoy building your blockchain-enabled games! --- ## Game Payment Patterns Source: https://www.fiber.world/docs/build/gaming/payment-patterns ## Overview There are three basic ways to attach Fiber payments to a game. They differ in one question: when can a losing player refuse to pay? 1. Pay as you play — payments happen after each game event, on the honor system. 2. Locked stakes, trusted oracle — both players lock money before play with hold invoices; a game server releases it event by event. 3. Locked stakes, provable oracle — the same lock-up, but the oracle proves each outcome with adaptor signatures instead of holding plaintext secrets. Each step down the list costs more engineering and buys more trust-safety. Pick based on how competitive the game is and how much you want to trust the oracle. ## Tier 1: pay as you play (honor system) The Build a Game tutorial works like this: [text code block] Every game event triggers a small payment after the fact. Nothing is locked up front. Trade-off: a losing player can simply stop paying — nothing enforces the transfer. This is fine for cooperative or arcade games where payments are rewards, and broken for competitive PvP with real stakes, where refusing to pay is the winning move. ## Tier 2: locked stakes with a trusted oracle Two Fiber primitives fix the refusal problem: - a payment hash commits to a secret preimage without revealing it; - a hold invoice locks the payer's funds against that hash — the payee can settleinvoice only once the preimage is revealed, otherwise cancelinvoice (or a timeout) returns the funds to the payer. openstrike-fiber-arena, a 1v1 FPS, uses them like this: [text code block] Money is locked before the first frame, so a player who gets hit cannot refuse to pay. Every release is checked by the client against the pre-published hashes and stays inside a per-match cap. Trade-off: the oracle — here the authoritative game server, which decides damage anyway — is trusted. It holds plaintext preimages and could in theory release one without a real event, though only inside the pre-authorized cap, and it never touches wallet keys. Best for real-time PvP that already needs an authoritative server. ## Tier 3: locked stakes with a provable oracle fiber-game, a turn-based protocol, locks stakes mutually — each player creates a hold invoice payable by the opponent's payment hash, and both pay each other — then removes the oracle's plaintext secrets with adaptor signatures: - before play, the oracle commits to a nonce R; each outcome ("A wins", "B wins", …) gets a signature point R + H(R ‖ O ‖ game_id ‖ outcome) · O; - each player encrypts their preimage with the signature point of the outcome where they lose, and only the ciphertext is published; - when the oracle signs the real outcome, the winner derives the same point from that signature, decrypts the loser's preimage, and settles. The oracle never stores a plaintext preimage, and a signature for the wrong outcome does not match the pre-committed point — cheating is cryptographically detectable instead of "trust us". Trade-off: more protocol machinery (commitments, encrypted preimages, signature verification), and it fits discrete win/lose/draw outcomes better than a stream of real-time events. Best for turn-based games — rock-paper- scissors, guess-the-number, card games. ## Which one should you pick? | | Tier 1: pay-as-you-play | Tier 2: trusted oracle | Tier 3: provable oracle | | ---------------------- | ----------------------- | --------------------------------------- | ---------------------------------------- | | When money moves | after each event | locked before play, released per event | locked before play, released on result | | Loser can refuse to pay | yes | no | no | | Oracle trust | none | trusted, cap-bounded | cryptographically accountable | | Implementation cost | lowest | medium | highest | | Best for | co-op / arcade | real-time skill-based PvP | turn-based / commitment games | Rule of thumb: if payments are just rewards, stay at Tier 1. If your game already runs an authoritative server, Tier 2 costs almost nothing — the server already decides the truth, so let it sign payment releases too. If the outcome is discrete and you want the oracle out of the trust model entirely, go Tier 3. Both implementations are open source — see openstrike-fiber-arena for the real-time client-server design and fiber-game for the adaptor-signature protocol. The hands-on starting point is the Build a Game tutorial. --- ## Interactive tutorials Source: https://www.fiber.world/docs/build/interactive-tutorials --- ## Configuration Reference Source: https://www.fiber.world/docs/operate/config-reference Fiber Node is mainly configured by config.yml. Many fields in this file can also be set from command-line flags or environment variables when starting fnn. ## Configuration Loading Native fnn loads configuration file in this order: 1. -c, --config if provided. 2. $BASEDIR/config.yml when -d, --dir is provided. 3. $HOME/.fiber-node/config.yml. Values from command-line flags and supported environment variables override config.yml; values not provided by either source fall back to built-in defaults. If both a command-line flag and its environment variable are provided, use the command-line value as the effective override. services decides which services start. Set it in config.yml, or pass one or more service names with -s, --services when starting fnn, for example --services fiber,rpc,ckb. When the command line includes at least one service name, that list replaces the services list in config.yml; when no service name is provided, fnn uses the list from config.yml. Valid service names are fiber, rpc, ckb, and cch. Data directories are derived from BASEDIR, which defaults to $HOME/.fiber-node and can be changed with global -d, --dir. Native startup sets service directories to $BASEDIR/fiber, $BASEDIR/ckb, and $BASEDIR/cch; do not rely on per-service basedir entries in config.yml. The generated help may list per-service base directory flags and environment variables, but native startup rewrites those service directories from BASE_DIR. > Tip: Run fnn --help to list the available configuration flags and environment variables. Most service-scoped options are generated from the same config structs that parse config.yml, so the corresponding YAML fields work too. For example, --fiber-listening-addr maps to fiber.listeningaddr, and --rpc-listening-addr maps to rpc.listeningaddr. ### Global Startup Options These options affect native fnn startup. They are not config.yml fields. | Option | Description | |--------|-------------| | -c, --config | Load the specified config file. | | -d, --dir | Set BASEDIR; also changes the implicit config path to $BASEDIR/config.yml when --config is not provided. | | -s, --services | Services to start. When present, this replaces the services list from config.yml. Values are comma-separated or space-separated. | | --check-validate | Validate the Fiber store database and exit without starting services. | | --restore | Restore the Fiber database and node keys from a v0.9.0 timestamped backup directory, then exit. See the native v0.9.0 restore requirements. | ## Top-Level Fields [yaml code block] | Field | Type | Default | Description | |-------|------|---------|-------------| | services | string[] | required | Services to run. Valid values: fiber, rpc, ckb, cch. | | fiber | object | {} | Fiber P2P, channel, gossip, watchtower, proxy, and Tor settings. | | rpc | object | {} | JSON-RPC server settings. | | ckb | object | {} | CKB chain integration and funding transaction settings. | | cch | object | {} | Cross-Chain Hub settings. Native builds only. | Service dependencies: - fiber requires ckb. - cch requires either the in-process fiber service or cch.fiberrpcurl. - Standalone cch mode, where fiber is not running in the same process, also requires cch.wrappedbtctypescript. ## Fiber Section (fiber) ### Network | Field | Type | Default | Description | |-------|------|---------|-------------| | listeningaddr | string | /ip4/0.0.0.0/tcp/0 | TCP listening address for P2P connections (multiaddr format) | | reuseportforwebsocket | bool | true | Also listen on a WebSocket multiaddr using the same TCP port. | | announcelisteningaddr | bool | false | Whether to announce the listening address to peers | | announceprivateaddr | bool | false | Whether to announce and process private addresses. Keep false unless testing or running a private network. | | announcedaddrs | string[] | [] | Public addresses to announce (multiaddr format). Set this to your public IP. | | announcednodename | string | — | Node name shown in RPC responses, TUI, and network graph | | bootnode_addrs | string[] | [] | Bootstrap node addresses for initial peer discovery (multiaddr format) | | chain | string | required | Chain specification: mainnet, testnet, or a custom chain spec path relative to BASEDIR. | | scripts | array | [] | Fiber contract script configuration. Mainnet/testnet configs should provide at least FundingLock and CommitmentLock entries from the bundled templates. | ### Contract Scripts [yaml code block] name must be one of the contract names used by Fiber, such as FundingLock or CommitmentLock. Each celldeps entry must contain exactly one of typeid or celldep. ### Channel | Field | Type | Default | Description | |-------|------|---------|-------------| | openchannelautoacceptminckbfundingamount | u64 | 10000000000 (100 CKB) | Minimum CKB funding to auto-accept channel requests | | autoacceptchannelckbfundingamount | u64 | 9900000000 (99 CKB) | CKB amount to auto-accept. Set to 0 to disable auto-accept. | | tobeacceptedchannelsnumberlimit | usize | 20 | Max pending channels from one peer | | tobeacceptedchannelsbyteslimit | usize | 51200 (50 KiB) | Max storage bytes of pending channels from one peer | | pendingchannelsnumberlimit | usize | 100 | Max pending channel openings globally | | fundingtimeoutseconds | u64 | 86400 (1 day) | Timeout for funding transaction confirmation | | externalfundingtimeoutseconds | u64 | 300 (5 min) | Timeout waiting for externally signed funding tx | ### TLC | Field | Type | Default | Description | |-------|------|---------|-------------| | tlcexpirydelta | u64 | 14400000 (4 hours, ms) | Expiry delta used when forwarding a TLC | | tlcminvalue | u128 | 0 | Minimum TLC value (0 = no minimum) | | tlcfeeproportionalmillionths | u128 | 1000 | Fee for forwarding TLCs (1000 = 0.1%) | finaltlcexpirydelta is not a node-level config field. It is a payment/invoice-level value with separate defaults in the payment code. ### Node Announcements, Peers, and Gossip | Field | Type | Default | Description | |-------|------|---------|-------------| | autoannouncenode | bool | true | Whether to automatically announce this node on startup. | | announcenodeintervalseconds | u64 | 3600 (1 hour) | NodeAnnouncement reannounce interval. 0 means never reannounce. | | maxinboundpeers | usize | 16 | Maximum inbound connections | | minoutboundpeers | usize | 8 | Minimum outbound connections to maintain | | enablepeerreconnectbackoff | bool | true | Whether to schedule reconnect backoff for peers with active channels after disconnects | | syncnetworkgraph | bool | true | Whether to sync network graph from peers | | gossipnetworkmaintenanceintervalms | u64 | 60000 (1 min) | Interval for gossip network maintenance | | gossipstoremaintenanceintervalms | u64 | 20000 (20 sec) | Interval for gossip store maintenance | | gossipnetworknumtargetedactivesyncingpeers | usize | 3 | Number of peers to actively request missed gossip messages from | | gossipnetworknumtargetedoutboundpassivesyncingpeers | usize | 3 | Number of outbound peers targeted for passive gossip syncing | | gossippolicy | object | built-in policy | Advanced config-file-only gossip rate-limit and ban policy. It is not exposed as CLI flags or environment variables. | ### Watchtower | Field | Type | Default | Description | |-------|------|---------|-------------| | watchtowercheckintervalseconds | u64 | 60 | Interval to check watchtower. 0 = never check. | | disablebuiltinwatchtower | bool | false | Disable built-in watchtower actor | | standalonewatchtowerrpcurl | string | — | URL of standalone watchtower RPC server | | standalonewatchtowertoken | string | — | RPC token for standalone watchtower | If disablebuiltinwatchtower is true, standalonewatchtowerrpcurl must be set. ### Proxy & Tor proxy and onion are config-file-only fields. They are not exposed as direct CLI flags or environment variables. | Field | Type | Default | Description | |-------|------|---------|-------------| | proxy.proxyurl | string | — | SOCKS5 proxy URL (e.g., socks5://127.0.0.1:9050) | | proxy.proxyrandomauth | bool | true | Random username/password for Tor stream isolation | | onion.listenononion | bool | false | Make node reachable via .onion address | | onion.onionserver | string | — | Tor SOCKS5 server for outbound .onion connections, e.g. 127.0.0.1:9050 | | onion.p2plistenaddress | string | derived | Local address the onion service forwards to, e.g. 127.0.0.1:8228 | | onion.onionprivatekeypath | string | $BASEDIR/fiber/onionprivatekey | Path for the onion service private key | | onion.torcontroller | string | 127.0.0.1:9051 | Tor controller address | | onion.torpassword | string | — | Tor controller password | | onion.onionexternalport | u16 | 8228 | External port exposed by the onion service | | onion.onionservicestarttimeout | usize | 5 | Seconds to wait for onion service registration | ### Feature-Gated Fields | Field | Type | Default | Description | |-------|------|---------|-------------| | metricsaddr | string | — | Metrics endpoint address, available only when the binary is built with the metrics feature. | ## RPC Section (rpc) | Field | Type | Default | Description | |-------|------|---------|-------------| | listeningaddr | string | [::1]:0 | Listen address for JSON-RPC. The bundled configs use 127.0.0.1:8227. | | biscuitpublickey | string | — | Public key for Biscuit authorization. If set, RPC requires Bearer token auth. | | enabledmodules | string[] | see below | RPC modules to enable | | corsenabled | bool | false | Enable CORS for HTTP RPC | | corsallowedorigins | string[] | [] | Allowed origins for CORS (empty = allow all if CORS enabled) | Default modules are cch, channel, graph, payment, info, invoice, and peer. Builds with the watchtower feature also enable watchtower by default. Available module names include cch, channel, graph, payment, info, invoice, peer, admin, pubsub, watchtower, dev in debug builds, and prof when built with the pprof feature. The admin module provides the native-node backup RPC and is not enabled by default. If listeningaddr is public, biscuitpublickey is required. Without auth, the RPC server refuses to bind public addresses such as 0.0.0.0:8227. ## CKB Section (ckb) | Field | Type | Default | Description | |-------|------|---------|-------------| | rpcurl | string | http://127.0.0.1:8114 | CKB node RPC URL | | udtwhitelist | array | [] | Supported UDT configurations (see below) | | txtracingpollingintervalms | u64 | 4000 | Polling interval for the CKB transaction tracing actor | | fundingtxshellbuilder | string | — | External command used to build funding transactions | ### UDT Configuration [yaml code block] script.args is compiled as a regular expression and matched against the UDT type script args rendered as a 0x-prefixed hex string. Use an exact regex if you only want to match one token. Each UDT celldeps entry must contain exactly one of: - typeid, a script used to resolve the current cell dep through the CKB indexer. - celldep, a direct outpoint plus deptype. fundingtxshellbuilder is configured under ckb, but its environment variable is FIBERFUNDINGTXSHELLBUILDER. The command receives a funding request JSON object on stdin and must print a CKB transaction JSON object to stdout on success. ## CCH Section (cch) — Cross-Chain Hub | Field | Type | Default | Description | |-------|------|---------|-------------| | lndrpcurl | string | https://127.0.0.1:10009 | LND gRPC endpoint | | lndcertpath | string | — | Path to TLS cert for gRPC | | lndmacaroonpath | string | — | Path to Macaroon file | | wrappedbtctypescriptargs | string | required | Wrapped BTC UDT type script args | | orderexpirydeltaseconds | u64 | 129600 (36h) | Cross-chain order expiry time | | basefeesats | u64 | 100 | Base fee per order (satoshis) | | feeratepermillionsats | u64 | 3000 | Proportional fee per million satoshis (0.3%) | | maxoutgoingfeepercentage | u64 | 80 | Max percentage of the collected CCH fee that may be used as outgoing Fiber routing fee budget. Must be 1..=100; the default leaves at least 20% for the operator. | | btcfinaltlcexpirydeltablocks | u64 | 360 | Final TLC relative expiry in BTC blocks | | ckbfinaltlcexpirydeltaseconds | u64 | 216000 (60h) | Final TLC relative expiry for CKB Fiber payments | | minoutgoinginvoiceexpirydeltaseconds | u64 | 21600 (6h) | Minimum acceptable relative expiry for outgoing invoices | | fiberrpcurl | string | — | External Fiber RPC endpoint for standalone CCH mode | | wrappedbtctypescript | string | — | Full wrapped BTC type script JSON. Required in standalone CCH mode. | | ignorestartupfailure | bool | false | Config-file-only setting to ignore CCH startup failure. | Relative lndcertpath and lndmacaroonpath values are resolved under the effective CCH data directory, normally $BASEDIR/cch. ## Environment Variables Only fields wired to clap can be overridden by environment variables. List values use comma-separated strings. Complex values such as FIBERSCRIPTS and CKBUDTWHITELIST are easier and less error-prone in YAML. ### Fiber | Field | Environment variable | |-------|----------------------| | listeningaddr | FIBERLISTENINGADDR | | announcelisteningaddr | FIBERANNOUNCELISTENINGADDR | | announceprivateaddr | FIBERANNOUNCEPRIVATEADDR | | announcedaddrs | FIBERANNOUNCEDADDRS | | bootnodeaddrs | FIBERBOOTNODEADDRS | | announcednodename | FIBERANNOUNCEDNODENAME | | chain | FIBERCHAIN | | scripts | FIBERSCRIPTS | | openchannelautoacceptminckbfundingamount | FIBEROPENCHANNELAUTOACCEPTMINCKBFUNDINGAMOUNT | | autoacceptchannelckbfundingamount | FIBERAUTOACCEPTCHANNELCKBFUNDINGAMOUNT | | tlcexpirydelta | FIBERTLCEXPIRYDELTA | | tlcminvalue | FIBERTLCMINVALUE | | tlcfeeproportionalmillionths | FIBERTLCFEEPROPORTIONALMILLIONTHS | | autoannouncenode | FIBERAUTOANNOUNCENODE | | announcenodeintervalseconds | FIBERANNOUNCENODEINTERVALSECONDS | | gossipnetworkmaintenanceintervalms | FIBERGOSSIPNETWORKMAINTENANCEINTERVALMS | | maxinboundpeers | FIBERMAXINBOUNDPEERS | | minoutboundpeers | FIBERMINOUTBOUNDPEERS | | enablepeerreconnectbackoff | FIBERENABLEPEERRECONNECTBACKOFF | | gossipstoremaintenanceintervalms | FIBERGOSSIPSTOREMAINTENANCEINTERVALMS | | gossipnetworknumtargetedactivesyncingpeers | FIBERGOSSIPNETWORKNUMTARGETEDACTIVESYNCINGPEERS | | gossipnetworknumtargetedoutboundpassivesyncingpeers | FIBERGOSSIPNETWORKNUMTARGETEDOUTBOUNDPASSIVESYNCINGPEERS | | syncnetworkgraph | FIBERSYNCNETWORKGRAPH | | watchtowercheckintervalseconds | FIBERWATCHTOWERCHECKINTERVALSECONDS | | standalonewatchtowerrpcurl | FIBERSTANDALONEWATCHTOWERRPCURL | | standalonewatchtowertoken | FIBERSTANDALONEWATCHTOWERTOKEN | | disablebuiltinwatchtower | FIBERDISABLEBUILTINWATCHTOWER | | tobeacceptedchannelsnumberlimit | FIBERTOBEACCEPTEDCHANNELSNUMBERLIMIT | | tobeacceptedchannelsbyteslimit | FIBERTOBEACCEPTEDCHANNELSBYTESSLIMIT | | pendingchannelsnumberlimit | FIBERPENDINGCHANNELSNUMBERLIMIT | | fundingtimeoutseconds | FIBERFUNDINGTIMEOUTSECONDS | | externalfundingtimeoutseconds | FIBEREXTERNALFUNDINGTIMEOUTSECONDS | | reuseportforwebsocket | FIBERREUSEPORTFORWEBSOCKET | | metricsaddr | FIBERMETRICSADDR | FIBERTOBEACCEPTEDCHANNELSBYTESSLIMIT keeps the current source spelling of the environment variable. The YAML field and CLI flag use bytes. ### RPC | Field | Environment variable | |-------|----------------------| | listeningaddr | RPCLISTENINGADDR | | biscuitpublickey | RPCBISCUITPUBLICKEY | | enabledmodules | RPCENABLEDMODULES | | corsenabled | RPCCORSENABLED | | corsallowedorigins | RPCCORSALLOWEDORIGINS | ### CKB | Field | Environment variable | |-------|----------------------| | rpcurl | CKBNODERPCURL | | udtwhitelist | CKBUDTWHITELIST | | txtracingpollingintervalms | CKBTXTRACINGPOLLINGINTERVALMS | | fundingtxshellbuilder | FIBERFUNDINGTXSHELLBUILDER | ### CCH | Field | Environment variable | |-------|----------------------| | lndrpcurl | CCHLNDRPCURL | | lndcertpath | CCHLNDCERTPATH | | lndmacaroonpath | CCHLNDMACAROONPATH | | wrappedbtctypescriptargs | CCHWRAPPEDBTCTYPESCRIPTARGS | | orderexpirydeltaseconds | CCHORDEREXPIRYDELTASECONDS | | basefeesats | CCHBASEFEESATS | | feeratepermillionsats | CCHFEERATEPERMILLIONSATS | | maxoutgoingfeepercentage | CCHMAXOUTGOINGFEEPERCENTAGE | | btcfinaltlcexpirydeltablocks | CCHBTCFINALTLCEXPIRYDELTABLOCKS | | ckbfinaltlcexpirydeltaseconds | CCHCKBFINALTLCEXPIRYDELTASECONDS | | minoutgoinginvoiceexpirydeltaseconds | CCHMINOUTGOINGINVOICEEXPIRYDELTASECONDS | | fiberrpcurl | CCHFIBERRPCURL | | wrappedbtctypescript | CCHWRAPPEDBTCTYPE_SCRIPT | Special variable: FIBERSECRETKEYPASSWORD — Required to decrypt the CKB private key at startup. No supported direct environment variable exists for native service directories, fiber.proxy, fiber.onion, fiber.gossippolicy, or cch.ignorestartupfailure. ## Example: Minimal Testnet Config [yaml code block] > Tip: Use the config files from the Fiber v0.9.0 release as starting points. They include the matching script hashes, cell deps, UDT whitelist entries, and bootnode addresses for each network. --- ## Fiber Node Backup Source: https://www.fiber.world/docs/operate/backup ## TL;DR Fiber v0.9.0 creates online backups automatically under $BASEDIR/fiber/backups/. Enable the admin RPC module when you also need an immediate manual backup. To restore one of these backup directories, stop the node, run fnn --restore , and then start the node normally with the same FIBERSECRETKEYPASSWORD. ## What a v0.9.0 Backup Contains Each timestamped native-node backup contains: - db/ — a consistent RocksDB checkpoint of the Fiber store; - key — the encrypted CKB private-key file; and - sk — the Fiber network identity key. The backup does not contain config.yml or your FIBERSECRETKEYPASSWORD. Store those separately in an encrypted location. Anyone with both the encrypted key and its password can control the associated funds. ## Automatic Online Backups When the Fiber service is running, v0.9.0 schedules a backup every 24 hours. Important channel-state changes pull the next backup forward so it normally runs within 60 seconds. The default location is: [text code block] For example, a node started with -d ./node1 writes backups below ./node1/fiber/backups/. These are live database checkpoints, so the node does not need to stop while they are created. Check that backups are appearing and contain the database and both key files: [bash code block] The built-in backup directory is on the same disk as the live database. Copy completed timestamp directories to encrypted storage on another device or host so a disk failure does not destroy both copies. ## Trigger an Immediate Backup The admin RPC module is not enabled by default. Add it while retaining the standard modules you use: [yaml code block] Restart the node after changing the module list, then trigger a backup with either interface: [bash code block] [bash code block] A successful request returns null and creates a new timestamped directory. Keep the RPC listener private; if you expose it beyond the local machine, configure Biscuit authentication and grant administrative access only to a trusted operator. ## Create a Full Offline Archive The built-in backup covers the Fiber database and node keys. A stopped-node archive is still useful when you also want the exact configuration and other files from the data directory: [bash code block] Store node1.tar.gz and the password separately. Restore this form of backup by extracting it and starting Fiber with the same base directory: [bash code block] ## Restore a v0.9.0 Online Backup Stop the node before restoring. Pass the timestamped directory itself—not its db subdirectory—to --restore. Restore into the stopped node's existing, initialized base directory. The v0.9.0 native binary cannot restore into an empty base directory, and its read-only fiber/sk file prevents an in-place restore unless you make that file writable first. The restored key returns to read-only mode. [bash code block] The restore command restores the database and the backed-up key and sk files, then exits. Start the node normally afterward: [bash code block] The restore process preserves the replaced database as a timestamped safety backup. Channels that could carry penalty risk are marked Stale; Fiber must passively audit them with their peers before normal operation resumes. Keep the node online and connected to those peers, and do not assume a restored channel is spendable merely because it exists in listchannels. ## Cross-Version Restore ### From v0.8.x to v0.9.0 For a complete v0.8.x data-directory backup, extract it and start the v0.9.0 binary. Fiber detects the older database and runs the supported migration on startup after confirmation. [bash code block] ### From v0.7.x or Older to v0.9.0 Databases older than version 20260302100001 cannot migrate directly with the v0.9.0 fnn binary. First use the v0.8.x fnn-migrate tool to upgrade the database, then start v0.9.0: [bash code block] Storage migration has been integrated into fnn since v0.9.0-rc1. The archived v0.8.x fnn-migrate binary is needed only for databases created before v0.8.x. --- ## Connect Public Nodes Source: https://www.fiber.world/docs/operate/connect-nodes ## What Are Public Nodes? Public nodes are reachable Fiber nodes operated for Mainnet or Testnet connectivity. You can connect to them as peers so your node can join the public Fiber network, discover graph data, and prepare for channel operations. Connecting to a public node only creates a peer connection. It does not open a channel or send payments by itself. ## Public Node Information ### Mainnet | Node | Pubkey | WSS Address | |------|--------|-------------| | node1 | 03a8d7da8d0934363dbc17f52c872e8d833016415266eabb3527439c5dd17adc6b | /dns4/ca.fiber.channel/tcp/443/wss/p2p/QmZCfzENZqWrWwifJj9BFDvxQWFyYw5GjdB4vN7Ynd4FxY | | node2 | 033a69e5be369dab43aefa96fa729d83c571ccb066f312136c6ab2d354fcc028f9 | /dns4/tokyo.fiber.channel/tcp/443/wss/p2p/QmZ73KHvZ5GFxf6XhHZ3icPeKFo93rk86kZ8qauox3avJP | ### Testnet | Node | Pubkey | WSS Address | |------|--------|-------------| | node1 | 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 | /dns4/bottle.fiber.channel/tcp/443/wss/p2p/QmXen3eUHhywmutEzydCsW4hXBoeVmdET2FJvMX69XJ1Eo | | node2 | 0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc | /dns4/bracer.fiber.channel/tcp/443/wss/p2p/QmbKyzq9qUmymW2Gi8Zq7kKVpPiNA1XUJ6uMvsUC4F3p89 | ## How to Connect Use fnn-cli peer connectpeer with the public node pubkey: [bash code block] For example, to connect to Testnet node1: [bash code block] You can also call the connectpeer RPC directly: [json code block] After connecting, check the peer list: [bash code block] Example response: [json code block] If the node cannot resolve the pubkey yet, wait for graph data to sync or connect with a known multiaddr when one is available. ### WebSocket Connections Native Fiber nodes usually connect by pubkey after graph sync, or by a known TCP multiaddr. WASM nodes running in a browser through the fiber-js package should use a public node WebSocket or WebSocket Secure multiaddr when one is available: [text code block] When connecting by pubkey, connectpeer can also filter resolved addresses with addrtype. Native builds default to tcp, while wasm builds default to ws and wss. ## References - connectpeer - listpeers - Network Resources --- ## Channel Lifecycle Source: https://www.fiber.world/docs/concept/channels/channel-lifecycle ## Overview A Fiber channel undergoes several states: NegotiatingFunding, CollaboratingFundingTx, SigningCommitment, AwaitingTxSignatures, AwaitingChannelReady, ChannelReady, ShuttingDown, and Closed. After restoring a backup, a channel may temporarily appear as Stale. RPC responses report the current state through the ChannelState enum. The following sections show the complete Fiber channel lifecycle in its actual state order. ## NegotiatingFunding NegotiatingFunding means the peers are agreeing on the parameters required before funding, such as funding amount, fee rates, TLC limits, and related channel settings. | Flag | Description | |------|-------------| | OURINITSENT | The local node has sent its channel-opening init message. | | THEIRINITSENT | The remote peer has sent its channel-opening init message. | | INITSENT | Both local and remote channel-opening init messages have been sent. | | AWAITINGEXTERNALFUNDING | The node is waiting for the user to sign and submit an externally funded transaction. | ## CollaboratingFundingTx CollaboratingFundingTx means the peers are building the funding transaction together. | Flag | Description | |------|-------------| | AWAITINGREMOTETXCOLLABORATIONMSG | The node is waiting for the peer's transaction-collaboration message. | | PREPARINGLOCALTXCOLLABORATIONMSG | The node is preparing its own transaction-collaboration message. | | OURTXCOMPLETESENT | The local node has sent txcomplete. | | THEIRTXCOMPLETESENT | The remote peer has sent txcomplete. | | COLLABORATIONCOMPLETED | Both sides have sent txcomplete. | ## SigningCommitment SigningCommitment means the funding transaction structure is ready and the peers are exchanging CommitmentSigned messages. | Flag | Description | |------|-------------| | OURCOMMITMENTSIGNEDSENT | The local node has sent CommitmentSigned. | | THEIRCOMMITMENTSIGNEDSENT | The remote peer has sent CommitmentSigned. | | COMMITMENTSIGNEDSENT | Both sides have sent CommitmentSigned. | ## AwaitingTxSignatures AwaitingTxSignatures means the peers have exchanged commitment signatures and are now collecting funding transaction signatures through TxSignatures messages. | Flag | Description | |------|-------------| | OURTXSIGNATURESSENT | The local node has sent funding transaction signatures. | | THEIRTXSIGNATURESSENT | The remote peer has sent funding transaction signatures. | | TXSIGNATURESSENT | Both sides have sent funding transaction signatures. | ## AwaitingChannelReady AwaitingChannelReady means the funding transaction has been signed or broadcast, and the channel is waiting for on-chain confirmation and ChannelReady messages from both peers. | Flag | Description | |------|-------------| | OURCHANNELREADY | The local node has sent ChannelReady. | | THEIRCHANNELREADY | The remote peer has sent ChannelReady. | | CHANNELREADY | Both sides have sent ChannelReady. | ## ChannelReady ChannelReady means the channel is active and can send or receive payments. This state has no stateflags. ChannelReady does not guarantee payment reliability. A payment may still fail if there is not enough liquidity on the correct side, policy limits are not met, too many TLCs are already in flight, or no route can be found. ## Stale Stale means the restored channel data may be older than the peer's latest state. Fiber v0.9.0 marks channels that could carry penalty risk as Stale after a database restore and waits for a passive audit with the peer before resuming normal channel operations. This state has no stateflags. Keep the node online and connected to the channel peer. Do not send payments, force close, or otherwise treat the restored channel as current until the audit completes and its state advances. ## ShuttingDown ShuttingDown means the channel is closing. Channels can close cooperatively, when both peers agree on the close, or uncooperatively, when one side force closes the channel. For a cooperative close, provide closescript and feerate. For a force close, set force to true; for example, fnn-cli channel shutdownchannel --force true. In that case, Fiber uses the default close script and fee rate from channel opening. | Flag | Description | |------|-------------| | OURSHUTDOWNSENT | The local node has sent Shutdown. | | THEIRSHUTDOWNSENT | The remote peer has sent Shutdown. | | AWAITINGPENDINGTLCS | Both sides have sent Shutdown, and pending TLCs still need to clear. | | DROPPINGPENDING | The node is dropping pending updates while closing. | | WAITINGCOMMITMENTCONFIRMATION | The node is waiting for commitment confirmation during shutdown. | ## Closed Closed means the channel has reached a terminal state and can no longer be used for payments. | Flag | Description | |------|-------------| | COOPERATIVE | The channel closed cooperatively. | | UNCOOPERATIVELOCAL | The local node closed the channel uncooperatively. | | UNCOOPERATIVEREMOTE | The remote peer closed the channel uncooperatively. | | ABANDONED | The channel was abandoned. | | FUNDINGABORTED | The channel opening failed before funding completed. | | WAITINGONCHAIN_SETTLEMENT | The channel is already in Closed, but the on-chain settlement is still pending. | ## Checking Channel State ### Using fnn-cli [bash code block] ### Using RPC [json code block] ## Related Topics - P2P Message Protocol - External Funding - Channel API Reference --- ## Unidirectional Channel Source: https://www.fiber.world/docs/concept/channels/unidirectional-channel ## TL;DR Set one_way: true when opening a channel to restrict payment flow to one direction: from initiator to acceptor. One-way channels are always private and cannot route third-party payments. When the channel closes, any unspent funds are returned to the initiator. [bash code block] ## What Is a Unidirectional Channel A unidirectional channel (also called a one-way channel) is a payment channel where funds can only flow in a single direction — from the channel initiator to the acceptor. Unlike a standard bidirectional channel, the acceptor cannot send payments back through the same channel. Think of it like a prepaid card: you load money onto it, and the merchant can only receive from it, never push money back. If you don't spend everything, you get the remainder back when you close the channel. Under the hood, the channel's TLC (Time-Locked Contract) logic restricts the initiator to be the only sender and the acceptor to be the only receiver. ## How It Works In a unidirectional channel: 1. The initiator opens the channel and provides all the funding 2. The acceptor does not contribute any funds 3. The initiator sends payments to the acceptor through off-chain TLC updates 4. The acceptor cannot send payments back — any attempt to do so will fail at the routing level with a "Failed to build route" error 5. When the channel closes, the final balance is settled on-chain The direction restriction is enforced at the routing level. The node will never find a valid path for a reverse payment, so such payments are rejected before they even leave the sender. One-way channels cannot be public. If you set both public: true and one_way: true, the node will reject the request with "An one-way channel cannot be public". This also means one-way channels cannot participate in routing payments for other nodes. ## Comparison with Bidirectional Channels | Property | Unidirectional | Bidirectional | |---|---|---| | Funding | Only initiator funds | Both parties can fund | | Payment direction | Initiator → Acceptor only | Both directions | | Public visibility | Always private | Can be public or private | | Routing for others | Cannot route third-party payments | Can route payments if public | | Channel rebalancing | Not applicable | Supported via circular payments | | Typical use case | Streaming, merchant receiving | General P2P payments | | Channel reserve | ≥99 CKB (initiator side, default) | ≥99 CKB per side (default) | ## Use Cases and Trade-offs ### Good Fit Streaming payments. A subscriber pays a content provider continuously — for example, paying per second of video watched or per API call made. The subscriber (initiator) funds the channel and streams payments to the provider (acceptor). Since payments only go one way, a unidirectional channel is a natural fit and avoids the complexity of managing liquidity in both directions. Merchant receiving. A merchant opens a channel with a specific customer or partner so the customer can make repeated purchases without on-chain transactions each time. The customer is the initiator; the merchant is the acceptor. IoT and machine-to-machine payments. A device that continuously pays for a service (compute, data, bandwidth) benefits from the simplified model — the device opens a one-way channel and drips payments as the service is consumed. ### When to Use Bidirectional Instead If you expect payments to flow in both directions — for example, two peers who may trade roles as payer and payee — use a standard bidirectional channel. A one-way channel cannot be "upgraded" to bidirectional after creation; you would need to close it and open a new one. ### Limitations - No reverse payments. If the acceptor needs to pay the initiator, they must open a separate channel. - No routing participation. One-way channels are invisible to the network graph, so they cannot earn routing fees or help relay payments for others. - No rebalancing. Since only one side holds funds, there is no liquidity rebalancing to manage. ## Opening a Unidirectional Channel Set oneway: true when calling openchannel. The parameter is optional and defaults to false. ### Using fnn-cli [bash code block] ### Using JSON-RPC [json code block] The funding_amount is in shannons (1 CKB = 10⁸ shannons). By default, 99 CKB is reserved for the channel reserve (98 CKB minimum cell capacity + 1 CKB default shutdown fee), so the available balance for payments is fundingamount - 9900000000 shannons. The FIBERAUTOACCEPTCHANNELCKBFUNDINGAMOUNT environment variable controls the auto-accept funding contribution (the amount the acceptor automatically contributes when accepting a channel open request), not the channel reserve itself. ## Sending Payments Once the channel is in ChannelReady state, the initiator can send payments to the acceptor using send_payment as usual. No special parameters are needed — the routing engine automatically knows the channel is one-way and only allows forward-direction payments. [json code block] If the acceptor tries to send a payment back through the same channel, the routing algorithm will fail because it cannot find a valid path: [code block] ## Closing and Fund Recovery Although funds flow in only one direction during the channel's lifetime, closing a one-way channel works exactly the same as closing a bidirectional channel. Any unspent balance is returned to the initiator. This is an important point that often causes confusion. A one-way channel restricts the payment direction during the channel's lifetime, but it does not mean the initiator forfeits unspent funds. When the channel closes — whether cooperatively or by force — the on-chain settlement distributes funds according to the current balance: - Initiator's remaining balance goes back to the initiator's CKB address - Acceptor's received balance goes to the acceptor's CKB address - The channel reserve (99 CKB by default) is released back to the initiator For example, if you open a one-way channel with 499 CKB and send 120 CKB worth of payments before closing: - You (initiator) receive back: 499 - 120 - fees = ~379 CKB (minus transaction fees) - The acceptor receives: 120 CKB Either party can initiate the close: [bash code block] Or via JSON-RPC: [json code block] For details on the cooperative and force-close flows, see Channel Lifecycle. ## Monitoring One-Way Channels When listing channels, the isoneway field indicates whether a channel is unidirectional: [bash code block] Response: [json code block] Combine isoneway with isacceptor to determine your role in the channel: | isoneway | isacceptor | Your role | |---|---|---| | true | false | You are the payer (TLC sender) | | true | true | You are the payee (TLC receiver) | | false | either | Standard bidirectional channel | ## Related Topics - Channel Lifecycle — full channel state transitions and closing flows - Channel Rebalancing — liquidity management in bidirectional channels - Payment Lifecycle — how TLC payments are processed --- ## External Funding Source: https://www.fiber.world/docs/concept/channels/external-funding ## What Is External Funding? External funding lets a Fiber node open a channel while an external wallet signs the funding transaction. The node negotiates the channel and builds the funding transaction, but the wallet provides the signatures for the CKB Cells it contributes. Use this flow when the funding Cells are controlled by a wallet or Lock Script outside the Fiber node. ## How It Works In an app integration, Fiber acts as the channel negotiator while the wallet remains the owner of the funding Cells. The flow is: 1. The app calls openchannelwithexternalfunding to ask the Fiber node to prepare an externally funded channel. 2. Fiber negotiates the channel and returns channelid plus unsignedfundingtx. 3. The app passes that exact transaction to the external wallet for signing. 4. The wallet signs the inputs it controls and returns the signed witnesses. 5. The app submits the signed transaction back to Fiber with submitsignedfundingtx. After the unsigned transaction is returned, the transaction structure is frozen. The app or wallet should only fill the corresponding witnesses; it should not rebuild the transaction or modify its structure. Do not rebuild the transaction or modify inputs, outputs, outputsdata, or celldeps after unsignedfundingtx is returned. ## Signing the Funding Transaction For Rust developers using secp256k1-sighash locks, Fiber includes a development-only signexternalfunding_tx implementation that shows the expected signing process. It resolves previous outputs, groups matching lock scripts, signs each script group, and writes the signature into WitnessArgs.lock. This helper only covers secp256k1 signing and should be treated as a development reference, not a production wallet integration. For JavaScript developers, see the pinned fiber-wallet example using @ckb-ccc/ccc. The example includes JSON-RPC to CCC transaction conversion, copying signed witnesses back to the original transaction, standard CCC signers, OmniLock witness preparation, and JoyID redirect signing. ## Related APIs - openchannelwithexternalfunding negotiates the channel and returns the unsigned funding transaction. - submitsignedfundingtx submits the signed funding transaction. - signexternalfundingtx is a development-only helper for secp256k1-sighash signing. --- ## Channel Rebalancing Source: https://www.fiber.world/docs/concept/channels/channel-rebalancing ## What Is Channel Rebalancing? Channel rebalancing is a way to move liquidity between your existing channels without opening or closing a channel. Fiber sends a circular payment that leaves through one channel and returns through another, so your total balance stays the same except for routing fees. This helps move local balance to the channel where you need more outbound liquidity. ## When to Rebalance Suppose a node has two channels: [text code block] Channel A has more local balance, while Channel B has more remote balance. If the node needs to send payments through Channel B, the low local balance on that channel may limit outbound payments. Rebalancing can shift liquidity from Channel A toward Channel B. A real-world case is a merchant that receives many customer payments through one public node, then later needs to send payouts through a different channel. Rebalancing can move liquidity into the payout channel without closing or reopening channels. ## How Fiber Handles Rebalancing Fiber supports two rebalancing methods: - Automatic rebalancing: use this when you want Fiber to find the circular route for you. It is simpler, but you cannot control which channels are used. - Manual rebalancing: use this when you want to target specific channels or hops. It gives more control, but you need to know the route. ## Automatic Rebalancing Use sendpayment with targetpubkey set to your own node pubkey, keysend: true, and allowselfpayment: true. Fiber will try to find a circular path automatically. [json code block] This method is easiest to use, but the routing algorithm chooses the path. allowselfpayment is not compatible with trampoline routing. ## Manual Rebalancing Use buildrouter and sendpaymentwithrouter when you want to control the exact circular route. First, build a route that starts from your node, passes through the peers you want to use, and returns to your own node: [json code block] buildrouter returns routerhops, which includes the route, fees, and expiry deltas. Then send the payment with the returned router: [json code block] To force a specific channel at a hop, include channeloutpoint in hopsinfo. ## Tips - Use dryrun: true first to check whether the route can be built and how much fee it will cost. - Make sure the rebalance amount plus routing fees does not exceed your local balance in the outbound channel. - Use maxfeeamount with sendpayment to cap the total routing fee. - Use listchannels after a successful rebalance to confirm the updated balances. ## Related APIs - sendpayment - buildrouter - sendpaymentwithrouter - list_channels --- ## Payment Lifecycle Source: https://www.fiber.world/docs/concept/payments/payment-lifecycle ## TL;DR Every payment goes through Created → Inflight → Success or Failed. A payment may be split into multiple attempts across different routes (multi-path). Failed attempts can be retried automatically. Funds are only released when the recipient reveals the preimage — otherwise they return to the sender. Every payment in the Fiber network goes through a well-defined lifecycle — from the moment you call send_payment to the instant the recipient receives funds. Understanding this lifecycle is essential for debugging payment issues, building applications on Fiber, and reasoning about the security guarantees that time-locked contracts provide. Fiber uses a three-layer architecture: a high-level PaymentSession represents your payment intent, one or more Attempts handle routing and retry logic, and TLCs (Time-Locked Contracts) enforce conditional transfers on each channel along the route. This decoupled design enables multi-path payments — a single payment can be split across multiple routes — and ensures that funds are only released when cryptographic conditions are met. ## State Machine Overview Fiber's payment system uses two independent state machines: PaymentSession (one per payment) and Attempt (one or more per session). The session status is derived from the aggregate state of its attempts. ### Session Lifecycle [mermaid code block] A session is Created when the payment request is accepted, becomes Inflight as soon as the first TLC is dispatched, and reaches a terminal state (Success or Failed) only when all attempts have resolved. ### Attempt Lifecycle [mermaid code block] Each attempt routes a portion of the payment through a specific path. If it fails with a retryable error (e.g., a channel temporarily lacks liquidity), it transitions to Retrying and is re-dispatched with a new route. Terminal errors (e.g., invoice expired, permanent channel failure) move it directly to Failed. How the two layers connect: When a session is Created, the payment module splits the total amount into one or more attempts (routes). As each attempt reaches Success, its settled amount is added to the session's running total. The session transitions to Success when the sum of all successful attempts meets or exceeds the requested amount. If all attempts end up Failed and the retry limit is exhausted, the session becomes Failed. ## PaymentSession States A PaymentSession represents the user's payment intent — "I want to send X amount to node Y." It tracks the overall progress across all routing attempts and is persisted to the store for the lifetime of the payment. ### Created The initial state when a payment request is submitted via send_payment. The system has accepted the payment intent but has not yet dispatched any TLCs. What happens here: The payment module validates the invoice (signature, expiry, amount), computes the maximum fee budget (default: 0.5% of the amount), checks available outbound channel liquidity, and invokes the path-finding algorithm. If multi-path payment (MPP) is enabled, the total amount is split into multiple attempts, each targeting a different route. Transition: Moves to Inflight as soon as the first AddTlc is sent to the first-hop peer. ### Inflight One or more routing attempts are actively being processed. Funds are locked in TLCs on each channel along the route, and onion packets are being forwarded hop by hop. What happens here: - Each attempt adds a TLC to the sender's first-hop channel and dispatches an onion packet - Intermediate nodes peel the onion, validate fees and expiry, then forward a new TLC to the next hop - As attempts settle (partially or fully), the settled amount accumulates on the session - The session remains Inflight until either the full amount is collected or all attempts have been exhausted Key transitions from Inflight: - → Success: When the sum of settled amounts across all successful attempts equals or exceeds the total payment amount - → Failed: When all attempts have failed and the retry limit has been reached, or a terminal error occurs ### Success The payment has been completed. The full amount has been delivered to the recipient and the preimage has been revealed. What this means: The recipient's node revealed the preimage (proof of payment) by including it in a RemoveTlcFulfill message. This preimage propagates backward through each hop, settling each TLC in sequence. All intermediate nodes have been paid their forwarding fees. The preimage is stored in the session and can be used as cryptographic proof of payment. ### Failed The payment could not be completed. All routing attempts have been exhausted and no retryable paths remain. Common failure reasons: | Error Code | Meaning | |------------|---------| | PermanentChannelFailure | A channel on the route is closed or does not exist | | ChannelDisabled | A channel on the route has been disabled by its operator | | UnknownNextPeer | The next node in the route is not connected | | PermanentNodeFailure | A node on the route is permanently unreachable | | IncorrectPaymentDetails | The invoice amount, hash, or other details do not match | | InvoiceExpired | The invoice has passed its expiry time | | InsufficientBalance | The sender's channels do not have enough local balance | | TemporaryChannelFailure | A channel temporarily lacks liquidity (retryable) | The failed_error field on the session provides the most recent failure reason from the RPC response. ## Attempt States An Attempt represents a single routing attempt for a portion of the payment amount. A PaymentSession can have one or more attempts (for multi-path payments). Each attempt carries its own route, retry counter, and status. ### Created The attempt has been initialized with a specific amount, route, and onion packet, but the first-hop AddTlc has not yet been sent. ### Inflight The first-hop AddTlc has been sent successfully and the TLC is propagating through the network. Each intermediate node is processing the onion packet and forwarding to the next hop. ### Success The attempt has been successfully completed. The recipient (or an intermediate node) revealed the preimage via RemoveTlcFulfill, and the funds have been settled through the route. The settled amount is credited toward the parent PaymentSession. ### Retrying The attempt failed, but the failure is retryable and the retry limit has not been reached. The system will automatically re-route and resend the attempt, potentially choosing a different path. When retrying happens: An intermediate node was temporarily unavailable, a channel lacked sufficient liquidity at that moment, or a transient network error occurred. The system updates the network graph to mark failed channels or nodes before retrying, increasing the chance of finding a working path. Retry limits: For single-path payments, the default retry limit is 5 attempts. For MPP payments, each part allows up to 3 retries, with the total capped at max_parts × 3. The retry delay uses an adaptive backoff (20 ms × pending retry count) to avoid thundering-herd effects. ### Failed The attempt has permanently failed. Either the error is not retryable (e.g., IncorrectPaymentDetails, InvoiceExpired), or the retry limit has been reached. ## How a Payment Flows To understand the full picture, here is what happens step by step when you call send_payment: 1. Validation: The invoice is parsed and validated (signature, expiry, amount match). The payment parameters are resolved — target pubkey, amount, max fee, hash algorithm, and optional trampoline hops. 2. Route building: The graph module runs a Dijkstra-like search backwards from the target to the source, using a probability-weighted cost model inspired by LND's bimodal routing. Each edge is scored based on historical success/failure data and channel capacity. For MPP, this step iterates to build multiple routes that collectively cover the total amount. 3. Onion construction: For each attempt, a Sphinx onion packet is constructed. The packet contains a per-hop payload (amount to forward, expiry, fee) encrypted for each node along the route. Only the intended recipient can decrypt the final payload; intermediate nodes see only their own instructions. 4. TLC dispatch: The first AddTlc is sent to the sender's first-hop peer, carrying the payment hash, amount, expiry, and the onion packet. The peer adds the TLC to their channel state. 5. Forwarding: When the first-hop peer commits the TLC (via CommitmentSigned / RevokeAndAck), it peels the onion packet. If it is not the final hop, it validates the fee and expiry, then creates a new outbound AddTlc on the next channel — linking the inbound and outbound TLCs via a forwarding_tlc reference. This process repeats at each hop. 6. Settlement: The final hop recognizes itself as the recipient (the onion indicates is_last). If the payment hash matches a known invoice, it reveals the preimage by sending RemoveTlcFulfill back to the previous hop. Each intermediate node then fulfills its upstream TLC using the same preimage, releasing funds hop by hop. 7. Session update: As each attempt settles, the PaymentSession recalculates its status. When the sum of successful attempt amounts reaches the requested total, the session transitions to Success. ## TLC State Machine Under the hood, each TLC on a channel goes through its own state machine as the two channel peers coordinate via CommitmentSigned and RevokeAndAck messages. There are separate state tracks for outbound TLCs (those you offered) and inbound TLCs (those you received). ### Outbound TLC [code block] - LocalAnnounced: You sent AddTlc to your peer, waiting for commitment - Committed: The TLC is locked into both parties' commitment transactions - RemoteRemoved: The peer resolved the TLC (fulfilled or failed) - RemoveWaitAck: Waiting for the final RevokeAndAck to confirm removal - RemoveAckConfirmed: The TLC has been safely removed from channel state ### Inbound TLC [code block] - RemoteAnnounced: Received AddTlc from peer, not yet committed - AnnounceWaitAck: Sent CommitmentSigned, waiting for peer's ACK - Committed: The TLC is locked into both parties' commitment transactions - LocalRemoved: This node resolved the TLC (fulfilled or failed) - RemoveAckConfirmed: The TLC has been safely removed from channel state The CommitmentSigned / RevokeAndAck exchange is the backbone of channel state updates. Every balance change — including TLC additions, removals, and fee updates — requires this two-step handshake. The revocation mechanism ensures that if a party submits an outdated commitment transaction on-chain, the other party can claim all channel funds as a penalty. ## Onion Routing Fiber uses Sphinx onion routing to preserve payment privacy. Each hop along a route can only see its immediate predecessor and successor — it cannot determine the full path, the sender, or the recipient (unless it is the first or last hop). ### How It Works When the sender constructs a payment, the route is encoded as a series of per-hop payloads: | Field | Description | |-------|-------------| | amounttoforward | How much this hop should forward to the next | | outgoingchannelid | The channel to forward on | | outgoingtlcexpiry | The expiry for the next hop's TLC | | fee | Forwarding fee this hop earns | These payloads are wrapped in layers of encryption — like an onion. The sender encrypts the payload for the last hop first, then wraps it for the second-to-last, and so on. Each node peels one layer using its shared secret (derived from ECDH with the sender's ephemeral session key) and forwards the remaining encrypted layers to the next hop. ### Error Propagation When an error occurs at any hop, the failing node creates a TlcErrPacket encrypted with its shared secret. This error packet is passed backward through the route — each intermediate hop re-encrypts it using its own shared secret. When the error reaches the sender, the sender decrypts it layer by layer (using all shared secrets in reverse order) to identify the failing hop and the error code. The error packet always undergoes exactly 27 decryption passes to prevent timing-based analysis of the failure location. ### Trampoline Routing For payments to recipients that are not well-connected in the network graph, Fiber supports trampoline routing. The sender delegates route-finding to one or more intermediate "trampoline" nodes: 1. The sender finds a path only to the first trampoline node 2. A nested trampoline onion packet (embedded in the payment onion's custom records) encodes the trampoline hops and their instructions 3. Each trampoline node peels its layer, performs its own route-finding to the next trampoline node (or the final recipient), and forwards the payment Trampoline routing is especially useful for mobile or light nodes that do not maintain a full network graph. The maximum number of trampoline hops is 5. MPP is only allowed with a single trampoline hop; multiple trampoline hops force single-path behavior. ## Multi-Path Payments Fiber supports Atomic Multi-path Payments (AMP), which split a large payment into smaller parts routed through different channels. This is critical for large payments when no single path has enough liquidity. ### How MPP Works 1. The PaymentSession in Created state computes how to split the total amount 2. The graph module iteratively finds routes — each call accounts for already-committed capacity on shared channels (via GraphChannelStat), preventing over-allocation 3. Each route becomes a separate Attempt with its own amount, path, and onion packet 4. A paymentsecret is included in the invoice and embedded in each attempt's custom records, so the recipient can correlate partial payments 5. As each attempt settles, the partial amounts accumulate on the session 6. When the sum of successful attempt amounts reaches the total, the session transitions to Success ### MPP Eligibility MPP requires three conditions: - The invoice has allowmpp set to true - maxparts is greater than 1 (default: 12) - The payment is not a keysend (keysend payments are always single-path) MPP is only allowed with a single trampoline hop. When multiple trampoline hops are specified, the payment is forced into single-path mode (maxparts is effectively 1). ## Payment Fees Every intermediate node along a payment route charges a forwarding fee. Understanding fees is important for cost estimation and for setting appropriate fee limits. ### Fee Calculation The forwarding fee for a single hop is computed as: [code block] Each node configures its own fee rate via tlcfeeproportionalmillionths. A value of 1000 corresponds to a 0.1% fee. ### Maximum Fee Budget When you call sendpayment, a maximum fee budget is computed automatically: [code block] The default maxfeerate is 5 (per thousand), meaning the sender allows up to 0.5% of the payment amount in total fees. You can override this with maxfeeamount or maxfeerate in the sendpayment parameters. The path-finding algorithm respects this budget — it will not select routes whose cumulative fees exceed the maximum. The session's feepaid method reports the actual total fees after settlement. ### Route-Level Fees The fee for a specific route is the difference between the amount locked at the first hop and the amount received at the final hop: [code block] For MPP payments, the total fee is the sum of all successful attempt route fees. ## Monitoring Payment Status ### Using fnn-cli [bash code block] ### Using RPC [json code block] ### Interpreting the Response The payment response includes: | Field | Description | |-------|-------------| | status | Session status: Created, Inflight, Success, or Failed | | amount | The total target amount | | feepaid | Total routing fees paid across all successful attempts | | failederror | The most recent error message (if failed) | If status is Inflight, the payment is still being processed — likely waiting for TLC propagation or retry. Payments in Inflight for an extended period may indicate a stuck route; the system's periodic status check will eventually time out unresponsive attempts. ## Common Issues | Symptom | Status | Likely Cause | Solution | |---------|--------|--------------|----------| | Payment stuck | Inflight (long time) | A hop is offline or unresponsive | Wait for TLC expiry timeout; the system will fail and retry the attempt | | Payment failed immediately | Failed | No route with sufficient liquidity | Open a channel to a well-connected node, or try MPP | | Partial payment delivered | Inflight (no progress) | Some attempts failed, remaining are retrying | Wait for retries or check failederror for the failure reason | | Fee too high | Failed | Route fees exceed maxfeeamount | Increase maxfeeamount or maxfee_rate, or use a shorter route | | Wrong amount | Failed | Invoice amount mismatch | Verify the invoice amount and payment hash before paying | ## Related Topics - Channel Lifecycle — how channels support payment operations - Invoice — creating and paying invoices - Multi-Hop Routing — how the routing graph and path-finding work - Trampoline Routing — delegated route-finding for light nodes - Hold Invoice — deferred settlement for conditional payments --- ## Invoice Source: https://www.fiber.world/docs/concept/payments/invoice-guide ## TL;DR - Create an invoice: fnn-cli invoice newinvoice --amount 1000 --currency fibt - Pay an invoice: fnn-cli payment sendpayment --invoice "fibt1..." - Check invoice status: fnn-cli invoice get_invoice --payment-hash 0x... - Invoices use bech32m encoding with network-specific prefixes (fibb, fibt, fibd) - Invoice states: Open → Received → Paid, or → Cancelled / Expired An invoice is a payment request generated by a recipient in the Fiber network. It encodes everything a sender needs to make a payment — the amount, the recipient's identity, cryptographic proof of payment, and optional metadata such as a description or expiry time. Think of an invoice as a "payment QR code." The recipient generates one, and the sender scans it (or copies the encoded string) to initiate a payment through send_payment. ## Anatomy of an Invoice Every Fiber invoice carries the following core fields: | Field | Required | Description | |-------|----------|-------------| | Currency | Yes | Network identifier: fibb (mainnet), fibt (testnet), or fibd (devnet) | | Amount | No | Payment amount in shannons (1 CKB = 10⁸ shannons). Omit for open-ended invoices (e.g., donations) | | Payment hash | Yes | A 32-byte cryptographic commitment. For regular invoices it's derived from a random preimage; for hold invoices, the recipient provides the preimage and the hash is computed from it | | Timestamp | Yes | Invoice creation time in milliseconds since the UNIX epoch | | Signature | Yes | A secp256k1 recoverable signature over the invoice data, proving the invoice was created by the holder of the payee's private key | In addition, invoices can carry optional attributes: | Attribute | Description | |-----------|-------------| | Description | A human-readable note, up to 639 characters (e.g., "Coffee") | | Expiry time | How long the invoice remains valid, in seconds from the timestamp | | Payee public key | The recipient's compressed secp256k1 public key (33 bytes). Enables signature verification without external lookup | | Fallback address | A CKB on-chain address to fall back to if the off-chain payment fails | | UDT type script | A CKB type script identifying a User Defined Token, for non-CKB asset payments | | Final expiry delta | Minimum TLC (Time-Locked Contract) expiry delta for the final hop, in milliseconds (min ~160 minutes, max 14 days) | | Hash algorithm | Hash function used for the payment hash: ckb_hash (blake2b-256, default) or sha256 | | Feature flags | Capability flags: multi-part payments (MPP), trampoline routing | | Payment secret | A 32-byte secret required for multi-part payments; auto-generated when MPP is enabled | ## Invoice Format Fiber invoices use bech32m encoding — the same checksum scheme used by modern Bitcoin addresses, but with a different data layout (not compatible with Lightning's BOLT 11). The design draws inspiration from BOLT 11 while adapting to the CKB ecosystem: invoice data is serialized using molecule (the standard serialization format in CKB projects), and cross-chain compatibility is handled through Fiber's built-in Cross-Chain Hub rather than through the invoice format itself. ### Human-Readable Part (HRP) The HRP combines a network prefix with an optional amount: | Prefix | Network | |--------|---------| | fibb | CKB mainnet ("fiber bytes", since 1 CKB = 1 Byte) | | fibt | CKB testnet | | fibd | CKB devnet | The amount is appended directly as a decimal number in shannons. For example, fibb1280 means mainnet, 1280 shannons. If no amount is specified, the HRP is just the prefix (e.g., fibb), indicating an open-ended invoice. ### Data Part The data part is encoded as base32 (u5) values and contains: 1. A flag byte indicating whether the invoice is signed 2. The invoice data, serialized and compressed (see below) 3. The signature (if signed): 104 u5 values encoding a 65-byte secp256k1 recoverable signature The maximum decompressed invoice data size is 16 KB. #### Data Fields The serialized invoice data contains the following fields: | # | Field | Required | Size | Description | |---|-------|----------|------|-------------| | 1 | timestamp | Yes | 128 bits | Milliseconds since the UNIX epoch | | 2 | paymenthash | Yes | 256 bits | Unique identifier of the invoice. Derived from blake2b256(preimage) for hold invoices, or randomly generated for AMP invoices | | 3 | expiry | No | 64 bits | Validity period in seconds; timestamp + expiry gives the expiration time | | 4 | description | No | Variable | UTF-8 text (e.g., "a cup of coffee"), max 639 characters | | 5 | finalexpirydelta | No | 64 bits | Final TLC expiry delta in milliseconds | | 6 | fallbackaddress | No | Variable | CKB on-chain address for fallback if payment fails | | 7 | feature | No | Variable | Feature flags (MPP, trampoline routing, etc.) | | 8 | payeepublickey | No | 33 bytes | Compressed secp256k1 public key of the payee | | 9 | udtscript | No | Variable | CKB type script for UDT token payments | | 10 | hashalgorithm | No | 1 byte | 0 = ckbhash (blake2b-256, default), 1 = sha256 | | 11 | payment_secret | No | 32 bytes | Secret required for multi-part payments | #### Encoding Pipeline Raw molecule-serialized bytes tend to contain consecutive zeros when optional fields are empty, making the bech32m output relatively long. To address this, Fiber compresses the molecule bytes using arithmetic coding (lossless), which roughly halves the encoded length: [code block] Decoding performs the inverse: bech32m decode, split signature from data, then arithmetic decompression, then molecule deserialization. #### Signature The signature is a 65-byte secp256k1 recoverable signature ([u8; 65] = 520 bits = 104 u5 values). It proves the invoice was generated by the holder of the payee's private key and can be used to verify integrity and correctness. The signing process: [code block] The signature is appended after the compressed data before the final bech32m encoding. An unsigned invoice (flag byte = 0) omits the signature entirely. ### Example [code block] ## Creating an Invoice ### Using fnn-cli Create a basic invoice with a specified amount: [bash code block] The amount is specified in shannons (1 CKB = 10⁸ shannons). The currency must match your node's network configuration. To create an invoice with an explicit expiry: [bash code block] The --expiry value is in seconds (86400 = 24 hours). ### Using RPC [json code block] All numeric fields in the RPC are hex-encoded. The amount is in shannons (0x3E8 = 1000), and expiry is in seconds (0xE10 = 3600, i.e. 1 hour). Full parameter reference: | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | amount | u128 (hex) | Yes | Invoice amount in shannons | | currency | Currency | Yes | Fibb (mainnet), Fibt (testnet), or Fibd (devnet) | | description | string | No | Human-readable description (max 639 chars) | | paymentpreimage | Hash256 | No | Settlement preimage. Mutually exclusive with paymenthash. If both are omitted, Fiber generates a random preimage and computes the payment hash using the specified hash algorithm (ckbhash by default) | | paymenthash | Hash256 | No | Payment hash only (creates a hold invoice). The caller supplies the hash; the preimage must be provided later via settleinvoice. Mutually exclusive with paymentpreimage | | expiry | u64 (hex) | No | Invoice validity period in seconds | | fallbackaddress | string | No | CKB on-chain fallback address | | finalexpirydelta | u64 (hex) | No | Final TLC expiry delta in milliseconds (min ~160 minutes, max 14 days). newinvoice defaults to the minimum; v0.9.0 treats an externally encoded invoice that omits the attribute as 24 hours. | | udttypescript | Script | No | CKB type script for UDT (non-CKB) assets | | hashalgorithm | HashAlgorithm | No | ckbhash (default) or sha256 | | allowmpp | bool | No | Enable multi-part payments | | allowtrampoline_routing | bool | No | Enable trampoline routing | Response: [json code block] The invoiceaddress is the bech32m-encoded string you share with the sender. The invoice object contains the full parsed invoice. ## Paying an Invoice Once you have an encoded invoice string, you can pay it using sendpayment. ### Using fnn-cli [bash code block] ### Using RPC [json code block] The payment module will validate the invoice, check for expiry, find a route through the network, and lock funds in TLCs along the path. For details on what happens after the payment is initiated, see Payment Lifecycle. ## Parsing an Invoice Before paying, you may want to inspect an invoice's contents — for example, to verify the amount or check the recipient's public key. ### Using fnn-cli [bash code block] ### Using RPC [json code block] This returns the full CkbInvoice object without paying it. Both signed and unsigned invoices can be parsed. ## Invoice States An invoice progresses through the following states: | State | Description | |-------|-------------| | Open | The invoice has been created and is waiting to be paid | | Received | A TLC matching the invoice's payment hash has arrived at the recipient's node, but the invoice has not yet been settled. This is the typical intermediate state for hold invoices | | Paid | The preimage has been revealed and the payment is fully settled. The recipient has received the funds | | Cancelled | The invoice was manually cancelled by the recipient via cancel_invoice | | Expired | The invoice has passed its expiry time (timestamp + expiry). This state is detected automatically | | Transition | Trigger | |------------|---------| | Open → Received | A TLC matching the invoice's payment hash arrives at the recipient's node | | Received → Paid | The preimage is revealed (via settle_invoice or automatic settlement) | | Received → Cancelled | The recipient calls cancel_invoice — held TLCs are rejected | | Received → Expired | Hold timeout fires before settle or cancel — held TLCs are released | | Open → Cancelled | The recipient calls cancel_invoice before any payment arrives | | Open → Expired | Current time exceeds timestamp + expiry (detected automatically) | For regular invoices, the status changes from Open directly to Paid almost instantly — skipping the Received state entirely. The Received state is typically only observable for hold invoices, where the TLC is held until the recipient explicitly calls settleinvoice. ## Checking Invoice Status Retrieve the current state of an invoice using its payment hash. ### Using fnn-cli [bash code block] ### Using RPC [json code block] The response includes the invoice address, the full invoice object, and the current status. If the invoice has passed its expiry time, the status is automatically reported as Expired even if it was previously Open. ## Cancelling an Invoice You can cancel an invoice that has not yet been paid. This is useful when a payment is no longer expected or when a hold invoice's condition was not met. ### Using fnn-cli [bash code block] ### Using RPC [json code block] You cannot cancel an invoice that has already been paid (Paid) or is already Cancelled. Cancelling a Received invoice will release any held TLCs back to the sender. ## Settling a Hold Invoice For hold invoices, settlement is a manual step. After the recipient has verified that the payment condition is met, they call settleinvoice with the preimage. [json code block] The node validates that hashalgorithm(preimage) == paymenthash, stores the preimage, and fulfills all held TLCs for this payment hash across all channels. Only invoices in the Received state can be settled. Always store the preimage securely when creating a hold invoice. If you lose the preimage, the funds remain locked until the invoice expires or the TLC times out. For the full hold invoice workflow — including creation, settlement, cancellation, and cross-chain swap usage — see Hold Invoice. ## User Defined Tokens (UDT) Fiber invoices support payments in assets other than native CKB by including a UDT type script — a CKB type script that identifies the token on-chain. To create a UDT invoice, pass the udttypescript parameter: [json code block] When a UDT type script is present, the amount refers to the UDT's base units rather than shannons. The sender's node must have a channel that supports the specified UDT to complete the payment. ## Advanced Features ### Multi-Path Payments (MPP) When allowmpp is set to true, the invoice signals that it supports receiving payments split across multiple routes. This is useful for large payments that exceed the capacity of any single channel path. [bash code block] When MPP is enabled, a paymentsecret is automatically generated and included in the invoice. The sender's payment module uses this secret to coordinate the partial payments. See Payment Lifecycle for details on how multi-path routing works, and Multi-Hop Payments for how payments traverse intermediate nodes. ### Trampoline Routing When allowtrampolinerouting is set to true, the invoice indicates the recipient supports trampoline routing — a routing optimization where the sender delegates part of the route-finding to intermediate "trampoline" nodes. See Trampoline Routing for a detailed explanation. [bash code block] ### Hash Algorithm By default, Fiber uses blake2b-256 (ckbhash) for payment hashes — the same hash function used throughout CKB. For cross-chain interoperability with networks that use SHA-256 (such as the Bitcoin Lightning Network), you can specify sha256: [bash code block] This is particularly relevant for cross-chain atomic swaps where both sides of the swap must use the same hash algorithm. ## Quick Reference | Action | RPC Method | fnn-cli | |--------|-----------|---------| | Create invoice | newinvoice | fnn-cli invoice newinvoice | | Parse invoice | parseinvoice | fnn-cli invoice parseinvoice | | Get invoice status | getinvoice | fnn-cli invoice getinvoice | | Cancel invoice | cancelinvoice | fnn-cli invoice cancelinvoice | | Settle hold invoice | settleinvoice | fnn-cli invoice settleinvoice | | Pay an invoice | sendpayment | fnn-cli payment send_payment | ## Related Topics - Hold Invoice — deferred settlement for conditional payments and atomic swaps - Multi-Hop Payments — how payments are routed through intermediate nodes - Trampoline Routing — delegating pathfinding to trampoline nodes for lightweight clients - Payment Lifecycle — how payments are routed and settled after an invoice is paid - Cross-Chain HTLC — how hold invoices enable cross-chain swaps between Fiber and Lightning --- ## Hold Invoice Source: https://www.fiber.world/docs/concept/payments/hold-invoice A hold invoice (also known as a HODL invoice) is a special type of invoice where the payment is accepted by the recipient's node but not immediately settled. Unlike a regular invoice — which is fulfilled the moment the incoming TLC arrives — a hold invoice keeps the payment in a held state until the recipient explicitly reveals the preimage or cancels it. This deferred-settlement mechanism is the building block for conditional payments on Fiber. ## TL;DR - A hold invoice locks the sender's funds in the network without letting the recipient claim them — settlement is deferred until the recipient reveals a preimage - Unlike a regular invoice (automatic settlement), the invoice enters a Received state where funds are committed but not yet transferred - The trust model depends on who holds the preimage: the recipient, a third-party escrow, or an automated system - Create with paymenthash only (no paymentpreimage); settle with settleinvoice; cancel with cancelinvoice - States: Open → Received → Paid, or → Cancelled / Expired [bash code block] ## When to Use Hold Invoices A regular invoice settles the instant the payment arrives — this is fine for straightforward transfers, but many real-world scenarios need the payment to be committed but not finalized until some external condition is verified. That is exactly what hold invoices provide: the sender's funds are locked in the network (so they cannot double-spend), yet the recipient cannot claim them until the agreed-upon condition is met. Typical situations where this matters: - Atomic swaps — You and a counterparty are trading assets across two chains (e.g., CKB and BTC). Each side creates a hold invoice with the same payment hash. Whoever settles first reveals the preimage, and the other party uses that same preimage to settle the other chain. Neither side can run away with both assets. - Digital goods delivery — A buyer pays upfront, but the seller should only receive funds after confirming the buyer got what they ordered. The payment sits in the Received state as proof of commitment; the seller calls settle_invoice once delivery is confirmed. - Escrow — A trusted third party holds the preimage. Funds are locked and visible to both sides, but release depends on the arbiter's judgment. - Cross-chain hub — Fiber's built-in cross-chain hub relies on hold invoices (on both the CKB and Lightning sides) to atomically swap between CKB and BTC. See Cross-Chain HTLC for details. If your use case is a simple transfer where the recipient should get paid immediately, use a regular invoice instead. ## How Hold Invoices Differ from Regular Invoices | | Regular Invoice | Hold Invoice | |---|---|---| | Creation | paymentpreimage is provided, or one is randomly generated | Only paymenthash is provided — no preimage is stored | | Settlement | Automatic — the TLC is fulfilled as soon as it arrives | Manual — the recipient must call settle_invoice with the preimage | | Status flow | Open → Paid | Open → Received → Paid (or Cancelled) | | Preimage | Known to the recipient from the start | Unknown to the node until the recipient reveals it | | Sender risk | Low — payment completes instantly | Funds are locked until the recipient settles or the invoice expires | | Use case | Everyday payments | Conditional payments, atomic swaps, escrow | ## Invoice States A hold invoice moves through a distinct set of states: | State | Meaning | |-------|---------| | Open | The invoice has been created but no payment has arrived yet | | Received | A TLC has arrived and is being held — the preimage has not been revealed | | Paid | The recipient revealed the preimage via settle_invoice and the TLCs have been fulfilled | | Cancelled | The recipient called cancel_invoice — held TLCs are rejected and funds return to the sender | | Expired | The invoice exceeded its expiry time without being settled | The Received state is unique to hold invoices. Regular invoices skip it entirely because the preimage is already available when the TLC arrives. ## How It Works The lifecycle of a hold invoice has four phases: 1. Creation — The recipient generates a preimage and computes its hash locally (e.g., paymenthash = sha256(preimage)). Only the paymenthash is passed to new_invoice; the preimage is kept secret. The invoice is stored with status Open and no preimage on the node. 2. Payment — The sender pays the invoice as usual via send_payment. The payment routes through the network and a TLC (Time-Locked Contract) arrives at the recipient's channel. 3. Hold — The recipient's node checks for a preimage. Since none is stored, it cannot fulfill the TLC immediately. Instead, the TLC is held and the invoice transitions to Received. The hold duration is bounded by the invoice's expiry time and the TLC's own expiry — whichever comes first. 4. Resolution — One of three things happens next: - Settlement: The recipient calls settleinvoice with the preimage. The node verifies it matches the paymenthash, stores it, and fulfills all held TLCs. The invoice transitions to Paid. - Cancellation: The recipient calls cancel_invoice. The invoice is marked Cancelled and all held TLCs are rejected with an InvoiceCancelled error, returning funds to the sender. - Timeout: If neither settle nor cancel happens before the hold expires, the held TLCs are released automatically. | Transition | Trigger | |------------|---------| | Open → Received | A TLC matching the payment hash arrives at the recipient's node, but no preimage is stored | | Received → Paid | The recipient calls settle_invoice with the matching preimage | | Received → Cancelled | The recipient calls cancel_invoice — held TLCs are rejected | | Received → Expired | Hold timeout fires before settle or cancel — held TLCs are released | | Open → Cancelled | The recipient calls cancel_invoice before any payment arrives | | Open → Expired | Current time exceeds timestamp + expiry without receiving a payment | ## Trust Model A hold invoice guarantees that funds are locked once the sender pays, but it does not by itself resolve real-world disputes — for example, a seller claims goods were shipped while the buyer claims they were not received. What a hold invoice provides is a cryptographic commitment layer; the question of who gets to decide when to release the funds is determined by who holds the preimage. ### Who Holds the Preimage? Recipient holds the preimage. The simplest model: the recipient decides when to settle. This works well when the sender trusts the recipient to act honestly — for instance, a buyer who will settle after confirming delivery. The risk is that a dishonest recipient could settle without fulfilling their side of the deal. Sender holds the preimage. The sender pays the hold invoice and later reveals the preimage to let the recipient claim the funds. This is equivalent to "I'll pay you after I'm satisfied." The risk is reversed: the sender could simply never reveal the preimage, leaving the recipient unpaid even after delivering goods. Third-party escrow. A trusted arbitrator holds the preimage. Neither the sender nor the recipient can unilaterally settle or cancel — only the escrow agent can. When both parties agree, the agent settles; when there is a dispute, the agent reviews evidence and decides whether to settle or cancel. This is the closest analogue to traditional escrow services. Automated / programmatic release. The preimage is released by an external system when a verifiable condition is met — for example, an on-chain oracle confirming a transaction, or a smart contract callback. Fiber's cross-chain hub uses this model: the preimage is automatically obtained from the outgoing payment on the other chain, so there is no human judgment involved and no opportunity for either party to cheat. ### Why Not Just Delay sendpayment? A common question is: why not use a regular invoice and simply call sendpayment only after the condition is met (e.g., after receiving the goods)? The difference is commitment. With a regular invoice, the buyer has made no commitment at all — they could walk away at any time, and the seller has no proof that the buyer ever intended to pay. The seller ships goods entirely on trust. With a hold invoice, the buyer has already called sendpayment and the funds are locked in the network. The seller can observe the Received state as verifiable proof that funds are committed and waiting. The buyer cannot double-spend or withdraw the locked funds. Both parties are protected: the seller knows the money is there, and the buyer knows the seller cannot claim it without fulfilling the condition. ### Why Not Use a Platform with Regular Invoices? Another common approach is to have a centralized platform mediate: the buyer pays a regular invoice (funds settle instantly into the platform's account), and the platform releases goods only after confirming payment. This works, but it requires the buyer to fully trust the platform — once the payment settles, the funds belong to the platform, and the buyer has no on-chain mechanism to get them back if the platform fails to deliver, goes offline, or acts maliciously. With a hold invoice, the funds never reach the recipient's account until settleinvoice is called. Before that point, the money is locked in the network — not in anyone's wallet. If the platform fails to deliver, it must call cancel_invoice to return the locked funds (or they are returned automatically on expiry). The buyer does not need to trust the platform to refund from its own balance; the refund path is enforced by the protocol itself. | | Regular invoice + platform | Hold invoice | |---|---|---| | Funds after payment | In the platform's account | Locked in the network, not claimable by anyone | | Buyer protection | Relies on the platform's refund policy | Enforced by the protocol — funds cannot be claimed without settlement | | Refund mechanism | Platform sends a new payment from its own balance | cancel_invoice releases the original locked funds automatically | | Trust assumption | Buyer trusts the platform | Neither party needs to trust the other | In short, hold invoices separate the cryptographic commitment (funds locked, preimage required to unlock) from the business logic (who decides when the condition is met). The commitment layer is universal; the trust model is built on top by your application. ## Creating a Hold Invoice To create a hold invoice, first generate a preimage and compute its hash on your side. Then pass only the hash to newinvoice. ### Generate a Preimage and Hash [bash code block] Store the preimage securely before creating the invoice. You will need it to settle later. If you lose the preimage, the funds remain locked until the invoice expires. ### Using fnn-cli [bash code block] ### Using RPC [json code block] Note that paymentpreimage is intentionally omitted. If you provide paymentpreimage instead of paymenthash, the node creates a regular invoice — the preimage is stored and TLCs are fulfilled automatically. If you provide neither, the node generates a random preimage for you, which also results in a regular invoice. ## Paying a Hold Invoice From the sender's perspective, paying a hold invoice is identical to paying a regular one: ### Using fnn-cli [bash code block] ### Using RPC [json code block] The sender's funds are locked in TLCs along the route until the recipient settles or cancels the invoice. The sender does not need to know whether the invoice is a hold invoice or a regular one. ## Settling a Hold Invoice Once the external condition is met (goods delivered, swap confirmed, etc.), reveal the preimage to claim the funds. ### Using fnn-cli [bash code block] ### Using RPC [json code block] The node verifies that hash(preimage) == paymenthash, stores the preimage, and fulfills all held TLCs. The invoice transitions to Paid. You can only settle an invoice that is in the Received state — meaning a payment has actually arrived. If the invoice is still Open (no payment received yet), calling settleinvoice returns an InvoiceStillOpen error. If the invoice has expired, you get InvoiceAlreadyExpired. ## Cancelling a Hold Invoice If the condition is never met, cancel the invoice to release the sender's funds. ### Using fnn-cli [bash code block] ### Using RPC [json code block] Cancellation is allowed when the invoice is in the Open, Received, or Expired state. An invoice that is already Paid or Cancelled cannot be cancelled. ## Checking Invoice Status You can query the current state of any invoice at any time: ### Using fnn-cli [bash code block] ### Using RPC [json code block] The response includes the invoice's current status field (Open, Received, Paid, Cancelled, or Expired), which tells you exactly where the hold invoice is in its lifecycle. ## Managing Hold Invoices | Action | RPC Method | fnn-cli | |--------|-----------|---------| | Create hold invoice | newinvoice (with paymenthash) | fnn-cli invoice newinvoice --payment-hash 0x... | | Settle hold invoice | settleinvoice | fnn-cli invoice settleinvoice --payment-hash 0x... --payment-preimage 0x... | | Cancel hold invoice | cancelinvoice | fnn-cli invoice cancelinvoice --payment-hash 0x... | | Check status | getinvoice | fnn-cli invoice getinvoice --payment-hash 0x... | | Parse invoice | parseinvoice | fnn-cli invoice parseinvoice --invoice fibt1... | Both settleinvoice and cancelinvoice require write("invoices") permission when using Biscuit token authentication. ## Common Issues | Issue | Error | Cause | Solution | |-------|-------|-------|----------| | Settling too early | InvoiceStillOpen | No payment has arrived yet | Wait for the sender's payment to reach your node | | Settling after expiry | InvoiceAlreadyExpired | The invoice's expiry time has passed | Create a new invoice with a longer expiry | | Wrong preimage | HashMismatch | The preimage doesn't match the paymenthash | Double-check the preimage used to compute the hash | | Cannot cancel | — | Invoice is already Paid or Cancelled | No action needed | | Funds stuck | — | Preimage lost before settlement | Wait for the invoice to expire; TLCs are released automatically | ## Related Topics - Invoice Guide — general invoice creation and management - Payment Lifecycle — how payments are routed and settled - Invoice Protocol — the technical specification for invoice encoding - Cross-Chain HTLC — how hold invoices enable cross-chain atomic swaps --- ## Multi-Hop Payments Source: https://www.fiber.world/docs/concept/routing/multi-hop ## TL;DR Multi-hop payments let you send assets to anyone in the Fiber network without opening a direct channel. The sender computes a route through intermediate nodes, wraps the routing instructions in layers of onion encryption, and locks each hop with a time-locked contract (TLC). Intermediate nodes forward the payment automatically and earn a small fee. The recipient reveals a preimage to settle, and funds cascade back through the route. ## Why Multi-Hop? In a payment channel network, two users can only transact directly if they share an open channel. Requiring a direct channel between every pair of users would be impractical — the number of channels would grow quadratically with the number of participants, and each channel requires on-chain funding. Multi-hop payments solve this by routing value through a chain of existing channels. If Alice has a channel with Bob, and Bob has a channel with Carol, Alice can pay Carol by "hopping" through Bob — without ever opening a channel with Carol directly. Bob acts as a relay node, forwarding the payment in exchange for a small fee. The same principle extends to arbitrarily long paths across the network. This design means that a well-connected network with relatively few channels can support payments between any two participants. As long as a path of funded channels exists from sender to receiver, the payment can be delivered. ## The Payment Flow A multi-hop payment in Fiber proceeds through four phases: pathfinding, onion construction, hop-by-hop forwarding, and settlement. ### Pathfinding Before sending a payment, the sender must discover a viable route through the network. Fiber nodes maintain a local view of the network graph — built from gossip messages that advertise channels, node addresses, and fee policies. The routing module uses a modified Dijkstra algorithm that searches from the target back to the source, evaluating each edge with a probability-based cost function inspired by LND's bimodal probability model. The cost function considers both the fee charged by each relay node and the estimated probability that the channel has sufficient liquidity to carry the payment. This produces routes that balance low cost against high success probability. The sender can influence route selection by specifying parameters such as maxfeeamount (the maximum total fee budget) and maxparts (for multi-path splitting). When no single path has enough liquidity for the full amount, Fiber can split the payment across multiple routes using atomic multi-path payments (AMP). Each part travels independently, and the payment only succeeds when all parts are delivered. ### Onion Construction Once a route is selected, the sender builds a Sphinx onion packet — a fixed-size (6500-byte) encrypted structure with one layer per hop. Each layer contains the PaymentHopData for that hop: the amount to forward, the TLC expiry timestamp, the funding transaction hash of the outgoing channel, and the public key of the next node. The final hop includes the payment preimage (for keysend payments) or custom records. The onion is constructed from the inside out. The sender generates a session key and performs an ECDH exchange with each hop's public key to derive a shared secret. Each layer is encrypted with a stream cipher keyed by the corresponding shared secret, and a MAC (HMAC) is appended for integrity verification. The result is a nested structure: the first hop can decrypt only the outermost layer, revealing its instructions and the encrypted blob for the next hop. No intermediate node can see beyond its own layer. [code block] This construction provides two guarantees: each node learns only its predecessor and successor (not the full route), and the sender is the only party who knows the complete path. ### Hop-by-Hop Forwarding With the onion packet ready, the sender creates a TLC (Time-Locked Contract) on the channel to the first hop and sends the AddTlc message along with the onion packet. The TLC locks the payment amount plus all forwarding fees, with an expiry deadline that gives every intermediate node enough time to either settle or fail the payment. When an intermediate node receives an incoming AddTlc, it processes the commitment transaction, then peels one layer of the onion packet using its private key. The decrypted layer reveals: - The amount to forward to the next hop - The expiry deadline for the outgoing TLC - The channel (identified by funding transaction hash) to use for forwarding - The encrypted onion packet for the next hop The node then creates a new TLC on its outgoing channel, forwarding a slightly smaller amount (the difference is its forwarding fee) with a shorter expiry. This process repeats at each hop until the onion reaches the final recipient. The expiry decreases at each hop by the node's configured tlcexpiry_delta. This time buffer ensures that if a downstream node fails to respond, the upstream node has enough time to remove the TLC before its own deadline expires. ### Settlement The final recipient validates the incoming payment against an outstanding invoice: checking the amount, the payment hash, and the expiry. If everything matches, the recipient stores the preimage and sends a RemoveTlcFulfill message back to the previous hop. The preimage is the cryptographic proof of payment — anyone who holds it can claim the locked funds. Settlement propagates backward through the route. Each intermediate node receives the RemoveTlcFulfill, removes its incoming TLC, and forwards the fulfillment to its own predecessor. By the time the preimage reaches the sender, every TLC along the route has been settled, and every relay node has effectively earned its forwarding fee by having shifted liquidity from its outgoing channel to its incoming channel. [code block] ## Time-Locked Contracts (TLC) Every hop in a multi-hop payment is secured by a TLC — Fiber's implementation of hash time-locked contracts. A TLC encodes two conditions: Hashlock: The funds can only be claimed by presenting the preimage that hashes to the payment hash included in the invoice. This ensures that only the intended recipient (who generated the preimage) can unlock the payment. Timelock: If the preimage is not presented before the expiry deadline, the funds automatically revert to the sender. This prevents intermediate nodes from indefinitely locking up liquidity. The combination of hashlock and timelock creates a trustless chain: each node can safely forward funds because it holds a TLC that can only be settled with the same preimage. No node in the chain needs to trust any other node — the contract enforces correct behavior. If any node fails to forward or settle, the timelock ensures that all TLCs eventually expire and funds return to their original owners. Fiber currently uses hash-based TLCs, with plans to migrate to Point Time-Locked Contracts (PTLC) in the future. PTLCs replace the hash with a curve point, removing the repeated-hash linkage across hops and improving payment privacy. ## Routing Fees Relay nodes earn fees for forwarding payments. The fee is calculated using a simple proportional formula: [code block] Each node configures its own fee rate via tlcfeeproportional_millionths in config.yml. A value of 1000 corresponds to a 0.1% fee. The sender's routing algorithm accounts for fees at each hop when computing the total cost and building the onion packet — each hop's amount includes the cumulative fees for all downstream hops. Example: Alice sends 1000 CKB to Dave through Bob (0.1% fee) and Carol (0.1% fee). The total fee is approximately 2 CKB. Alice locks 1002 CKB on her channel with Bob. Bob forwards 1001 CKB to Carol (keeping 1 CKB). Carol forwards 1000 CKB to Dave (keeping 1 CKB). Dave receives exactly 1000 CKB. The fee policy is announced to the network via gossip messages, so senders can factor relay fees into their pathfinding decisions. Nodes that set fees too high will see fewer payments routed through them; nodes that set fees too low may attract traffic but earn less per forward. ## Privacy Through Onion Routing Onion routing is central to payment privacy in Fiber. Without it, every intermediate node would know the sender, the recipient, and the full route — leaking sensitive financial information to every relay in the path. The Sphinx onion construction ensures that each node can decrypt only its own layer. Bob knows that Alice sent him a payment and that he should forward it to Carol, but he does not know whether Carol is the final recipient or just another relay. Carol knows that Bob forwarded a payment to her and that she should forward it to Dave, but she does not know that Alice originated the payment. This design provides hop-by-hop privacy: no single node (except the sender) has a complete view of the route. The final recipient sees only the last hop, and cannot determine the origin. Additionally, Fiber uses constant-time error decoding: when a payment fails, the error packet traverses backward through the route using XOR cipher streams. The origin node always attempts exactly 27 decryption passes regardless of where the error occurred, preventing the erring node from inferring its position in the route through timing analysis. ## Error Handling and Retry Not every payment succeeds on the first attempt. A channel may lack sufficient liquidity, a node may be temporarily offline, or an expiry may be too tight. When a hop cannot process a TLC, it generates a TlcErrPacket — an encrypted error message that propagates backward to the sender. Common error codes include TemporaryChannelFailure (the channel doesn't have enough balance right now), TemporaryNodeFailure (the node is temporarily unable to process), FeeInsufficient (the forwarded amount doesn't cover the node's fee), and ExpiryTooSoon (the TLC expiry doesn't leave enough time for the downstream route). When the sender receives an error, the PaymentActor decodes it, updates the local network graph with the failure information, and — if the error is retryable — automatically builds a new route that avoids the failed channel or node. Each PaymentAttempt has a configurable retry limit, and the PaymentSession tracks the aggregate progress across all attempts. | Error | Meaning | Typical Action | |-------|---------|---------------| | TemporaryChannelFailure | Channel liquidity exhausted | Retry via alternate path | | TemporaryNodeFailure | Node temporarily unavailable | Retry after brief delay | | FeeInsufficient | Forwarded amount below fee | Recalculate amounts | | ExpiryTooSoon | Expiry window too narrow | Increase expiry delta | | IncorrectOrUnknownPaymentDetails | Invoice mismatch at recipient | Check invoice and amount | | PermanentChannelFailure | Channel closed or unavailable | Remove from graph, retry | ## Sending a Multi-Hop Payment The following example walks through a complete multi-hop payment: Alice (nodeA) pays Bob (nodeB) through two public relay nodes. The topology is: [code block] ### 1. Create an Invoice on the Recipient Bob creates an invoice on nodeB for 1 CKB: [bash code block] [json code block] The response includes the encoded invoice string (starting with fibt1... on testnet). ### 2. Send the Payment Alice pays the invoice from nodeA. Fiber automatically discovers a route through the relay nodes, constructs the onion packet, and locks the TLCs: [bash code block] [json code block] The response includes the paymenthash, the route taken (routers), and the total fee charged by relay nodes. The routers field shows each hop as node(amount, channeloutpoint), revealing the full path the sender computed. ### 3. Preview a Route Without Sending Use dryrun to validate the route and estimate fees without actually sending: [bash code block] [json code block] This returns the same route and fee information without creating any TLCs, which is useful for checking whether a payment would succeed before committing funds. ### 4. Monitor the Payment Check the payment status by its hash: [bash code block] [json code block] The status field progresses through Created → Inflight → Success or Failed. If the payment fails, failederror contains the decoded error message from the erring hop, and the sender's routing module will automatically retry with a different path when the error is retryable. ### Specifying Trampoline Hops When the sender has limited graph visibility (e.g., a lightweight wallet), trampoline hops delegate pathfinding to intermediate nodes: [json code block] The sender only needs a route to the first trampoline node. The remaining path is encoded in an inner trampoline onion that each trampoline node peels and forwards. See Trampoline Routing for the full specification. ## Running a Relay Node Relay nodes are the backbone of multi-hop routing. If you operate a well-connected Fiber node with public channels, other users' payments will automatically route through you, and you earn forwarding fees for each one. ### Requirements A relay node needs a server with a publicly reachable IP address, sufficient CKB to fund channels, stable uptime (payments can only route through online nodes), and balanced liquidity on both sides of each channel. Each channel side must reserve 99 CKB (98 CKB for the commitment lock's on-chain occupied capacity + 1 CKB for the shutdown transaction fee) — this amount is locked for on-chain settlement and cannot be used for off-chain payments. The actual minimum funding amount depends on your openchannelautoacceptminckbfundingamount setting (code default: 100 CKB). For example, the public Fiber relay nodes require at least 499 CKB (99 CKB reserve + 400 CKB usable). ### Configuration The key settings in config.yml for relay operation are: [yaml code block] Without a publicly reachable address, other nodes cannot connect to you. You must set announcedaddrs to your public IP or use a relay/VPN service. ### Maintaining Liquidity The main operational challenge for relay nodes is liquidity balance. After forwarding many payments in one direction, a channel becomes one-sided: your local balance depletes, and you can no longer forward in that direction. [code block] The solution is channel rebalancing: using circular payments to shift liquidity between channels. You can rebalance automatically with sendpayment and allowselfpayment: true, or manually by specifying an explicit route with buildrouter and sendpaymentwithrouter. Monitor your channel balances regularly with fnn-cli channel listchannels and rebalance proactively before channels become one-sided. ### Monitoring Forwarding Activity Check which payments have been forwarded through your node and track earned fees: [bash code block] ## Related Topics - Trampoline Routing — delegating pathfinding to trampoline nodes for lightweight clients - Payment Lifecycle — the state machine behind every payment attempt - Invoice — creating and paying invoices - Channel Rebalancing — keeping relay liquidity balanced - Cross-Chain HTLC — atomic swaps between Fiber and the Bitcoin Lightning Network - Gossip Protocol — how nodes discover channels and build the network graph --- ## Trampoline Routing Source: https://www.fiber.world/docs/concept/routing/trampoline-routing ## TL;DR Trampoline routing lets a sender delegate pathfinding to well-connected intermediate nodes called trampoline nodes. Instead of computing the full route to the recipient, the sender only needs to find a path to the first trampoline node. The remaining route is encoded inside a smaller inner trampoline onion that is embedded in the outer payment packet. Each trampoline node independently finds a route to the next hop, peeling one layer of the inner onion and launching a fresh payment downstream. The final recipient sees only the final payload and settles the payment as usual. This design is essential for lightweight clients that cannot store the full network graph, for bridging payments across disconnected sub-networks, and for routing through private channels that are invisible to the sender. ## Why Trampoline Routing? In a standard multi-hop payment, the sender must know the entire network topology to compute a route from source to destination. The routing module searches the local graph — built from gossip messages advertising channels, node addresses, and fee policies — and produces a complete hop-by-hop path. This works well for nodes that maintain a full graph view, but it creates problems for several important use cases that trampoline routing is designed to address. ## When to Use Trampoline Routing ### Mobile and Lightweight Clients The most common scenario is a mobile wallet or lightweight node that has gossip sync disabled to save bandwidth and storage. Such a node only knows about its direct channel partners — it has no visibility into the broader network graph. Without trampoline routing, this node simply cannot send multi-hop payments because it has no way to construct a complete route. With trampoline routing, the mobile wallet only needs to know the pubkey of a single well-connected trampoline node (typically operated by the wallet service provider or a public infrastructure node). The wallet routes to the trampoline node through its direct channel, and the trampoline node — which maintains a full graph — handles the rest of the pathfinding. [code block] This is the primary use case: enabling nodes with minimal graph knowledge to participate in the payment network as full senders. ### Private Channel as the Last Hop A recipient may be reachable only through a private channel — a channel that was not announced via gossip and is therefore invisible to the broader network. This is common when a user opens a channel with a specific merchant or service provider without broadcasting it. [code block] In this scenario, Alice has no way to discover the Dave→Eve channel through gossip. Even if she knows Eve's pubkey, source routing will fail because no public path leads to Eve. By specifying Dave (or Carol, if Dave is also a trampoline node) as a trampoline hop, Alice can deliver the payment to the trampoline node, and the trampoline node — which knows about the private channel — can forward it to Eve. ### Bridging Separate Network Clusters As the Fiber network grows, it may naturally partition into clusters — groups of well-connected nodes with only a few channels bridging between them. A sender in one cluster may have detailed knowledge of its local topology but no visibility into a distant cluster where the recipient lives. [code block] Trampoline routing lets each cluster maintain its own graph knowledge while using bridge nodes as trampolines to route across clusters. The sender routes within its own cluster to the bridge node, and the bridge node routes within the destination cluster to the recipient. This is more scalable than requiring every node to maintain a global graph. ### Payment Service Providers A payment service provider (PSP) or exchange can operate trampoline nodes as part of its infrastructure. Customers send payments to the PSP's trampoline node, and the PSP leverages its well-connected position and full graph knowledge to route payments efficiently across the network. This model decouples the routing intelligence from the end-user client: - The customer's node stays lightweight (no gossip, no graph) - The PSP's trampoline node handles all pathfinding and retry logic - The customer only needs to trust the PSP for routing, not for custody (funds are still secured by TLCs) This architecture mirrors the trampoline model in the Lightning Network, where service providers operate trampoline nodes to enable their users to send payments without maintaining a routing table. ### Choosing Between Source and Trampoline Routing In practice, you should use trampoline routing when the sender has limited graph visibility (gossip sync disabled, mobile client, stale graph after restart), when the recipient is reachable only through private channels, or when the payment must cross network cluster boundaries. For well-connected nodes with a full and current graph view, standard source routing typically provides more efficient and predictable results — the sender can optimize fees and expiry across the entire route rather than delegating those decisions to intermediate nodes. ## How It Works A trampoline payment proceeds through the same four phases as a standard multi-hop payment — pathfinding, onion construction, hop-by-hop forwarding, and settlement — but with an additional layer of indirection. [code block] ### Sender: Route to the First Trampoline The sender's pathfinding task is dramatically simplified. Instead of computing a route all the way to the recipient, the sender only needs a route from itself to the first trampoline node. The routing module runs the standard graph search algorithm against the local network graph, but the target is the first trampoline node rather than the final recipient. The amount locked on this outer route is finalamount + maxfeeamount, where maxfee_amount is the total fee budget the sender is willing to spend across all trampoline hops. The sender delivers this full amount to the first trampoline node; any fees consumed by the outer route reduce what the first trampoline receives. ### Inner Trampoline Onion Construction Once the outer route is computed, the sender builds an inner trampoline onion that encodes the remaining hops. This inner onion uses the same Sphinx construction as the outer payment onion, but with a smaller packet size of 1300 bytes (compared to the outer onion's 6500 bytes) because it must fit inside the outer onion's hop payload. The inner onion contains one layer per trampoline hop, plus a final layer for the recipient. Each trampoline hop carries a Forward payload with the following information: - nextnodeid — the public key of the next trampoline node (or the final recipient) - amounttoforward — the amount that must arrive at the next hop - buildmaxfeeamount — the fee budget allocated for this hop's own sub-route - tlcexpirydelta and tlcexpirylimit — expiry constraints for the downstream TLCs - hashalgorithm — the hash algorithm used for the payment hash - max_parts — optional parameter controlling multi-path splitting for the downstream sub-route The final layer carries a Final payload with the finalamount, the finaltlcexpirydelta, and optionally a paymentpreimage (for keysend payments) and custom records (for multi-path payments). The trampoline onion bytes are embedded in the last hop of the outer payment onion's customrecords using a reserved key. This keeps the outer PaymentHopData schema unchanged, maintaining backward compatibility with nodes that do not support trampoline routing. [code block] ### Trampoline Node: Forwarding When a node receives a payment and detects a trampoline payload embedded in the outer onion, it switches from normal forwarding to trampoline forwarding. The process involves several steps: 1. Feature check. The node verifies that it supports trampoline routing (feature bit 5, TRAMPOLINE_ROUTING). If the feature is not enabled, the node returns a RequiredNodeFeatureMissing error. 2. Peel the inner onion. Using its private key, the trampoline node decrypts one layer of the inner trampoline onion, revealing its TrampolineHopPayload. 3. Validate the forward payload. The node checks that the incoming amount exceeds amounttoforward (the difference is the available fee), and that this available fee exactly matches buildmaxfee_amount. This ensures that the sender's fee allocation is correctly propagated. 4. Launch a downstream payment. The trampoline node creates a brand-new payment to nextnodeid using its own PaymentActor. This downstream payment carries: - The amounttoforward as the payment amount - The buildmaxfeeamount as the routing fee budget - A TrampolineContext containing the remaining inner onion (for the next trampoline hop) and a reference to the incoming TLC The downstream payment goes through the normal pathfinding and onion construction process. If the trampoline node is forwarding to another trampoline, the inner onion is embedded again. If it is forwarding to the final recipient, the remaining onion contains only the Final payload. Each trampoline node independently finds its own route to the next hop. This means the trampoline node uses its local network graph — which may be more complete than the sender's — to compute the best path. If the downstream payment fails, the trampoline node's PaymentActor can retry with an alternate route, just like any other payment. ### Final Recipient: Settlement When the outer onion reaches the final recipient and contains a trampoline payload, the recipient peels the inner trampoline onion to extract the Final payload. This payload provides the finalamount, the expiry delta, and any optional preimage or custom records. The recipient validates that the forwarded amount matches the expected final amount, checks the payment against the outstanding invoice, and settles the TLC by revealing the preimage. From the recipient's perspective, the payment looks like a normal incoming payment — the trampoline routing is transparent. ### Upstream TLC Resolution Each trampoline node holds an incoming (upstream) TLC from the previous hop and an outgoing (downstream) TLC from its own payment. When the downstream payment completes: On success: The trampoline node receives the preimage from the downstream settlement and fulfills the upstream TLC by sending a RemoveTlcFulfill message back to the previous hop. The preimage cascades backward through the entire route. On failure: The trampoline node wraps the downstream error in a TrampolineFailed error structure — which includes the trampoline node's ID and the inner error packet — and fails the upstream TLC with a RemoveTlcFail message. The error propagates backward to the sender, who can decide whether to retry the entire payment. ## Fee Behavior Trampoline routing introduces a two-level fee structure: fees for the outer route (sender to first trampoline) and fees for the inner trampoline hops. ### Fee Allocation The sender specifies a maxfeeamount that covers the entire trampoline route. The fee is consumed in order: 1. Outer route fees are deducted first. The sender locks finalamount + maxfee_amount on the first hop of the outer route. Each relay node on the outer route takes its fee, reducing the amount that arrives at the first trampoline node. 2. Trampoline hop fees are allocated from the remaining budget. The sender splits the residual fee evenly across all trampoline hops, passing each hop's share as buildmaxfee_amount in the inner onion payload. 3. Each trampoline node uses its buildmaxfeeamount as the routing fee budget for finding a path to the next hop. Any relay fees on the sub-route between two trampoline nodes are paid from this budget. ### Fee Formula The forwarding fee at each relay node follows the standard TLC fee formula: [code block] Where feerate is the node's configured tlcfeeproportional_millionths. A value of 1000 corresponds to a 0.1% fee. Example: Alice sends 1000 CKB to Eve through trampoline node Bob, with maxfeeamount of 10 CKB. Alice locks 1010 CKB on her outer route to Bob. If the outer route costs 2 CKB in relay fees, Bob receives 1008 CKB. Bob's buildmaxfeeamount is 8 CKB (the remaining budget). Bob finds a route to Eve and forwards 1000 CKB, keeping up to 8 CKB as the cumulative fee for the sub-route. ### Fee Estimation When the sender does not have detailed fee information for the trampoline hops (for example, when gossip sync is disabled), the routing module estimates fees using a default fee rate. The recommended minimum fee is computed as ceil(finalamount * DEFAULTFEERATE / 1,000,000) per trampoline hop, providing a reasonable guardrail for the maxfeeamount parameter. ## Expiry Behavior Each trampoline hop adds a default expiry delta (DEFAULTTLCEXPIRYDELTA) to the total time-lock duration. This ensures that every intermediate node on every sub-route has enough time to settle or fail the payment before its TLC expires. The total accumulated expiry delta across all remaining trampoline hops must not exceed tlcexpirylimit, which sets an upper bound on how far into the future a TLC can be locked. If the accumulated delta would exceed this limit, the route construction fails. ## Error Handling Trampoline payments can fail at any level: on the outer route, within a trampoline node's downstream payment, or at the final recipient. The error handling mechanism preserves the layered structure of the payment. When a downstream payment fails at a trampoline node, the error is wrapped in a TlcErrData::TrampolineFailed structure that contains: - nodeid — the public key of the trampoline node where the failure occurred - innererrorpacket — the encrypted error packet from the downstream payment This wrapped error is sent back through the upstream TLC, propagating to the sender via the normal error path. The sender's PaymentActor can then decode the error, update its local graph, and decide whether to retry the payment — potentially choosing a different first trampoline node or adjusting the fee budget. If a trampoline node's downstream payment exhausts its buildmaxfeeamount without finding a viable route (for example, because relay fees on the sub-route are higher than expected), the payment fails with a fee-insufficient error. The sender can retry with a higher maxfeeamount or different trampoline hops. Common error scenarios include: | Error | Where It Occurs | Typical Cause | |-------|----------------|---------------| | TemporaryChannelFailure | Outer or sub-route | Channel liquidity exhausted | | FeeInsufficient | Trampoline node | buildmaxfeeamount too small for the sub-route | | RequiredNodeFeatureMissing | Trampoline node | Node does not support trampoline routing | | IncorrectOrUnknownPaymentDetails | Final recipient | Invoice mismatch or expired invoice | | ExpiryTooSoon | Any hop | Expiry window too narrow for remaining hops | ## Privacy Benefits Trampoline routing provides meaningful privacy improvements over standard source routing. In a multi-hop payment with source routing, the sender knows the complete path from source to destination. While intermediate nodes only see their predecessor and successor (thanks to onion routing), the sender has full visibility into the route topology. If the sender is also a relay node, it can observe which nodes are transacting. With trampoline routing, the sender only knows the route to the first trampoline node. The trampoline nodes know only their immediate predecessor and successor in the trampoline chain. The final recipient sees only the last hop and cannot determine the origin of the payment. This creates a stronger privacy boundary: - The sender does not learn the topology between the first trampoline and the recipient. - Each trampoline node learns only the previous and next hop in the chain, not the full trampoline route. - The final recipient cannot correlate the payment with the original sender. This is particularly valuable for lightweight clients that would otherwise need to request routing information from a centralized service, leaking their payment intentions. ## Using Trampoline Routing ### Via RPC Set the trampolinehops field in the sendpayment RPC to specify the trampoline nodes. When this field is provided, the routing module automatically switches to trampoline routing mode. [json code block] The trampolinehops array is an ordered list of public keys. The first entry is the node the sender routes to directly; subsequent entries form the trampoline chain. The final recipient is implied by the invoice and must not appear in the trampoline hops list. When using trampolinehops, maxfeeamount is required. This sets the total fee budget for the entire trampoline route, including both the outer route and all trampoline sub-routes. ### Feature Negotiation Trampoline routing uses feature bit 5 (TRAMPOLINEROUTING) in the node feature vector. By default, Fiber nodes advertise trampoline routing as a required feature. A node must support this feature to act as a trampoline hop. If a payment encounters a node that does not advertise trampoline support, the routing module skips that node during trampoline hop selection. Invoices can also signal trampoline support via the allowtrampolinerouting() check on the invoice's feature bits. This allows the recipient to indicate whether it can process trampoline payments. ### Keysend Payments Trampoline routing supports keysend payments (where the sender generates the preimage instead of the recipient). The preimage is included in the inner onion's Final payload and propagated to the recipient during settlement. Keysend over trampoline works the same way as standard keysend, with the only difference being that the routing is delegated to trampoline nodes. ## Constraints Trampoline routing introduces several constraints beyond those of standard multi-hop payments: | Constraint | Value | Notes | |------------|-------|-------| | Max trampoline hops | 5 | Defined as MAXTRAMPOLINEHOPSLIMIT | | maxfeeamount | Required | Must be set when trampolinehops is provided | | Duplicate hops | Rejected | The trampoline hops list must not contain duplicates | | Target in hops | Rejected | The final recipient must not appear in trampolinehops | | Self-payment | Incompatible | allowselfpayment cannot be used with trampoline routing | | MPP with multiple trampoline hops | Incompatible | max_parts > 1 requires exactly 1 trampoline hop | | Inner packet size | 1300 bytes | Smaller than the outer onion's 6500 bytes | The MPP (Multi-Path Payment) restriction exists because splitting a trampoline payment across multiple paths would require coordinating multiple inner onions, each with their own trampoline hop chain. With a single trampoline hop, MPP works normally — the sender splits the payment across multiple routes to the trampoline node, and the trampoline node forwards each part independently. ## Trampoline vs. Source Routing Understanding when to use trampoline routing versus standard source routing helps in choosing the right approach for a given payment scenario. | Aspect | Source Routing | Trampoline Routing | |--------|---------------|-------------------| | Route knowledge | Sender knows the full path | Sender only knows path to first trampoline | | Onion layers | Single outer onion | Two layers: outer + inner trampoline onion | | Pathfinding | Sender does all pathfinding | Each trampoline node does its own pathfinding | | Fee allocation | Sender allocates fees per hop | Fee budget split across trampoline hops | | MPP support | Full MPP support | MPP only with single trampoline hop | | Self-payment | Supported (circular routes) | Not supported | | Retry behavior | Sender retries with new route | Each trampoline independently retries its sub-route | | Error propagation | Direct onion error unwrapping | Errors wrapped in TrampolineFailed at each boundary | In practice, trampoline routing is most useful when the sender has limited graph visibility (gossip sync disabled, mobile client), when the recipient is reachable only through private channels, or when the network spans disconnected sub-graphs. For well-connected nodes with a full graph view, standard source routing typically provides more efficient and predictable results. ## Related Topics - Multi-Hop Payments — the foundation that trampoline routing builds on - Payment Lifecycle — how payment sessions and attempts are managed - Invoice Guide — creating invoices and understanding payment parameters --- ## Biscuit Authentication in Fiber Source: https://www.fiber.world/docs/concept/security/biscuit-auth This document provides an overview of Biscuit authentication, how it's integrated into Fiber's RPC system, and how to use it. ## 1. What is Biscuit Authentication? [Biscuit] is a modern authorization token format designed for distributed verification. It's similar in concept to other bearer tokens like JWTs (JSON Web Tokens). ## 2. How Biscuit Auth Works in Fiber RPC Biscuit is integrated into the Fiber RPC, it can be disabled when RPC listens on a private address, but must be enabled when listen to public address. When enabled, it protects RPC endpoints by requiring a valid Biscuit token with permission for resources. Here's a step-by-step breakdown of the process: 1. Client Request: The client sends an RPC request and includes the Biscuit token in the Authorization HTTP header, formatted as a Bearer token. The token itself is Base64-encoded. 2. Middleware Interception: On the server, the BiscuitAuthMiddleware intercepts every incoming RPC request before it reaches the actual method handler. 3. Token Extraction and Verification: The middleware extracts the Base64-encoded token from the header, decodes it, and verifies its signature using the server's configured public key. If the signature is invalid, the request is immediately rejected. 4. Rule-based Authorization: If the signature is valid, the middleware proceeds to the authorization step. * Fiber maintains a predefined set of authorization rules for each RPC method. For example, the open_channel method requires a token with the write("channels") permission. There are three permission types: read (read-only access), write (mutate access), and internal (reserved for internal services). Note that write does not imply read — they are independent grants. The middleware builds a Biscuit Authorizer. This authorizer is loaded with the rule corresponding to the RPC method being called. It also adds contextual facts to the authorizer, such as the current time (e.g., time(2023-10-27T10:00:00Z)). This allows for policies that depend on time (e.g., token expiration checks). * For watchtower methods, the middleware additionally requires that the token contains a node($id) fact. This node_id is extracted from the token and injected into the RPC request as context, enabling channel-level authorization scoped to the calling node. 5. Policy Execution: The authorizer then evaluates the token against the loaded rules and facts. It checks if the permissions granted in the token (the "authority" block and any attenuated blocks) satisfy the requirements of the RPC method's policy. 6. Access Control: If the authorization check passes, the middleware forwards the request to the intended RPC method, and the operation proceeds. If the check fails (either due to insufficient permissions or a failed check like an expired timestamp), the middleware rejects the request with an "Unauthorized" error. This entire process happens transparently for the RPC methods themselves. The logic is neatly contained within the middleware, ensuring that security is applied consistently across all protected endpoints. The current rules for each RPC methods: `` rust // Cch rule("sendbtc", r#"allow if write("cch");"#); rule("receivebtc", r#"allow if write("cch");"#); rule("getcchorder", r#"allow if read("cch");"#); rule("subscribestorechanges", r#"allow if internal("storechanges");"#); // channels rule("openchannel", r#"allow if write("channels");"#); rule("acceptchannel", r#"allow if write("channels");"#); rule("abandonchannel", r#"allow if write("channels");"#); rule("listchannels", r#"allow if read("channels");"#); rule("shutdownchannel", r#"allow if write("channels");"#); rule("updatechannel", r#"allow if write("channels");"#); rule("openchannelwithexternalfunding", r#"allow if write("channels");"#); rule("submitsignedfundingtx", r#"allow if write("channels");"#); // dev rule("commitmentsigned", r#"allow if write("dev");"#); rule("addtlc", r#"allow if write("dev");"#); rule("removetlc", r#"allow if write("dev");"#); rule("checkchannelshutdown", r#"allow if write("dev");"#); rule("signexternalfundingtx", r#"allow if write("dev");"#); rule("submitcommitmenttransaction", r#"allow if write("dev");"#); // prof rule("pprof", r#"allow if write("pprof");"#); // graph rule("graphnodes", r#"allow if read("graph");"#); rule("graphchannels", r#"allow if read("graph");"#); // info rule("nodeinfo", r#"allow if read("node");"#); rule("newinvoice", r#"allow if write("invoices");"#); rule("parseinvoice", r#"allow if read("invoices");"#); rule("getinvoice", r#"allow if read("invoices");"#); rule("cancelinvoice", r#"allow if write("invoices");"#); rule("settleinvoice", r#"allow if write("invoices");"#); // payment rule("sendpayment", r#"allow if write("payments");"#); rule("getpayment", r#"allow if read("payments");"#); rule("listpayments", r#"allow if read("payments");"#); rule("buildrouter", r#"allow if read("payments");"#); rule("sendpaymentwithrouter", r#"allow if write("payments");"#); // peer rule("connectpeer", r#"allow if write("peers");"#); rule("disconnectpeer", r#"allow if write("peers");"#); rule("listpeers", r#"allow if read("peers");"#); // watchtower (all watchtower methods also require RPC context for channel-level auth) rule("createwatchchannel", r#"allow if write("watchtower");"#); rule("removewatchchannel", r#"allow if write("watchtower");"#); rule("updaterevocation", r#"allow if write("watchtower");"#); rule("updatependingremotesettlement", r#"allow if write("watchtower");"#); rule("updatelocalsettlement", r#"allow if write("watchtower");"#); rule("createpreimage", r#"allow if write("watchtower");"#); rule("removepreimage", r#"allow if write("watchtower");"#); [code block] bash # make sure you use 0.6.0 version cargo install biscuit-cli --vers 0.6.0-beta.2 [code block]bash biscuit keypair [code block] Generating a new random keypair Private key: ed25519-private/89d6c88919e5ca326fbb8d1cbef406df08c0620575376651d53008762dc81f45 Public key: ed25519/17b172749be74276f0ed35a5d0685752684a3c5722114bba447a2f301136db79 [code block]yaml rpc: # ... other rpc settings biscuitpublickey: "ed25519/17b172749be74276f0ed35a5d0685752684a3c5722114bba447a2f301136db79" # Your ed25519 public key string [code block]bash fnn --rpc-biscuit-public-key "ed25519/17b172749be74276f0ed35a5d0685752684a3c5722114bba447a2f301136db79" [code block]bash export RPCBISCUITPUBLICKEY="ed25519/17b172749be74276f0ed35a5d0685752684a3c5722114bba447a2f301136db79" fnn [code block]text // Grant permissions for specific modules read("peers"); write("payments"); // You can also add checks, like an expiration date. // This check ensures the token is only valid before the specified UTC timestamp. check if time($time), $time <= 2025-01-01T00:00:00Z; [code block]bash biscuit generate --private-key ed25519-private/89d6c88919e5ca326fbb8d1cbef406df08c0620575376651d53008762dc81f45 permissions.bc [code block] ErsBClEKBXBlZXJzCghwYXltZW50cxgDIgkKBwgAEgMYgAgiCQoHCAESAxiBCDImCiQKAggbEgYIBRICCAUaFgoECgIIBQoICgYggIvSuwYKBBoCCAISJAgAEiDAjoKKNTZpA61ImgoD5Q2sSbBjA3ixK1R2M65a2TOxbRpA4SF04LS5zD6OempqQObA2TTlTCANI7bZkDpl7JIecjR59GFoNtyKYak-jXq2gDUHadj2I8SbJ0N-vN1RPslDSIiCiBR7VZ7ZJaPE4nhxUUpgpd5MXr3hqi0RfNERy3NE4KjaQ== [code block] Authorization: Bearer ErsBClEKBXBlZXJzCghwYXltZW50cxgDIgkKBwgAEgMYgAgiCQoHCAESAxiBCDImCiQKAggbEgYIBRICCAUaFgoECgIIBQoICgYggIvSuwYKBBoCCAISJAgAEiDAjoKKNTZpA61ImgoD5Q2sSbBjA3ixK1R2M65a2TOxbRpA4SF04LS5zD6OempqQObA2TTlTCANI7bZkDpl7JIecjR59GFoNtyKYak-_jXq2gDUHadj2I8SbJ0N-vN1RPslDSIiCiBR7VZ7ZJaPE4nhxUUpgpd5MXr3hqi0RfNERy3NE4KjaQ== ` This token grants exactly the permissions you defined and will be successfully verified by a Fiber server configured with the corresponding public key. ## 5. Test Examples The Fiber codebase contains numerous tests that demonstrate the usage of Biscuit tokens. These are excellent resources for understanding the system. ### a. RPC Integration Tests The file crates/fiber-lib/src/fiber/tests/rpc.rs contains integration-style tests for the RPC interface. Key examples include: * testrpcbasicwithauth: Shows how to make multiple authenticated RPC calls with a single token. * testrpcauthwithouttoken: Demonstrates that an unauthenticated request to a protected endpoint is rejected. * testrpcauthwithinvalid_token: Tests that a token signed by an incorrect private key is rejected. * testrpcauthwithwrong_permission: Shows that a valid token with insufficient permissions is correctly denied access. Link to file: crates/fiber-lib/src/fiber/tests/rpc.rs ### b. Unit Tests for Authorization Logic The file crates/fiber-lib/src/rpc/biscuit.rs contains unit tests that focus specifically on the token authorization logic. These are useful for seeing how different kinds of Datalog rules are handled. * testbiscuitauth: Basic tests for checking read and write permissions. * testbiscuitauth_watchtower: A more advanced example of granting permissions for watchtower methods with RPC context requirements. * testbiscuittoken_timeout: Demonstrates how to create and verify a token with an expiration date. Link to file: crates/fiber-lib/src/rpc/biscuit.rs` [biscuit]: https://www.biscuitsec.org/ [biscuit-doc]: https://doc.biscuitsec.org/usage/command-line --- ## Watchtower Source: https://www.fiber.world/docs/concept/security/watchtower ## What Is a Watchtower? If a Fiber node is offline for some time, the counterparty may try to force-close the channel with an outdated state. If that state is not challenged in time, the counterparty could claim funds that should belong to you. A watchtower is a service that helps protect against this risk. When a commitment transaction is submitted on-chain, the watchtower checks whether it uses an outdated revoked state. If it does, the watchtower can submit a penalty transaction so the outdated state cannot be used to claim funds. Otherwise, the channel can follow the normal settlement path. ## What the Watchtower Does At a high level, the watchtower has three jobs: - Monitor channels: track watched channels as they are opened, updated, and closed. - Detect outdated states: check whether an on-chain close uses an older revoked channel state. - Respond on-chain: either allow normal settlement or submit a penalty transaction for an outdated state. ## Penalty Transactions A penalty transaction is the on-chain response to an outdated revoked channel state. In Fiber, this response is implemented as a revocation transaction. The watchtower compares the commitment number in the on-chain transaction with the revocation data stored for the channel. If the transaction uses an older revoked state, the watchtower constructs and submits a revocation transaction. If the on-chain close is valid, the watchtower follows the normal settlement path instead. ## How to Run a Watchtower Fiber nodes can run in two forms: the native fnn node and the WASM node provided by the fiber-js package. Watchtower deployment depends on which form you use. - Native fnn nodes can use the built-in watchtower, which runs inside the node process. - WASM nodes should use a standalone watchtower service and forward watchtower events to it through standalonewatchtowerrpcurl. ### Built-in Watchtower The built-in watchtower is available for native fnn nodes. It is enabled when disablebuiltinwatchtower is set to false. [yaml code block] watchtowercheckintervalseconds controls how often the built-in watchtower checks on-chain data. The default is 60 seconds. ### Standalone Watchtower A standalone watchtower runs as a separate RPC service and can receive watchtower updates from one or more Fiber nodes. See Watchtower API Reference for detailed parameter and return type specifications. Because a standalone watchtower receives channel watch data, choose an operator according to the deployment's trust requirements. Configure the Fiber node to send watchtower updates to the standalone service: [yaml code block] standalonewatchtowertoken is only needed when the standalone watchtower RPC service requires authentication. If the built-in watchtower is disabled, standalonewatchtowerrpcurl must be configured. ## References - Fiber WatchtowerActor source - Fiber watchtower store source - Watchtower APIs --- ## RPC Overview Source: https://www.fiber.world/docs/api-reference # RPC Overview The JSON-RPC interface is the primary way applications interact with a Fiber Network Node (FNN). Whether you are building a wallet, a payment processor, a network explorer, or automating channel management, you communicate with the node through this HTTP-based RPC layer. Fiber's RPC server is built on jsonrpsee, a Rust JSON-RPC framework. It speaks standard JSON-RPC 2.0 over HTTP and supports both synchronous method calls and WebSocket-based pub/sub for real-time store change notifications. ## Endpoint [code block] The listen address is controlled by the rpc.listeningaddr configuration field. The example configs shipped with the node use: [code block] Because the RPC server binds to a single TCP socket, all modules share the same endpoint. There is no per-module URL path (e.g., /v1/channel); instead, you specify the target method inside the JSON-RPC request body. ## JSON-RPC Request Format Every request is a standard JSON-RPC 2.0 object sent as an HTTP POST with Content-Type: application/json. [json code block] | Field | Type | Required | Description | |-------|------|----------|-------------| | jsonrpc | string | Yes | Must be "2.0". | | method | string | Yes | The RPC method name (e.g., sendpayment, open_channel). | | params | array | Yes | Method arguments as a single-element array containing the params object. Even methods that take no arguments must send an empty array []. | | id | number \| string \| null | Yes | Client-defined request identifier; echoed back in the response for correlation. | > Implementation detail: Internally, the middleware may inject an RpcContext object as the first element of the params array when authentication is enabled and the method requires caller identity (e.g., watchtower operations). Client callers do not need to construct this themselves; the server injects it after verifying the Biscuit token. ## JSON-RPC Response Format ### Success [json code block] ### Error [json code block] Fiber uses standard jsonrpsee error codes where applicable. In addition, authentication failures return a custom error: | Code | Message | Meaning | |------|---------|---------| | -32999 | Unauthorized | The request lacked a valid Biscuit token, the token was revoked, or the token does not carry the required permission for the method. | ## RPC Modules Functionality is grouped into modules. You can selectively enable or disable modules via the rpc.enabled_modules config field. By default, the stable modules cch, channel, graph, info, invoice, payment, and peer are enabled. The watchtower module is also enabled when the node is compiled with the watchtower feature. The admin, pubsub, dev, and prof modules are not enabled by default. | Module | Description | |--------|-------------| | info | Node identity, version, and runtime parameters. | | peer | Connect, disconnect, and list P2P peers. | | channel | Open, accept, close, update, and list payment channels. | | payment | Send payments, query payment status, and build custom routes. | | invoice | Create, parse, cancel, and settle BOLT-11 style invoices. | | graph | Query the network graph for nodes and channels. | | cch | Cross-Chain Hub operations (BTC ↔ CKB swaps). | | admin | Native-node administration, including online backups. | | watchtower | Channel monitoring and revocation data for watchtower services. | | pubsub | WebSocket subscription to store change events. | | dev | Debug builds only. Low-level debugging helpers. | | prof | Requires pprof feature. CPU/memory profiling endpoints. | Modules are activated at server startup. If a module is disabled, its methods are not registered and will return a "Method not found" error. ## Authentication Fiber uses Biscuit bearer tokens for RPC authorization. Biscuit is a decentralized, attenuable authorization token format similar in spirit to JWT but with fine-grained, rule-based policies written in Datalog. ### When is auth required? - Private addresses (loopback, LAN, and link-local interfaces like 127.0.0.1, [::1], 192.168.x.x): Authentication is optional. If no biscuitpublickey is configured, the server accepts all local requests. - Unspecified bind addresses (0.0.0.0, [::]): Treated as public addresses. Authentication is mandatory. - Public addresses: Authentication is mandatory. The server will refuse to start on a public IP unless rpc.biscuitpublickey is set. This is a hard safety guard to prevent accidental exposure of an open RPC. ### Sending a token Include the Base64-encoded Biscuit token in the HTTP Authorization header: [code block] ### Permission model Each method is protected by a Datalog rule that checks for a specific permission fact in the token. Permissions are scoped by resource and access level: - read("") — grants read-only access to that resource. - write("") — grants write access to that resource only; it does not imply read access. Resources map roughly to modules, with two exceptions for the dev module: - node, peers, channels, payments, invoices, graph, cch, watchtower, pprof — each maps to the module of the same name. - chain, messages — used only by low-level dev module methods (submitcommitmenttransaction and commitmentsigned respectively); there are no standalone chain or messages API-reference pages. For example, to call sendpayment, the token must contain write("payments"). To call nodeinfo, the token needs read("node"). For a full rule table and instructions on generating keys and tokens, see Biscuit Authentication. ## Configuration RPC settings live under the rpc: key in config.yml: [yaml code block] You can also override every field via environment variables or CLI flags: [bash code block] ### CORS If corsenabled is true, the server adds Cross-Origin Resource Sharing headers to HTTP responses. When corsallowedorigins is empty, all origins are allowed (*). If you list specific origins, preflight OPTIONS requests are handled automatically. ## Concrete Example Below is a complete curl conversation with a locally running Fiber node. No authentication is needed because the node is listening on localhost and biscuitpublickey is not configured. ### 1. Query node information [bash code block] Response: [json code block] ### 2. Send a payment (with authentication) If your node requires Biscuit auth, add the Authorization header: [bash code block] Response: [json code block] ## WebSocket Subscriptions In addition to request/response HTTP calls, the pubsub module exposes a WebSocket subscription endpoint: - Subscribe: subscribestorechanges - Unsubscribe: unsubscribestorechanges - Notification topic: storechanges When authentication is enabled, subscribestore_changes requires read("cch") in the Biscuit token (the same resource used by the CCH module). This reflects the fact that the subscription is primarily intended for Cross-Chain Hub integration rather than general client use. This is primarily used by external services (such as a standalone Cross-Chain Hub instance) to receive real-time store change events from the Fiber node without polling. ## Security Checklist 1. Never expose an unauthenticated RPC to the public internet. The node will block this at startup, but double-check your listening_addr and firewall rules. 2. Keep the Biscuit private key offline. Only the public key belongs in the node config. 3. Scope tokens narrowly. Create tokens with the minimum permissions required (e.g., read("graph") only, rather than an all-access token). 4. Use CORS allowlists in production rather than . 5. Prefer localhost or Unix-domain sockets when the client and node run on the same machine. ## Further Reading - Biscuit Authentication — key generation, token creation, and permission rules - Configuration Reference — all rpc. config fields - The per-method API references in this section for detailed parameter and return types --- ## Module `Info` Source: https://www.fiber.world/docs/api-reference/node/info ## Module Info The RPC module for node information. ### Method node_info Get the node information. #### Params None #### Returns version - String, The version of the node software. * commit_hash - String, The commit hash of the node software. pubkey - Pubkey, The identity public key of this node (secp256k1 compressed, hex without 0x prefix). features - Vec, The features supported by the node. * node_name - Option, The optional name of the node. addresses - Vec, A list of multi-addresses associated with the node (as strings). chain_hash - Hash256, The hash of the blockchain that the node is connected to. * openchannelautoacceptminckbfunding_amount - u64, The minimum CKB funding amount for automatically accepting open channel requests, serialized as a hexadecimal string. * autoacceptchannelckbfunding_amount - u64, The CKB funding amount for automatically accepting channel requests, serialized as a hexadecimal string. * defaultfundinglock_script - Script, The default funding lock script for the node. * tlcexpirydelta - u64, The locktime expiry delta for Time-Locked Contracts (TLC), serialized as a hexadecimal string. * tlcminvalue - u128, The minimum value for Time-Locked Contracts (TLC) we can send, serialized as a hexadecimal string. * tlcfeeproportional_millionths - u128, The fee (to forward payments) proportional to the value of Time-Locked Contracts (TLC), expressed in millionths and serialized as a hexadecimal string. * channel_count - u32, The number of channels associated with the node, serialized as a hexadecimal string. * pendingchannelcount - u32, The number of pending channels associated with the node, serialized as a hexadecimal string. * peers_count - u32, The number of peers connected to the node, serialized as a hexadecimal string. * udtcfginfos - UdtCfgInfos, Configuration information for User-Defined Tokens (UDT) associated with the node. --- ## Module `Peer` Source: https://www.fiber.world/docs/api-reference/node/peer ## Module Peer RPC module for peer management. ### Method connect_peer Connect to a peer. #### Params address - Option, The address of the peer to connect to (as a multiaddr string). Either address or pubkey must be provided. pubkey - Option, The public key of the peer to connect to. The node resolves the address from locally synced graph data. save - Option, Whether to save the peer address to the peer store. addr_type - Option, Filter addresses by transport type when connecting by pubkey. If not specified, the node uses target-specific defaults: native builds choose from tcp addresses only, while wasm builds choose from ws/wss. #### Returns None ** ### Method disconnect_peer Disconnect from a peer. #### Params pubkey - Pubkey, The public key of the peer to disconnect. #### Returns None *** ### Method list_peers List connected peers #### Params None #### Returns peers - Vec, A list of connected peers. --- ## Module `Admin` Source: https://www.fiber.world/docs/api-reference/node/admin ## Module Admin The admin module contains privileged node-administration operations. It is available on native v0.9.0 nodes but is not enabled by default. Add admin to rpc.enabledmodules and protect any non-local RPC listener with Biscuit authentication before using it. ### Method backup Creates an immediate online backup of the Fiber store and node key files. The backup is written to $BASEDIR/fiber/backups/. #### Params None. Send an empty params array. #### Returns None (null in the JSON-RPC response). #### Example [json code block] The equivalent command is: [bash code block] See Fiber Node Backup for module configuration, automatic backups, and restore instructions. --- ## Module `Channel` Source: https://www.fiber.world/docs/api-reference/channels/channel ## Module Channel RPC module for channel management. ### Method open_channel Attempts to open a channel with a peer. #### Params * pubkey - Pubkey, The public key of the peer to open a channel with. The peer must be connected through the connect_peer rpc first. * funding_amount - u128, The amount of CKB or UDT to fund the channel with. public - Option, Whether this is a public channel (will be broadcasted to network, and can be used to forward TLCs), an optional parameter, default value is true. one_way - Option, Whether this is a one-way channel (will not be broadcasted to network, and can only be used to send payment one way), an optional parameter, default value is false. * fundingudttype_script - Option, The type script of the UDT to fund the channel with, an optional parameter. * shutdownscript - Option, The script used to receive the channel balance, an optional parameter, default value is the secp256k1blake160sighashall script corresponding to the configured private key. * commitmentdelayepoch - Option, The delay time for the commitment transaction, must be an EpochNumberWithFraction in u64 format, an optional parameter, default value is 1 epoch, which is 4 hours. * commitmentfeerate - Option, The fee rate for the commitment transaction, an optional parameter. * fundingfeerate - Option, The fee rate for the funding transaction, an optional parameter. * tlcexpirydelta - Option, The expiry delta to forward a tlc, in milliseconds, default to 4 hours, which is 4 60 60 * 1000 milliseconds Expect it >= 2/3 commitmentdelayepoch. This parameter can be updated with rpc update_channel later. * tlcminvalue - Option, The minimum value for a TLC our side can send, an optional parameter, default is 0, which means we can send any TLC is larger than 0. This parameter can be updated with rpc update_channel later. * tlcfeeproportionalmillionths - Option, The fee proportional millionths for a TLC, proportional to the amount of the forwarded tlc. The unit is millionths of the amount. default is 1000 which means 0.1%. This parameter can be updated with rpc updatechannel later. Not that, we use outbound channel to calculate the fee for TLC forwarding. For example, if we have a path A -> B -> C, then the fee B requires for TLC forwarding, is calculated the channel configuration of B and C, not A and B. * maxtlcvalueinflight - Option, The maximum value in flight for TLCs, an optional parameter. This parameter can not be updated after channel is opened. * maxtlcnumberinflight - Option, The maximum number of TLCs that can be accepted, an optional parameter, default is 125 This parameter can not be updated after channel is opened. #### Returns * temporarychannelid - Hash256, The temporary channel ID of the channel being opened *** ### Method accept_channel Accepts a channel opening request from a peer. #### Params * temporarychannelid - Hash256, The temporary channel ID of the channel to accept * funding_amount - u128, The amount of CKB or UDT to fund the channel with * shutdownscript - Option, The script used to receive the channel balance, an optional parameter, default value is the secp256k1blake160sighashall script corresponding to the configured private key * maxtlcvalueinflight - Option, The max tlc sum value in flight for the channel, default is u128::MAX This parameter can not be updated after channel is opened. * maxtlcnumberinflight - Option, The max tlc number in flight send from our side, default is 125 This parameter can not be updated after channel is opened. * tlcminvalue - Option, The minimum value for a TLC our side can send, an optional parameter, default is 0, which means we can send any TLC is larger than 0. This parameter can be updated with rpc update_channel later. * tlcfeeproportionalmillionths - Option, The fee proportional millionths for a TLC, proportional to the amount of the forwarded tlc. The unit is millionths of the amount. default is 1000 which means 0.1%. This parameter can be updated with rpc updatechannel later. Not that, we use outbound channel to calculate the fee for TLC forwarding. For example, if we have a path A -> B -> C, then the fee B requires for TLC forwarding, is calculated the channel configuration of B and C, not A and B. * tlcexpirydelta - Option, The expiry delta to forward a tlc, in milliseconds, default to 1 day, which is 24 60 60 * 1000 milliseconds This parameter can be updated with rpc update_channel later. #### Returns * channel_id - Hash256, The final ID of the channel that was accepted, it's different from the temporary channel ID *** ### Method abandon_channel Abandon a channel, this will remove the channel from the channel manager and DB. Only channels not in Ready or Closed state can be abandoned. #### Params * channel_id - Hash256, The temporary channel ID or real channel ID of the channel being abandoned #### Returns None ** ### Method list_channels Lists all channels. #### Params pubkey - Option, The public key to list channels for. An optional parameter, if not provided, all channels will be listed. include_closed - Option, Whether to include closed channels in the list, an optional parameter, default value is false * onlypending - Option, When set to true, only return channels that are still being opened (non-final states: negotiating, collaborating on funding tx, signing, awaiting tx signatures, awaiting channel ready) as well as channels whose opening attempt failed. Default is false. Mutually exclusive with includeclosed. #### Returns channels - Vec, The list of channels ** ### Method shutdown_channel Shuts down a channel. #### Params * channel_id - Hash256, The channel ID of the channel to shut down * closescript - Option, The script used to receive the channel balance, only support secp256k1blake160sighashall script for now default is defaultfundinglock_script in CkbConfig * fee_rate - Option, The fee rate for the closing transaction, the fee will be deducted from the closing initiator's channel balance default is 1000 shannons/KW * force - Option, Whether to force the channel to close, when set to false, closescript and feerate should be set, default is false. When set to true, closescript and feerate will be ignored and will use the default value when opening the channel. #### Returns None ** ### Method update_channel Updates a channel. #### Params * channel_id - Hash256, The channel ID of the channel to update enabled - Option, Whether the channel is enabled, default value is true tlcexpirydelta - Option, The expiry delta for the TLC locktime * tlcminimumvalue - Option, The minimum value for a TLC * tlcfeeproportional_millionths - Option, The fee proportional millionths for a TLC #### Returns None ** ### Method openchannelwithexternalfunding Opens a channel with external funding. The node will negotiate the channel with the peer, but the user must sign the funding transaction themselves using their own wallet. This is useful when the user wants to fund a channel from an external wallet rather than having the node sign with its internal key. Returns the final unsigned funding transaction after internal tx collaboration has frozen the structure. The user must sign it and submit it with submitsignedfunding_tx without changing the transaction structure. #### Params * pubkey - Pubkey, The identity public key of the peer to open a channel with. The peer must already be connected through the connect_peer rpc first. * funding_amount - u128, The amount of CKB or UDT to fund the channel with. public - Option, Whether this is a public channel (will be broadcasted to network, and can be used to forward TLCs), an optional parameter, default value is true. fundingudttype_script - Option, The type script of the UDT to fund the channel with, an optional parameter. * shutdown_script - Script, The script used to receive the channel balance when the channel is closed. This is REQUIRED for external funding. * fundinglockscript - Script, The lock script that controls the funding cells. The node will collect cells with this lock script to build the funding transaction. The user must be able to sign for this lock script. * fundinglockscriptcelldeps - Option>, Optional extra cell deps required by fundinglockscript. This is useful for custom wallet lock scripts whose deps are not part of the genesis defaults. * commitmentdelayepoch - Option, The delay time for the commitment transaction, must be an EpochNumberWithFraction in u64 format, an optional parameter, default value is 1 epoch, which is 4 hours. * commitmentfeerate - Option, The fee rate for the commitment transaction, an optional parameter. * fundingfeerate - Option, The fee rate for the funding transaction, an optional parameter. * tlcexpirydelta - Option, The expiry delta to forward a tlc, in milliseconds, default to 4 hours, which is 4 60 60 * 1000 milliseconds Expect it >= 2/3 commitmentdelayepoch. This parameter can be updated with rpc update_channel later. * tlcminvalue - Option, The minimum value for a TLC our side can send, an optional parameter, default is 0, which means we can send any TLC is larger than 0. This parameter can be updated with rpc update_channel later. * tlcfeeproportionalmillionths - Option, The fee proportional millionths for a TLC, proportional to the amount of the forwarded tlc. The unit is millionths of the amount. default is 1000 which means 0.1%. This parameter can be updated with rpc updatechannel later. * maxtlcvalueinflight - Option, The maximum value in flight for TLCs, an optional parameter. This parameter can not be updated after channel is opened. * maxtlcnumberinflight - Option, The maximum number of TLCs that can be accepted, an optional parameter, default is 125 This parameter can not be updated after channel is opened. #### Returns * channel_id - Hash256, The channel ID of the channel being opened. * unsignedfundingtx - Transaction, The final unsigned funding transaction that needs to be signed. *** ### Method submitsignedfundingtx Submits a signed funding transaction for an externally funded channel. After calling openchannelwithexternalfunding, the user signs the returned final negotiated unsigned transaction with their wallet and submits it here. The signed transaction should be directly broadcastable and will not be structurally modified. External signers must keep inputs, outputs, outputsdata, and cell_deps unchanged. See the external funding guide for signing details and examples. #### Params * channelid - Hash256, The channel ID returned from openchannelwithexternal_funding. * signedfundingtx - Transaction, The signed funding transaction. #### Returns * channel_id - Hash256, The channel ID. * fundingtxhash - Hash256, The hash of the funding transaction that was submitted. --- ## Module `Invoice` Source: https://www.fiber.world/docs/api-reference/payments/invoice ## Module Invoice RPC module for invoice management. ### Method new_invoice Generates a new invoice. #### Params amount - u128, The amount of the invoice. description - Option, The description of the invoice. currency - Currency, The currency of the invoice. payment_preimage - Option, The preimage to settle an incoming TLC payable to this invoice. If preimage is set, hash must be absent. If both preimage and hash are absent, a random preimage is generated. * payment_hash - Option, The hash of the preimage. If hash is set, preimage must be absent. This condition indicates a 'hold invoice' for which the tlc must be accepted and held until the preimage becomes known. expiry - Option, The expiry time of the invoice, in seconds. fallback_address - Option, The fallback address of the invoice. * finalexpirydelta - Option, The final HTLC timeout of the invoice, in milliseconds. Minimal value is 16 hours, and maximal value is 14 days. * udttypescript - Option, The UDT type script of the invoice. * hash_algorithm - Option, The hash algorithm of the invoice. * allow_mpp - Option, Whether allow payment to use MPP * allowtrampolinerouting - Option, Whether allow payment to use trampoline routing #### Returns * invoice_address - String, The encoded invoice address. invoice - CkbInvoice, The invoice. ** ### Method parse_invoice Parses a encoded invoice. #### Params invoice - String, The encoded invoice address. #### Returns invoice - CkbInvoice, The invoice. *** ### Method get_invoice Retrieves an invoice. #### Params * payment_hash - Hash256, The payment hash of the invoice. #### Returns * invoice_address - String, The encoded invoice address. invoice - CkbInvoice, The invoice. status - CkbInvoiceStatus, The invoice status *** ### Method cancel_invoice Cancels an invoice, only when invoice is in status Open can be canceled. #### Params * payment_hash - Hash256, The payment hash of the invoice. #### Returns * invoice_address - String, The encoded invoice address. invoice - CkbInvoice, The invoice. status - CkbInvoiceStatus, The invoice status *** ### Method settle_invoice Settles an invoice by saving the preimage to this invoice. #### Params * payment_hash - Hash256, The payment hash of the invoice. * payment_preimage - Hash256, The payment preimage of the invoice. #### Returns * None --- ## Module `Payment` Source: https://www.fiber.world/docs/api-reference/payments/payment ## Module Payment RPC module for channel management. ### Method send_payment Sends a payment to a peer. #### Params * targetpubkey - Option, The public key (Pubkey) of the payment target node, serialized as a hex string. You can obtain a node's pubkey via the nodeinfo or graph_nodes RPC. amount - Option, the amount of the payment, the unit is Shannons for non UDT payment If not set and there is a invoice, the amount will be set to the invoice amount paymenthash - Option, the hash to use within the payment's HTLC. If not set and keysend is set to true, a random hash will be generated. If not set and there is a paymenthash in the invoice, it will be used. Otherwise, payment_hash need to be set. * finaltlcexpiry_delta - Option, the TLC expiry delta should be used to set the timelock for the final hop, in milliseconds * tlcexpirylimit - Option, the TLC expiry limit for the whole payment, in milliseconds, each hop is with a default tlc delta of 1 day suppose the payment router is with N hops, the total tlc expiry limit is at least (N-1) days this is also the default value for the payment if this parameter is not provided invoice - Option, the encoded invoice to send to the recipient timeout - Option, the payment timeout in seconds, if the payment is not completed within this time, it will be cancelled * maxfeeamount - Option, the maximum fee amounts in shannons that the sender is willing to pay. Note: In trampoline routing mode, the sender will use the maxfeeamount as the total fee as much as possible. * maxfeerate - Option, the maximum fee rate per thousand, default is 5 (0.5%) * max_parts - Option, max parts for the payment, only used for multi-part payments * trampoline_hops - Option>, Optional explicit trampoline hops. When set to a non-empty list [t1, t2, ...], routing will only find a path from the payer to t1, and the inner trampoline onion will encode t1 -> t2 -> ... -> final. keysend - Option, keysend payment udttypescript - Option, udt type script for the payment * allowselfpayment - Option, Allow paying yourself through a circular route, default is false. This is useful for channel rebalancing: the payment flows out of one channel and back through another, shifting liquidity between your channels without changing your total balance (only routing fees are deducted). Set targetpubkey to your own node pubkey and keysend to true to perform a rebalance. Note: allowself_payment is not compatible with trampoline routing. * custom_records - Option, Some custom records for the payment which contains a map of u32 to Vec<u8> The key is the record type, and the value is the serialized data For example: [json code block] * hophints - Option>, Optional route hints to reach the destination through private channels. Note: 1. this is only used for the private channels with the last hop. 2. hophints is only a hint for routing algorithm, it is not a guarantee that the payment will be routed through the specified channels, it is up to the routing algorithm to decide whether to use the hints or not. For example (pubkey, channeloutpoint, feerate, tlcexpirydelta) suggest path router to use the channel of channeloutpoint at hop with pubkey to forward the payment and the fee rate is feerate and tlcexpirydelta is tlcexpirydelta. * dryrun - Option, dryrun for payment, used for check whether we can build valid router and the fee for this payment, it's useful for the sender to double check the payment before sending it to the network, default is false #### Returns * payment_hash - Hash256, The payment hash of the payment * payment_preimage - Option, The preimage learned from a successful payment attempt. status - PaymentStatus, The status of the payment created_at - u64, The time the payment was created at, in milliseconds from UNIX epoch * lastupdatedat - u64, The time the payment was last updated at, in milliseconds from UNIX epoch * failed_error - Option, The error message if the payment failed fee - u128, fee paid for the payment custom_records - Option, The custom records to be included in the payment. routers - Vec, The router is a list of nodes that the payment will go through. We store in the payment session and then will use it to track the payment history. If the payment adapted MPP (multi-part payment), the routers will be a list of nodes. For example: A(amount, channel) -> B -> C -> D means A will send amount with channel to B. ** ### Method get_payment Retrieves a payment. #### Params * payment_hash - Hash256, The payment hash of the payment to retrieve #### Returns * payment_hash - Hash256, The payment hash of the payment * payment_preimage - Option, The preimage learned from a successful payment attempt. status - PaymentStatus, The status of the payment created_at - u64, The time the payment was created at, in milliseconds from UNIX epoch * lastupdatedat - u64, The time the payment was last updated at, in milliseconds from UNIX epoch * failed_error - Option, The error message if the payment failed fee - u128, fee paid for the payment custom_records - Option, The custom records to be included in the payment. routers - Vec, The router is a list of nodes that the payment will go through. We store in the payment session and then will use it to track the payment history. If the payment adapted MPP (multi-part payment), the routers will be a list of nodes. For example: A(amount, channel) -> B -> C -> D means A will send amount with channel to B. ** ### Method build_router Builds a router with a list of pubkeys and required channels. #### Params amount - Option, the amount of the payment, the unit is Shannons for non UDT payment If not set, the minimum routable amount 1 is used udttypescript - Option, udt type script for the payment router * hops_info - Vec, A list of hops that defines the route. This does not include the source hop pubkey. A hop info is a tuple of pubkey and the channel(specified by channel funding tx) will be used. This is a strong restriction given on payment router, which means these specified hops and channels must be adapted in the router. This is different from hop hints, which maybe ignored by find path. If channel is not specified, find path algorithm will pick a channel within these two peers. An error will be returned if there is no router could be build from given hops and channels * finaltlcexpiry_delta - Option, the TLC expiry delta should be used to set the timelock for the final hop, in milliseconds #### Returns * router_hops - Vec, The hops information for router *** ### Method sendpaymentwith_router Sends a payment to a peer with specified router. This method differs from SendPayment in that it allows users to specify a full route manually. A typical use case is channel rebalancing: you can construct a circular route (your node -> intermediate nodes -> your node) to shift liquidity between your channels. To rebalance, follow these steps: 1. Call buildrouter with hopsinfo defining the circular route you want, e.g. yournode -> peerA -> peerB -> yournode. 2. Call sendpaymentwithrouter with the returned routerhops and keysend: true. Only routing fees are deducted; your total balance across channels remains the same. #### Params * paymenthash - Option, the hash to use within the payment's HTLC. If not set and keysend is set to true, a random hash will be generated. If not set and there is a paymenthash in the invoice, it will be used. Otherwise, payment_hash need to be set. router - Vec, The router to use for the payment invoice - Option, the encoded invoice to send to the recipient * custom_records - Option, Some custom records for the payment which contains a map of u32 to Vec<u8> The key is the record type, and the value is the serialized data. Limits: the sum size of values can not exceed 2048 bytes. For example: [json code block] keysend - Option, keysend payment udttypescript - Option, udt type script for the payment * dryrun - Option, dryrun for payment, used for check whether we can build valid router and the fee for this payment, it's useful for the sender to double check the payment before sending it to the network, default is false #### Returns * payment_hash - Hash256, The payment hash of the payment * payment_preimage - Option, The preimage learned from a successful payment attempt. status - PaymentStatus, The status of the payment created_at - u64, The time the payment was created at, in milliseconds from UNIX epoch * lastupdatedat - u64, The time the payment was last updated at, in milliseconds from UNIX epoch * failed_error - Option, The error message if the payment failed fee - u128, fee paid for the payment custom_records - Option, The custom records to be included in the payment. routers - Vec, The router is a list of nodes that the payment will go through. We store in the payment session and then will use it to track the payment history. If the payment adapted MPP (multi-part payment), the routers will be a list of nodes. For example: A(amount, channel) -> B -> C -> D means A will send amount with channel to B. ** ### Method list_payments Lists all payments, optionally filtered by status. #### Params status - Option, Filter payments by status. If not set, all payments are returned. limit - Option, The maximum number of payments to return. Default is 15. after - Option, The payment hash to start returning payments after (exclusive cursor for pagination). #### Returns payments - Vec, The list of payments. * last_cursor - Option, The last cursor for pagination. Use this as after in the next request to get more results. --- ## Module `Graph` Source: https://www.fiber.world/docs/api-reference/network/graph ## Module Graph RPC module for graph management. ### Method graph_nodes Get the list of nodes in the network graph. #### Params limit - Option, The maximum number of nodes to return. after - Option, The cursor to start returning nodes from. #### Returns nodes - Vec, The list of nodes. last_cursor - JsonBytes, The last cursor. *** ### Method graph_channels Get the list of channels in the network graph. #### Params limit - Option, The maximum number of channels to return. after - Option, The cursor to start returning channels from. #### Returns channels - Vec, A list of channels. last_cursor - JsonBytes, The last cursor for pagination. --- ## Module `Cch` Source: https://www.fiber.world/docs/api-reference/cross-chain/cch ## Module Cch RPC module for cross chain hub demonstration. > Testnet cWBTC Faucet: faucet-cwbtc.ckb.dev provides free cWBTC test tokens for CCH development. See the Cross-Chain HTLC guide for setup and usage. ### Method send_btc Creates a CCH order for a BTC Lightning payee. #### Params * btcpayreq - String, Payment request string for the BTC Lightning payee. currency - Currency, Request currency #### Returns timestamp - u64, Seconds since epoch when the order is created * expirydeltaseconds - u64, Relative expiry time in seconds from created_at that the order expires * wrappedbtctype_script - Script, Wrapped BTC type script * incoming_invoice - CchInvoice, Generated invoice for the incoming payment * outgoingpayreq - String, The final payee to accept the payment. It has the different network with incoming invoice. * payment_hash - Hash256, Payment hash for the HTLC for both CKB and BTC. * amount_sats - u128, Amount required to pay in Satoshis, including fee * fee_sats - u128, Fee in Satoshis status - CchOrderStatus, Order status ** ### Method receive_btc Creates a CCH order for a CKB Fiber payee. #### Params * fiberpayreq - String, Payment request string for the CKB Fiber payee. #### Returns timestamp - u64, Seconds since epoch when the order is created expirydeltaseconds - u64, Relative expiry time in seconds from created_at that the order expires * wrappedbtctype_script - Script, Wrapped BTC type script * incoming_invoice - CchInvoice, Generated invoice for the incoming payment * outgoingpayreq - String, The final payee to accept the payment. It has the different network with incoming invoice. * payment_hash - Hash256, Payment hash for the HTLC for both CKB and BTC. * amount_sats - u128, Amount required to pay in Satoshis, including fee * fee_sats - u128, Fee in Satoshis status - CchOrderStatus, Order status ** ### Method getcchorder Get a CCH order by payment hash. #### Params * payment_hash - Hash256, Payment hash for the HTLC for both CKB and BTC. #### Returns timestamp - u64, Seconds since epoch when the order is created expirydeltaseconds - u64, Relative expiry time in seconds from created_at that the order expires * wrappedbtctype_script - Script, Wrapped BTC type script * incoming_invoice - CchInvoice, Generated invoice for the incoming payment * outgoingpayreq - String, The final payee to accept the payment. It has the different network with incoming invoice. * payment_hash - Hash256, Payment hash for the HTLC for both CKB and BTC. * amount_sats - u128, Amount required to pay in Satoshis, including fee * fee_sats - u128, Fee in Satoshis * status - CchOrderStatus, Order status --- ## Module `Dev` Source: https://www.fiber.world/docs/api-reference/dev-tooling/dev ## Module Dev RPC module for development purposes, this module is not intended to be used in production. This module will be disabled in release build. ### Method commitmentsigned Sends a commitmentsigned message to the peer. #### Params * channelid - Hash256, The channel ID of the channel to send the commitmentsigned message to #### Returns None ** ### Method add_tlc Adds a TLC to a channel. #### Params * channel_id - Hash256, The channel ID of the channel to add the TLC to amount - u128, The amount of the TLC payment_hash - Hash256, The payment hash of the TLC expiry - u64, The expiry of the TLC hash_algorithm - Option, The hash algorithm of the TLC #### Returns * tlc_id - u64, The ID of the TLC *** ### Method remove_tlc Removes a TLC from a channel. #### Params * channel_id - Hash256, The channel ID of the channel to remove the TLC from * tlc_id - u64, The ID of the TLC to remove reason - RemoveTlcReason, The reason for removing the TLC, either a 32-byte hash for preimage fulfillment or an u32 error code for removal #### Returns None *** ### Method submitcommitmenttransaction Submit a commitment transaction to the chain #### Params * channel_id - Hash256, Channel ID * commitment_number - u64, Commitment number #### Returns * tx_hash - Hash256, Submitted commitment transaction hash *** ### Method checkchannelshutdown Manually trigger CheckShutdownTx on all channels #### Params * channel_id - Hash256, Channel ID #### Returns None ** ### Method signexternalfundingtx Sign an external funding transaction with a provided private key. This is a development-only RPC that signs an unsigned funding transaction (returned from openchannelwithexternalfunding) using the provided private key. The signed transaction can then be submitted via submitsignedfundingtx. #### Params * unsignedfundingtx - ckbjsonrpctypes::Transaction, The unsigned funding transaction returned from openchannelwithexternalfunding. * private_key - String, The private key to sign the transaction, as a 0x-prefixed 32-byte hex string. Note: This is a development-only RPC and the private key is provided directly. #### Returns * signedfundingtx - ckbjsonrpctypes::Transaction, The signed funding transaction that can be submitted via submitsignedfunding_tx. --- ## Module `Prof` Source: https://www.fiber.world/docs/api-reference/dev-tooling/prof ## Module Prof RPC module for profiling This module require build with pprof feature and debug symbol. ### Method pprof Collects a temporary CPU profile and writes a flamegraph SVG to disk. #### Params * duration_secs - Option, Duration to profile in seconds. Defaults 10s. #### Returns * path - String, Path of the generated flamegraph SVG. --- ## Module `Watchtower` Source: https://www.fiber.world/docs/api-reference/dev-tooling/watchtower ## Module Watchtower RPC module for watchtower related operations ### Method createwatchchannel Create a new watched channel #### Params * channel_id - Hash256, Channel ID * fundingudttype_script - Option, Funding UDT type script * localsettlementkey - Privkey, The local party's private key used to settle the commitment transaction (hex without 0x prefix) * remotesettlementkey - Pubkey, The remote party's public key used to settle the commitment transaction (hex without 0x prefix) * localfundingpubkey - Pubkey, The local party's funding public key (hex without 0x prefix) * remotefundingpubkey - Pubkey, The remote party's funding public key (hex without 0x prefix) * settlement_data - SettlementData, Settlement data #### Returns None ** ### Method removewatchchannel Remove a watched channel #### Params * channel_id - Hash256, Channel ID #### Returns None ** ### Method update_revocation Update revocation #### Params * channel_id - Hash256, Channel ID * revocation_data - RevocationData, Revocation data * settlement_data - SettlementData, Settlement data #### Returns None ** ### Method updatependingremote_settlement Update pending remote settlement #### Params * channel_id - Hash256, Channel ID * settlement_data - SettlementData, Settlement data #### Returns None ** ### Method updatelocalsettlement Update settlement #### Params * channel_id - Hash256, Channel ID * settlement_data - SettlementData, Settlement data #### Returns None ** ### Method create_preimage Create preimage #### Params * payment_hash - Hash256, Payment hash preimage - Hash256, Preimage #### Returns None *** ### Method remove_preimage Remove preimage #### Params * payment_hash - Hash256, Payment hash #### Returns * None --- ## Types Reference Source: https://www.fiber.world/docs/api-reference/types/types ## Types Reference ### Type Attribute The attributes of the invoice. #### Enum with values of * finalhtlctimeout - u64, This attribute is deprecated since v0.6.0, The final tlc time out, in milliseconds * finalhtlcminimumexpirydelta - u64, The final tlc minimum expiry delta, in milliseconds, default is 1 day * expiry_time - std::time::Duration, The expiry time of the invoice, in seconds description - String, The description of the invoice fallback_addr - String, The fallback address of the invoice * udt_script - String, The udt type script of the invoice (serialized as 0x-prefixed hex of molecule bytes) * payeepublickey - Pubkey, The payee public key of the invoice (validated compressed secp256k1 key, hex without 0x prefix) * hash_algorithm - HashAlgorithm, The hash algorithm of the invoice feature - Vec, The feature flags of the invoice payment_secret - String, The payment secret of the invoice ** ### Type CchInvoice The generated proxy invoice for the incoming payment. The JSON representation: [text code block] #### Enum with values of Fiber - String, Fiber invoice string Lightning - String, Lightning invoice string * ### Type CchOrderStatus The status of a cross-chain hub order, will update as the order progresses. #### Enum with values of Pending - Order is created and waiting for the incoming invoice to collect enough TLCs. IncomingAccepted - The incoming invoice collected the required TLCs and is ready to send outgoing payment to obtain the preimage. OutgoingInFlight - The outgoing payment is in flight. OutgoingSuccess - The outgoing payment is settled and preimage has been obtained. Success - Both payments are settled and the order succeeds. Failed - Order is failed. * ### Type Channel The channel data structure. #### Fields channel_id - Hash256, The channel ID * is_public - bool, Whether the channel is public * is_acceptor - bool, Is this channel initially inbound? An inbound channel is one where the counterparty is the funder of the channel. * isoneway - bool, Is this channel one-way? Combines with is_acceptor to determine if the channel able to send payment to the counterparty or not. * channel_outpoint - Option, The outpoint of the channel pubkey - Pubkey, The public key of the channel counterparty. fundingudttype_script - Option, The UDT type script of the channel state - ChannelState, The state of the channel local_balance - u128, The local balance of the channel * offeredtlcbalance - u128, The offered balance of the channel * remote_balance - u128, The remote balance of the channel * receivedtlcbalance - u128, The received balance of the channel * pending_tlcs - Vec, The list of pending tlcs * latestcommitmenttransaction_hash - Option, The hash of the latest commitment transaction * created_at - u64, The time the channel was created at, in milliseconds from UNIX epoch enabled - bool, Whether the channel is enabled tlcexpirydelta - u64, The expiry delta to forward a tlc, in milliseconds, default to 1 day, which is 24 60 60 * 1000 milliseconds This parameter can be updated with rpc update_channel later. * tlcfeeproportionalmillionths - u128, The fee proportional millionths for a TLC, proportional to the amount of the forwarded tlc. The unit is millionths of the amount. default is 1000 which means 0.1%. This parameter can be updated with rpc updatechannel later. Not that, we use outbound channel to calculate the fee for TLC forwarding. For example, if we have a path A -> B -> C, then the fee B requires for TLC forwarding, is calculated the channel configuration of B and C, not A and B. * shutdowntransactionhash - Option, The hash of the shutdown transaction * failure_detail - Option, Human-readable reason why the channel opening failed. Only present when the channel is in a failed state (e.g. abandoned or funding aborted). ** ### Type ChannelInfo The Channel information. #### Fields channel_outpoint - OutPoint, The outpoint of the channel. node1 - Pubkey, The identity public key of the first node (secp256k1 compressed, hex string). node2 - Pubkey, The identity public key of the second node (secp256k1 compressed, hex string). * created_timestamp - u64, The created timestamp of the channel, which is the block header timestamp of the block that contains the channel funding transaction. * updateinfoofnode1 - Option, The update info from node1 to node2, e.g. timestamp, feerate, tlcexpirydelta, tlcminimumvalue * updateinfoofnode2 - Option, The update info from node2 to node1, e.g. timestamp, feerate, tlcexpirydelta, tlcminimumvalue capacity - u128, The capacity of the channel. chain_hash - Hash256, The chain hash of the channel. * udttypescript - Option, The UDT type script of the channel. ** ### Type ChannelState The state of a channel. Serialized with adjacently-tagged representation using PascalCase variant names and flags. This is different from the internal ChannelState in fiber-types which uses default serde for bincode compatibility. #### Enum with values of NegotiatingFunding - NegotiatingFundingFlags, We are negotiating the parameters required for the channel prior to funding it. For channels opened with external funding, this state is also used together with NegotiatingFundingFlags::AWAITINGEXTERNALFUNDING to indicate that we are waiting for the user to sign and submit the funding transaction externally. CollaboratingFundingTx - CollaboratingFundingTxFlags, We're collaborating with the other party on the funding transaction. SigningCommitment - SigningCommitmentFlags, We have collaborated over the funding and are now waiting for CommitmentSigned messages. * AwaitingTxSignatures - AwaitingTxSignaturesFlags, We've received and sent commitment_signed and are now waiting for both party to collaborate on creating a valid funding transaction. * AwaitingChannelReady - AwaitingChannelReadyFlags, We've received/sent fundingcreated and fundingsigned and are thus now waiting on the funding transaction to confirm. ChannelReady - Both we and our counterparty consider the funding transaction confirmed and the channel is now operational. ShuttingDown - ShuttingDownFlags, We've successfully negotiated a closing_signed dance. At this point, the ChannelManager Closed - CloseFlags, This channel is closed. Stale - The channel state may be outdated after a database restore. The node must passively audit the channel with its peer before resuming normal operation. ** ### Type ChannelUpdateInfo The channel update info with a single direction of channel. #### Fields timestamp - u64, The timestamp is the time when the channel update was received by the node. enabled - bool, Whether the channel can be currently used for payments (in this one direction). outbound_liquidity - Option, The exact amount of balance that we can send to the other party via the channel. * tlcexpirydelta - u64, The difference in htlc expiry values that you must have when routing through this channel (in milliseconds). * tlcminimumvalue - u128, The minimum value, which must be relayed to the next hop via the channel * fee_rate - u64, The forwarding fee rate for the channel. *** ### Type CkbInvoice Represents a syntactically and semantically correct lightning BOLT11 invoice. There are three ways to construct a CkbInvoice: 1. using [CkbInvoiceBuilder] 2. using str::parse::(&str) (see [CkbInvoice::from_str]) #### Fields currency - Currency, The currency of the invoice amount - Option, The amount of the invoice signature - Option, The signature of the invoice (hex encoded) data - InvoiceData, The invoice data, including the payment hash, timestamp and other attributes ** ### Type CkbInvoiceStatus The status of an invoice. #### Enum with values of Open - The invoice is open and can be paid. Cancelled - The invoice is cancelled. Expired - The invoice is expired. Received - The invoice is received, but not settled yet. Paid - The invoice is paid. ** ### Type Currency The currency of the invoice, can also used to represent the CKB network chain. #### Enum with values of Fibb - The mainnet currency of CKB. Fibt - The testnet currency of the CKB network. Fibd - The devnet currency of the CKB network. *** ### Type GetPaymentCommandResult The result of a get_payment command, which includes the payment hash, status, timestamps, error message if failed, fee paid, and custom records. #### Fields * payment_hash - Hash256, The payment hash of the payment * payment_preimage - Option, The preimage learned from a successful payment attempt. status - PaymentStatus, The status of the payment created_at - u64, The time the payment was created at, in milliseconds from UNIX epoch * lastupdatedat - u64, The time the payment was last updated at, in milliseconds from UNIX epoch * failed_error - Option, The error message if the payment failed fee - u128, fee paid for the payment custom_records - Option, The custom records to be included in the payment. routers - Vec, The router is a list of nodes that the payment will go through. We store in the payment session and then will use it to track the payment history. If the payment adapted MPP (multi-part payment), the routers will be a list of nodes. For example: A(amount, channel) -> B -> C -> D means A will send amount with channel to B. ### Type Hash256 A 256-bit hash (32 bytes), serialized as 0x-prefixed hex string. On deserialization, both 0x-prefixed and non-prefixed hex strings are accepted. No domain-specific validation is performed — the only check is hex format and 32-byte length. ### Type HashAlgorithm HashAlgorithm is the hash algorithm used in the hash lock. #### Enum with values of ckb_hash - The default hash algorithm, CkbHash sha256 - The sha256 hash algorithm * ### Type HopHint A hop hint is a hint for a node to use a specific channel. #### Fields pubkey - Pubkey, The public key of the node * channel_outpoint - OutPoint, The outpoint of the channel * fee_rate - u64, The fee rate to use this hop to forward the payment. * tlcexpirydelta - u64, The TLC expiry delta to use this hop to forward the payment. ** ### Type HopRequire A hop requirement to meet when building a router. Does not include the source node; the last hop is the target node. #### Fields pubkey - Pubkey, The public key of the node * channeloutpoint - Option, The outpoint for the channel, which means use channel with channeloutpoint to reach this node ** ### Type Htlc The htlc data structure. #### Fields id - u64, The id of the htlc amount - u128, The amount of the htlc payment_hash - Hash256, The payment hash of the htlc expiry - u64, The expiry of the htlc forwardingchannelid - Option, If this HTLC is involved in a forwarding operation, this field indicates the forwarding channel. For an outbound htlc, it is the inbound channel. For an inbound htlc, it is the outbound channel. * forwardingtlcid - Option, If this HTLC is involved in a forwarding operation, this field indicates the forwarding tlc id. status - TlcStatus, The status of the htlc * ### Type InboundTlcStatus The status of an inbound tlc. #### Enum with values of RemoteAnnounced - Received tlc from remote party, but not committed yet AnnounceWaitPrevAck - We received another AddTlc peer message when we are waiting for the ack of the last one. AnnounceWaitAck - We have sent commitment signed to peer and waiting ACK for confirming this AddTlc Committed - We have received ACK from peer and Committed this tlc LocalRemoved - We have removed this tlc, but haven't received ACK from peer RemoveAckConfirmed - We have received the ACK for the RemoveTlc, it's safe to remove this tlc * ### Type InvoiceData The metadata of the invoice. #### Fields timestamp - u128, The timestamp of the invoice * payment_hash - Hash256, The payment hash of the invoice attrs - Vec, The attributes of the invoice, e.g. description, expiry time, etc. * ### Type NodeInfo The Node information. #### Fields node_name - String, The name of the node. version - String, The version of the node. addresses - Vec, The addresses of the node (serialized as strings). features - Vec, The node features supported by the node. pubkey - Pubkey, The identity public key of the node (secp256k1 compressed, hex string), same as pubkey in list_peers. timestamp - u64, The latest timestamp set by the owner for the node announcement. When a Node is online this timestamp will be updated to the latest value. chain_hash - Hash256, The chain hash of the node. * autoacceptminckbfunding_amount - u64, The minimum CKB funding amount for automatically accepting open channel requests. * udtcfginfos - UdtCfgInfos, The UDT configuration infos of the node. ** ### Type OutboundTlcStatus The status of an outbound tlc. #### Enum with values of LocalAnnounced - Offered tlc created and sent to remote party Committed - Received ACK from remote party for this offered tlc RemoteRemoved - Remote party removed this tlc RemoveWaitPrevAck - We received another RemoveTlc message from peer when we are waiting for the ack of the last one. RemoveWaitAck - We have sent commitment signed to peer and waiting ACK for confirming this RemoveTlc RemoveAckConfirmed - We have received the ACK for the RemoveTlc, it's safe to remove this tlc * ### Type PaymentCustomRecords The custom records to be included in the payment. The key is hex encoded of u32, it's range limited in 0 ~ 65535, and the value is hex encoded of Vec with 0x as prefix. For example: [json code block] #### Fields data - HashMap>, The custom records to be included in the payment. * ### Type PaymentStatus The status of a payment, will update as the payment progresses. The transfer path for payment status is Created -> Inflight -> Success | Failed. MPP Behavior*: A single session may involve multiple attempts (HTLCs) to fulfill the total amount. #### Enum with values of Created - Initial status. A payment session is created, but no HTLC has been dispatched. Inflight - The first hop AddTlc is sent successfully and waiting for the response. Success - The payment is finished. All related HTLCs are successfully settled. Failed - The payment session has terminated. * ### Type PeerInfo The information about a peer connected to the node. #### Fields pubkey - Pubkey, The identity public key of the peer. * address - String, The multi-address associated with the connecting peer (as a string). Note: this is only the address which used for connecting to the peer, not all addresses of the peer. The graph_nodes in Graph rpc module will return all addresses of the peer. ### Type Privkey A private key byte array (32 bytes), serialized as hex without 0x prefix. On deserialization, only hex format and 32-byte length are checked. Both 0x-prefixed and non-prefixed hex strings are accepted on input. Cryptographic validation is left to the RPC layer's conversion to internal Privkey. ### Type Pubkey A compressed public key (33 bytes), serialized as hex without 0x prefix. On deserialization, only hex format and 33-byte length are checked (no secp256k1 validation). Both 0x-prefixed and non-prefixed hex strings are accepted on input. Cryptographic validation is left to the RPC layer's conversion to internal Pubkey. ** ### Type RemoveTlcReason The reason for removing a TLC. #### Enum with values of RemoveTlcFulfill - The reason for removing the TLC is that it was fulfilled RemoveTlcFail - The reason for removing the TLC is that it failed * ### Type RevocationData Data needed to revoke an outdated commitment transaction. #### Fields commitment_number - u64, The commitment transaction version number that was revoked * aggregated_signature - Vec, The aggregated signature from both parties that authorizes the revocation (hex string, 64 bytes) output - CellOutput, The output cell from the revoked commitment transaction (hex-encoded molecule bytes) output_data - Bytes, The associated data for the output cell (e.g., UDT amount for token transfers, hex-encoded molecule bytes) *** ### Type RouterHop A router hop information for a payment, a paymenter router is an array of RouterHop, a router hop generally implies hop target will receive amountreceived with channeloutpoint of channel. #### Fields target - Pubkey, The node that is sending the TLC to the next node. channel_outpoint - OutPoint, The channel of this hop used to receive TLC * amount_received - u128, The amount that the source node will transfer to the target node. * incomingtlcexpiry - u64, The expiry for the TLC that the source node sends to the target node. ** ### Type SessionRoute The router is a list of nodes that the payment will go through. #### Fields nodes - Vec, The nodes in the route ** ### Type SessionRouteNode The node and channel information in a payment route hop. #### Fields pubkey - Pubkey, The public key of the node amount - u128, The amount for this hop channel_outpoint - OutPoint, The channel outpoint for this hop ** ### Type SettlementData Data needed to authorize and execute a settlement transaction. #### Fields local_amount - u128, The total amount of CKB/UDT being settled for the local party * remote_amount - u128, The total amount of CKB/UDT being settled for the remote party tlcs - Vec, The list of pending Time-Locked Contracts (TLCs) included in this settlement * ### Type SettlementTlc Data needed to authorize and execute a Time-Locked Contract (TLC) settlement transaction. #### Fields tlc_id - TLCId, The ID of the TLC (either offered or received) * hash_algorithm - HashAlgorithm, The hash algorithm used for the TLC * payment_amount - u128, The amount of CKB/UDT involved in the TLC * payment_hash - Hash256, The hash of the payment preimage expiry - u64, The expiry time for the TLC in milliseconds local_key - Privkey, The local party's private key used to sign the TLC (hex without 0x prefix) * remote_key - Pubkey, The remote party's public key used to verify the TLC (hex without 0x prefix) ** ### Type TLCId The id of a TLC, it can be either offered or received. #### Enum with values of Offered - u64, Offered TLC id Received - u64, Received TLC id * ### Type TlcStatus The status of a tlc. #### Enum with values of Outbound - OutboundTlcStatus, Outbound tlc Inbound - InboundTlcStatus, Inbound tlc * ### Type TransportType The type of transport to filter by when resolving peer addresses. #### Enum with values of tcp - TCP transport (e.g. /ip4/1.2.3.4/tcp/8080) ws - WebSocket transport (e.g. /ip4/1.2.3.4/tcp/8080/ws) wss - WebSocket Secure transport (e.g. /dns/example.com/tcp/443/wss) ** ### Type UdtArgInfo The UDT argument info which is used to identify the UDT configuration. #### Fields name - String, The name of the UDT. script - UdtScript, The script of the UDT. autoacceptamount - Option, The minimum amount of the UDT that can be automatically accepted. * cell_deps - Vec, The cell deps of the UDT. ** ### Type UdtCellDep The UDT cell dep which is used to identify the UDT configuration for a Fiber Node. #### Fields out_point - OutPointWrapper, The out point of the cell dep. * dep_type - DepType, The type of the cell dep. ### Type UdtCfgInfos A list of UDT configuration infos. ### Type UdtDep Udt script on-chain dependencies. #### Fields * celldep - Option, cell dep described by outpoint. * type_id - Option, cell dep described by type ID. ** ### Type UdtScript The UDT script which is used to identify the UDT configuration for a Fiber Node. #### Fields code_hash - H256, The code hash of the script. * hash_type - ScriptHashType, The hash type of the script. args - String, The arguments of the script. ** --- ## Troubleshooting Source: https://www.fiber.world/docs/faq/troubleshooting This page is a work in progress. More common errors and solutions will be added over time. If you encounter an issue not listed here, please open an issue on GitHub. This guide covers common errors you may encounter when running a Fiber node, along with their causes and solutions. ## Payment Errors ### Failed to Build Route Symptom: send_payment returns an error indicating no route could be found. Possible causes: - No channel path exists between you and the target node with sufficient liquidity. - Your local balance is too low in all available channels. - The target node is not reachable in the network graph. Solutions: - Verify your channels have sufficient local balance using listchannels. - Check that the target node is in the network graph using graphnodes. - Open a channel to a well-connected public node to improve reachability. ### Payment Timeout Symptom: A payment stays in Inflight state and never completes. Possible causes: - An intermediate node on the route is offline. - The route has insufficient time locks for the number of hops. Solutions: - Wait for the payment to automatically fail and retry. - Try a different route using buildrouter and sendpaymentwithrouter. ### Fee Too High Symptom: send_payment fails with status Failed and a fee-related error. Possible causes: - The cumulative relay fees along the route exceed your maxfeeamount or maxfeerate budget. - The route is longer than expected, accumulating more fees per hop. Solutions: - Increase maxfeeamount or maxfeerate in your sendpayment call. - Use dryrun: true to preview the route and estimated fees before committing funds. - Try a shorter route by specifying explicit hops with build_router. See Payment Lifecycle for details on how the fee budget works. ### Partial Payment Delivered Symptom: A multi-path payment (MPP) shows Inflight with no progress — some parts have succeeded while others are stuck or failed. Possible causes: - Some routes have sufficient liquidity while others do not. - One or more attempts failed with retryable errors, and the retry limit has been reached. Solutions: - Check failederror on the payment to identify which attempt failed. - Wait for the system's automatic retry mechanism to find alternate paths. - If the payment times out, retry with a higher maxparts value to increase route diversity. ### Invoice Amount Mismatch Symptom: send_payment fails with IncorrectOrUnknownPaymentDetails or Failed. Possible causes: - The invoice amount does not match what the sender expected to pay. - The invoice has expired (current time exceeds timestamp + expiry). - The payment hash does not correspond to any known invoice on the recipient's node. Solutions: - Verify the invoice amount and expiry using parseinvoice before paying. - If the invoice has expired, request a new one from the recipient with a longer expiry. - Confirm the payment hash matches the invoice you intend to pay. ### Payment Error Codes Reference When a payment fails, the failederror field contains a decoded error code from the failing hop. The most common codes are: | Error Code | Meaning | Typical Action | |------------|---------|---------------| | TemporaryChannelFailure | Channel liquidity exhausted | Retry — the system automatically finds an alternate path | | TemporaryNodeFailure | Node temporarily unavailable | Retry after a brief delay | | FeeInsufficient | Forwarded amount does not cover the relay node's fee | Increase maxfeeamount or recalculate amounts | | ExpiryTooSoon | Expiry window too narrow for remaining hops | Increase tlcexpirydelta | | IncorrectOrUnknownPaymentDetails | Invoice mismatch at the recipient | Check invoice amount, hash, and expiry | | PermanentChannelFailure | Channel closed or unavailable | The system removes it from the graph and retries | | RequiredNodeFeatureMissing | A node does not support a required feature (e.g., trampoline routing) | Use a different route or trampoline node | See Multi-Hop Payments for a full explanation of error handling and retry behavior. ## Channel Errors ### Channel Stuck in Awaiting State Symptom: Channel remains in AwaitingLockin or similar state after opening. Possible causes: - The funding transaction has not been confirmed on-chain yet. - The peer is offline and cannot exchange channel_ready messages. Solutions: - Check the funding transaction status on a CKB explorer. - Ensure the peer is connected using list_peers. ### Cannot Open Channel Symptom: open_channel fails immediately. Possible causes: - You are not connected to the target peer. - Insufficient CKB balance for the funding amount plus channel reserve (99 CKB per side). - The peer has rejected the channel opening request. Solutions: - Connect to the peer first using connectpeer. - Verify your CKB balance is sufficient. - Check the peer's autoaccept configuration. ### One-Way Channel Cannot Be Public Symptom: openchannel with both oneway: true and public: true fails with "An one-way channel cannot be public". Cause: Unidirectional channels are always private. The node enforces this restriction because one-way channels cannot route third-party payments, so advertising them in the network graph would be misleading. Solution: - Remove the public: true flag. One-way channels must be private. - If you need a public channel that can route payments in both directions, use a standard bidirectional channel instead. ### Reverse Payment Fails on One-Way Channel Symptom: send_payment from the acceptor back to the initiator on a one-way channel fails with Failed to build route. Cause: In a unidirectional channel, funds can only flow from initiator to acceptor. The routing engine cannot find a valid path for a reverse-direction payment. Solutions: - Open a separate channel in the reverse direction if the acceptor needs to pay the initiator. - Use a bidirectional channel if you expect payments to flow in both directions. See Unidirectional Channel for the full specification and use-case trade-offs. ## Invoice Errors ### Settling Too Early Symptom: settle_invoice returns InvoiceStillOpen. Cause: The invoice is still in the Open state — no incoming TLC has arrived yet. You can only settle a hold invoice once a payment has reached your node and the invoice transitions to Received. Solutions: - Wait for the sender's payment to propagate through the network and reach your node. - Verify the sender has actually called sendpayment and check the payment status on their side. - Use getinvoice to monitor the invoice status; settle once it shows Received. ### Settling After Expiry Symptom: settle_invoice returns InvoiceAlreadyExpired. Cause: The invoice's expiry time (timestamp + expiry) has passed. Expired invoices cannot be settled even if the preimage is valid. Solutions: - Create a new invoice with a longer expiry value and have the sender pay again. - When creating hold invoices for time-sensitive operations (e.g., atomic swaps), set a generous expiry to account for network propagation delays. ### Wrong Preimage Symptom: settle_invoice returns HashMismatch. Cause: The preimage you provided does not hash to the invoice's payment_hash. This typically happens when the preimage was generated separately and the wrong one was used, or when it was corrupted during storage. Solutions: - Double-check that you are using the exact preimage that was used to compute the paymenthash. - If generating preimages programmatically, verify the hash immediately after creation: hashalgorithm(preimage) == paymenthash (where hashalgorithm is ckb_hash by default, unless sha256 is specified in the invoice). ### Preimage Lost Before Settlement Symptom: A hold invoice is in Received state but you cannot settle it because the preimage is lost. Cause: The preimage was not stored securely after the invoice was created, and is no longer recoverable. Solution: - Wait for the invoice to expire. Once the expiry time passes, the held TLCs are automatically released and funds return to the sender. - To prevent this in the future, always store the preimage in a secure location before creating the invoice. See Hold Invoice for the complete hold invoice lifecycle and trust model. ### Cannot Cancel Invoice Symptom: cancel_invoice fails or is rejected. Cause: The invoice is already in the Paid or Cancelled state. Only invoices in Open, Received, or Expired state can be cancelled. Solution: - Check the current invoice status with get_invoice. If it is Paid, the payment has already been settled and cannot be reversed. If it is Cancelled, no further action is needed. ## Trampoline Routing Errors ### Trampoline Feature Not Supported Symptom: A trampoline payment fails with RequiredNodeFeatureMissing. Cause: The trampoline node specified in trampolinehops does not support trampoline routing (feature bit 5, TRAMPOLINEROUTING is not enabled). Solutions: - Use a different trampoline node that advertises trampoline routing support. - Check the node's features using graph_nodes or contact the node operator. - By default, Fiber nodes advertise trampoline routing as a required feature — if the node is running an older version, it may not support it. ### Trampoline Sub-Route Fee Insufficient Symptom: A trampoline payment fails with FeeInsufficient originating from a trampoline node. Cause: The buildmaxfee_amount allocated to the trampoline hop is too small to cover the relay fees on the sub-route between two trampoline nodes. This often happens when the sender underestimates the fee budget or when relay fees on the sub-route are higher than expected. Solutions: - Retry with a higher maxfeeamount to give trampoline nodes a larger fee budget. - Try different trampoline hops that may have shorter or cheaper sub-routes. - Use dry_run: true to estimate the total fee before sending. See Trampoline Routing for a detailed explanation of the two-level fee structure and error handling. ## Connection Errors ### HTTP 503 / RPC Unavailable Symptom: RPC calls return HTTP 503 or connection refused. Possible causes: - The node is not running. - The RPC port is not accessible (firewall, wrong address). - Biscuit authentication is required but not provided. Solutions: - Verify the node process is running. - Check config.yml for the correct RPC listen address. - Provide a valid Biscuit auth token if RPC is protected. ### macOS Gatekeeper Blocks fnn Binary Symptom: macOS prevents the fnn binary from running with a security warning. Solution: 1. Go to System Settings > Privacy & Security. 2. Click Open Anyway next to the security warning. 3. Alternatively, run: xattr -d com.apple.quarantine /path/to/fnn ## Configuration Issues ### Wrong Secret Key Password Symptom: Node fails to start with Secret key file error: decryption failed. Solution: - Ensure the FIBERSECRETKEY_PASSWORD environment variable matches the password used when the key was encrypted. - If you have lost the password but have a plaintext private key, refer to the Backup Guide for recovery steps. ### Peer ID vs Pubkey (v0.8.0+ Migration) Symptom: RPC calls using peer_id parameter fail. Cause: Since v0.8.0, Fiber uses pubkey (hex-encoded secp256k1) instead of peer_id (base58) for peer identification. Solution: - Update all RPC calls to use pubkey instead of peer_id. - Refer to the v0.8.0 migration guide for a complete list of changes. ## Related Topics - Payment Lifecycle — payment states, attempts, and the TLC state machine - Invoice — creating, paying, and managing invoices - Hold Invoice — deferred settlement and common hold invoice issues - Multi-Hop Payments — routing, error handling, and retry behavior - Trampoline Routing — delegated pathfinding for lightweight clients - Unidirectional Channel — one-way channel constraints and limitations --- ## Fiber Network Glossary Source: https://www.fiber.world/docs/res/glossary Simple and non-technical explanations of key Fiber Network terminology. ## Asset The value unit that Fiber moves through channels. An asset can be the native CKB token or a user-defined token (UDT) on the Nervos CKB chain. Unless a specific asset type matters, references to "assets" in this glossary mean any Fiber-supported token. ## Fiber Network (Lightning-style on CKB) The "Asset High-Speed Highway" for the CKB ecosystem. While asset transfers usually wait for block confirmations on-chain, Fiber Network uses Lightning-style channels to move these assets through "side lanes" with near-instant speed and negligible fees. Final settlements are only reported back to the CKB mainnet when necessary. ## Node An "Asset Transit Station." This can be your CKB wallet or a dedicated server. Nodes are the backbone of the fiber network, responsible for maintaining channels and ensuring that assets flow safely to their destinations. ## Payment Channel A "Private Conveyor Belt" between two nodes. Both parties lock a certain amount of Cells (CKB's version of smart UTXOs) containing assets on the mainnet. Once open, you can slide balances back and forth instantly—like moving beads on an abacus—without touching the main blockchain. ## Commitment Transaction A "Latest Settlement Agreement." Every time the balance of assets changes in the channel, both parties sign a new agreement. It's your safety net: if your partner disappears, you can submit this latest agreement to the CKB mainnet to reclaim your Cells and assets. ## Gossip Protocol The "Status Broadcast." Since Fiber Network is decentralized, nodes "gossip" to share info: "I have a channel open," or "I support routing this specific asset." This lets nodes build a route map. Gossip shares channel presence and fees—not real-time balances—so a path found this way can still lack capacity. ## Multi-hop Routing The "Asset Relay Race." You don't need a direct channel with everyone. If you want to send assets to someone you aren't connected to, the network finds a path through intermediate nodes, "hopping" the assets from channel to channel until they reach the destination. ## Onion Routing A "Multi-Layered Privacy Wrap." When you send assets, the route is encrypted in layers. Each intermediate node only knows where to pass the "package" next, they don't know where it started or where it's finally going, keeping your financial activity private. ## Preimage The "Digital Claim Ticket." This is a secret random string generated by the recipient. It acts as the only key that can unlock the payment. Whoever presents the correct Preimage according to the contract rules gets to claim the assets in the channel. ## TLC (Time Locked Contract) A "Timed Vault" used inside Fiber channels. A TLC locks a specific amount of assets with two conditions: "If the recipient presents the correct Preimage before the deadline, the assets are released to them. If the deadline passes, the assets return to the sender." TLCs are the fundamental building block that makes multi-hop routing and conditional payments possible on Fiber — the concept is equivalent to an HTLC on the Bitcoin Lightning Network, but implemented on CKB. ## HTLC (Hashed Timelock Contract) A "Timed Vault with a Passcode." The sender locks assets in a box with two rules: "If you show the 'Claim Ticket' (Preimage) within a certain time, the assets are yours. If the timer runs out, the assets fly back to me." This ensures middlemen can't run away with the money. ## PTLC (Point Timelock Contract) The "Mathematical Upgrade" to HTLC. In a PTLC, the lock is a curve point (like a public key) rather than a hash, so the secret acts like a private key. This removes the repeated-hash linkage, improving privacy and enabling cleaner multi-path/swap designs. PTLC support is a planned direction; today Fiber still runs on hash-based TLC/HTLC flows. ## Invoice A "Digital Bill" or QR code. When you want to receive a payment, you generate an invoice. It contains the asset type, the amount, an expiry, and a lock derived from a secret (a payment hash today, or a point in a PTLC future). The sender scans it, and Fiber Network automatically finds the best path to pay you. ## Routing Fee The "Asset Handling Fee." Intermediate nodes lock up their own liquidity to forward your payment. The payer covers a small fee (in the asset being moved) as compensation. ## Watchtower Your "Asset Bodyguard." Since channel asset states are stored off-chain, a dishonest partner might try to broadcast an "old agreement" to steal funds while you are offline. A Watchtower monitors the CKB chain 24/7 and automatically intercepts any cheating attempts, punishing the attacker. --- ## Fiber Cheat Code Source: https://www.fiber.world/docs/res/cheat-code A quick-reference cheat sheet for Fiber developers and node operators. Keep this page bookmarked for everyday operations. ## Node Quick Start Use Run a Fiber Node for installation and startup steps. This page focuses on command, RPC, and configuration quick references after a node is running. The default RPC endpoint is http://127.0.0.1:8227. Use --url with fnn-cli to target a different node (e.g. ./fnn-cli --url http://127.0.0.1:8237 info). If you encounter 503 errors with fnn-cli, run: [bash code block] Fiber RPC and CLI commands act on the node you target. In a two-node payment flow, create invoices on the receiver/payee node and send payments from the sender/payer node. Channel balances and configuration are also returned or updated from the local node's perspective. ## Common CLI Commands ### Node & Peer | Category | Operation | Run on | Command | |----------|-----------|--------|---------| | Node | List available commands | Any shell | fnn-cli --help | | Node | View node info | Node being inspected | fnn-cli info | | Node | Trigger an online backup (admin module required) | Node being backed up | fnn-cli admin backup | | Peer | Connect to a peer (by pubkey) | Node initiating the connection | fnn-cli peer connectpeer --pubkey | | Peer | Connect to a WSS peer (by pubkey) | Browser/WASM-facing or WSS-capable node | fnn-cli peer connectpeer --pubkey --addr-type wss | | Peer | Connect to a peer (by address) | Node initiating the connection | fnn-cli peer connectpeer --address "/ip4//tcp/" | | Peer | List connected peers | Node being inspected | fnn-cli peer listpeers | | Peer | Disconnect a peer | Node dropping the connection | fnn-cli peer disconnectpeer --pubkey | ### Channel | Category | Operation | Run on | Command | |----------|-----------|--------|---------| | Channel | Open a CKB channel | Channel initiator / funder | fnn-cli channel openchannel --pubkey --funding-amount | | Channel | Open a UDT channel | Channel initiator / funder | fnn-cli channel openchannel --pubkey --funding-amount --funding-udt-type-script '' | | Channel | List all channels | Node whose local channel state you want | fnn-cli channel listchannels | | Channel | List pending opens only | Node whose local pending channel state you want | fnn-cli channel listchannels --only-pending true | | Channel | Close a channel | Either channel participant; caller initiates close | fnn-cli channel shutdownchannel --channel-id --force true | | Channel | Update channel config | Node whose local forwarding config you want to change | fnn-cli channel updatechannel --channel-id --tlc-minimum-value | ### Invoice & Payment | Category | Operation | Run on | Command | |----------|-----------|--------|---------| | Invoice | Create a CKB invoice | Receiver / payee | fnn-cli invoice newinvoice --amount 10000000000 --currency Fibt --description "Test" | | Invoice | Create a UDT invoice | Receiver / payee | fnn-cli invoice newinvoice --amount --currency Fibt --udt-type-script '' | | Invoice | Parse an invoice | Any node or shell targeting a node | fnn-cli invoice parseinvoice --invoice "fibt1..." | | Payment | Send payment | Sender / payer; not the node that created the invoice | fnn-cli payment sendpayment --invoice "fibt1..." | | Payment | Send keysend payment | Sender / payer | fnn-cli payment sendpayment --target-pubkey --amount --keysend true | | Payment | Check payment status | Sender / payer that created the payment session | fnn-cli payment getpayment --payment-hash 0x... | | Payment | List all payments | Sender / payer whose payment sessions you want | fnn-cli payment listpayments | Amount encoding differs between CLI and RPC: fnn-cli accepts decimal integers in the underlying unit, while RPC JSON uses hex strings for integer fields. For CKB, the underlying unit is shannons (100000000 = 1 CKB). For UDTs, use the token's base units. fnn-cli does not treat 1000 as 1000 CKB. ## RPC Quick Reference The default RPC endpoint is http://127.0.0.1:8227. All calls use JSON-RPC 2.0. ### Node & Peer | Method | Run on | Description | Key Params | |--------|--------|-------------|------------| | nodeinfo | Node being inspected | Get node identity & config | — | | connectpeer | Node initiating the connection | Connect to a remote peer | pubkey, address, save, addrtype | | disconnectpeer | Node dropping the connection | Disconnect from a peer | pubkey | | listpeers | Node being inspected | List connected peers | — | ### Channel | Method | Run on | Description | Key Params | |--------|--------|-------------|------------| | openchannel | Channel initiator / funder | Open a new channel | pubkey, fundingamount, public, oneway, fundingfeerate, fundingudttypescript | | openchannelwithexternalfunding | Initiator whose external wallet controls fundinglockscript | Negotiate a channel funded by an external wallet | pubkey, fundingamount, shutdownscript, fundinglockscript, fundinglockscriptcelldeps | | submitsignedfundingtx | Same node that called openchannelwithexternalfunding | Submit the externally signed funding transaction | channelid, signedfundingtx | | acceptchannel | Acceptor / node that received the pending open request | Manually accept a pending channel | temporarychannelid, fundingamount | | listchannels | Node whose local channel state you want | List channels; balances are local perspective | pubkey, includeclosed, onlypending | | shutdownchannel | Either channel participant; caller initiates close | Close a channel | channelid, closescript, feerate, force | | updatechannel | Node whose local forwarding config you want to change | Update channel parameters | channelid, tlcfeeproportionalmillionths, tlcminimumvalue, tlcexpirydelta | ### Invoice & Payment | Method | Run on | Description | Key Params | |--------|--------|-------------|------------| | newinvoice | Receiver / payee | Create an invoice and store its preimage locally | amount, currency, description, expiry, udttypescript | | parseinvoice | Any node | Decode an invoice string | invoice | | sendpayment | Sender / payer; not the node that created the invoice | Send a payment | invoice or targetpubkey + amount + keysend | | buildrouter | Sender / payer route source | Build an explicit route | hopsinfo, amount, udttypescript | | sendpaymentwithrouter | Sender / payer route source | Send with a manually supplied route | router, invoice or keysend | | getpayment | Sender / payer that created the payment session | Get payment status | paymenthash | | list_payments | Sender / payer whose payment sessions you want | List payments | status, limit, after | ### Example RPC Call [bash code block] ## Channel Lifecycle [code block] | State | Description | |-------|-------------| | NegotiatingFunding | Peers are negotiating channel parameters; external funding may be waiting for user-signed tx submission | | CollaboratingFundingTx | Peers are collaborating on the funding transaction | | SigningCommitment | Funding collaboration is complete; peers are exchanging commitment signatures | | AwaitingTxSignatures | Commitment signatures are exchanged; peers are finalizing funding transaction signatures | | AwaitingChannelReady | Funding transaction is submitted/confirmed and peers are exchanging channel-ready messages | | ChannelReady | Channel is open and operational; off-chain payments are possible | | ShuttingDown | A cooperative shutdown has started | | Closed | Channel is fully settled on-chain | | Stale | Restored channel state may be outdated and must be passively audited with its peer before normal operation resumes | Use fnn-cli channel listchannels to check the state of your channels. JSON-RPC returns states as {"statename":"ChannelReady"} and similar PascalCase names. Each side must reserve 99 CKB (98 for commitment lock + 1 for shutdown fee). ## Payment Lifecycle [code block] | State | Description | |-------|-------------| | Created | Payment session initialized; no TLC has been dispatched yet | | Inflight | At least one payment attempt is active or waiting for settlement/retry | | Success | Payment settled; preimage revealed, funds transferred | | Failed | Payment terminated after failures or exhausted retries | Fiber uses a two-level state machine: a high-level PaymentSession represents the intent, and one or more PaymentAttempts handle actual routing. An attempt can move through Created, Inflight, Retrying, Success, and Failed. ## Fee Calculation The forwarding fee for a TLC (Timelock Contract) is calculated as: [code block] Example: With tlcfeeproportional_millionths = 1000 (0.1%), forwarding 1,000 CKB earns the relay node 1 CKB. The fee is calculated using the outbound channel's configuration. For a path A → B → C, the fee B charges is based on the channel between B and C, not A and B. ## Important Constants & Parameters | Parameter | Purpose | Default / Typical Value | |-----------|---------|------------------------| | Reserved CKB | Channel reserve for lock and fees | 99 CKB (98 commitment + 1 shutdown fee) | | tlcfeeproportionalmillionths | Forwarding fee rate | 1000 (= 0.1%) | | tlcminvalue | Minimum TLC value to forward | 0 (any amount) | | tlcexpirydelta | TLC expiry window (milliseconds) | 14400000 (4 hours) | | commitmentdelayepoch | Commitment delay / to-self delay | 1 epoch (~4 hours) | | maxtlcvalueinflight | Max total TLC value in flight | u128::MAX by default; set at channel open, not updatable | | maxtlcnumberinflight | Max number of TLCs in flight | 125 | | pendingchannelsnumberlimit | Global max pending channel openings | 100 | | tobeacceptedchannelsnumberlimit | Max pending channel openings from one peer | 20 | | openchannelautoacceptminckbfundingamount | Min CKB funding for auto-accept | Built-in default: 10000000000 (100 CKB); public nodes commonly require 499 CKB funding | | autoacceptchannelckbfunding_amount | Auto-accept contribution | Built-in default: 9900000000 (99 CKB); public nodes commonly contribute 25000000000 (250 CKB) | ## Capacity Formula For CKB channels, each side must reserve CKB for on-chain operations: [code block] Example: Fund 499 CKB → 499 − 99 = 400 CKB available for off-chain payments. The 99 CKB reserve is per side. In a two-party CKB channel, both sides must reserve this amount. This reserve is not available for CKB payments — it covers the commitment transaction lock and the shutdown transaction fee. For UDT channels, the channel balance is in token base units, but the funding transaction still needs enough CKB capacity and fees. ## Testnet Resources | Resource | URL | Notes | |----------|-----|-------| | CKB Testnet Faucet | faucet.nervos.org | Get testnet CKB | | RUSD Stablecoin | testnet0815.stablepp.xyz | Mint testnet RUSD | | CKB Explorer | explorer.nervos.org | Testnet block explorer | | Fiber Dashboard | dashboard.fiber.channel | Network topology & stats | | Fiber Releases | GitHub Releases | Download latest binary | ### Public Testnet Nodes | Name | Pubkey | Auto-accept CKB | Auto-accept UDT | |------|--------|------------------|------------------| | fiber-testnet-public-bottle | 02b6d4e3ab86a2ca2fad6fae0ecb2e1e559e0b911939872a90abdda6d20302be71 | ≥ 499 CKB | RUSD ≥ 20 | | fiber-testnet-public-bracer | 0291a6576bd5a94bd74b27080a48340875338fff9f6d6361fe6b8db8d0d1912fcc | ≥ 499 CKB | RUSD ≥ 20 | ## Common Patterns ### Multi-Hop Routing You don't need a direct channel to every peer. Fiber automatically discovers paths through intermediate relay nodes: [code block] Local nodes do not need a public IP — they connect through public relay nodes. See Multi-Hop Routing for full details. ### Channel Rebalancing When one side of a channel is depleted, rebalance by sending a payment back to yourself: Automatic — use sendpayment with allowself_payment: true: [json code block] Manual — use buildrouter + sendpaymentwithrouter for control over the exact path. See Channel Rebalancing for details. ### Keysend Payments Send a payment without an invoice by specifying the recipient's pubkey directly: [bash code block] Keysend is useful for spontaneous payments, tipping, and machine-to-machine transactions where the recipient cannot pre-generate an invoice. For CKB payments, the CLI amount is in shannons (100000000 = 1 CKB). Further reading: - Channel Lifecycle — detailed channel state transitions - Payment Lifecycle — payment session and attempt states - HTTP RPC Reference — full RPC API reference - Network Resources — public nodes, faucets, and explorers - Channel Rebalancing — liquidity management --- ## Fiber Network Light Paper Source: https://www.fiber.world/docs/res/light-paper ## Overview Fiber Network is a next-generation, common lightning network built on Nervos CKB and off-chain channels. It is designed to provide fast, low-cost, and decentralized multi-token payments and peer-to-peer transactions for RGB++ assets. ## Background ### Evolution and Challenges of Blockchain Technology Blockchain technology has undergone rapid evolution since the inception of Bitcoin. Initially designed for simple payments, it has gradually expanded into various domains such as smart contracts, decentralized finance (DeFi), and non-fungible tokens (NFTs). Despite its significant advantages in security, transparency, and decentralization, blockchain technology faces several challenges in scalability and transaction speed. 1. Scalability. Traditional blockchains like Bitcoin and Ethereum face significant bottlenecks in transaction throughput. Due to Bitcoin's block size limit and 10-minute block generation time, its network can only process about 7 transactions per second; Ethereum, despite improvements, still has a transaction processing capacity far below traditional payment networks. 2. High transaction fees. As network congestion increases, transaction fees rise significantly. For instance, gas fees on the Ethereum network during peak times may exceed the transaction amount itself, severely affecting user experience and reducing the feasibility of micropayments. 3. Long transaction confirmation times. In traditional blockchain networks, transactions need to wait for multiple block confirmations to be considered final. This process can take minutes to hours, making it unsuitable for instant payment scenarios. Although Nervos CKB has made improvements in terms of performance and confirmation times, it still needs to further increase transaction speed and reduce transaction costs to meet the demands of micropayments and instant payments. ### Inspiration from the Lightning Network The Lightning Network, a layer 2 scaling solution for the Bitcoin network, has successfully achieved fast, low-cost micropayments through off-chain transactions and payment channels. Its core concepts include: 1. Payment channels: Users create payment channels on-chain. Once a channel is opened, both parties can conduct unlimited off-chain transactions, only settling on-chain when the channel is closed. This significantly reduces the number of on-chain transactions, improves transaction speed, and lowers transaction fees. 2. Hash Time-Locked Contracts (HTLC): Through HTLCs, the Lightning Network ensures secure fund transfers, mitigating counterparty risk. Even if off-chain transactions fail, users can still secure their funds through on-chain contracts. 3. Routing mechanism: The Lightning Network uses multi-hop routing, allowing users to complete payments without opening direct channels with recipients, thus enhancing network flexibility and usability. ## Advantages of Nervos CKB Nervos CKB is a blockchain platform focused on versatility and security. Its unique design offers distinct advantages in addressing blockchain scalability and interoperability issues: 1. Consensus mechanism: Based on the NC-Max consensus protocol, it combines Proof of Work (PoW) with state rent mechanisms, ensuring network security and effective resource utilization. 2. Powerful smart contract capabilities: CKB's unique Cell model and RISC-V instruction set virtual machine significantly enhance the capabilities of the UTXO model. This not only supports Turing-complete smart contracts but also easily implements features such as account abstraction and covenants, providing more flexible programmability, better interoperability, and scalability for decentralized applications. 3. Tokenomics: CKB's tokenomics encourages long-term holding and rational use of network resources, providing a secure and sustainable decentralized environment for applications, developers, and users. ## Significance of the Fiber Network Project By building off-chain channels on Nervos CKB, we aim to combine the successful experience of the Lightning Network with CKB's technical advantages to create a fast, low-cost, and decentralized multi-asset real-time payment network. Specifically: 1. Solving scalability issues: Through off-chain payment channels and multi-hop routing, Fiber Network can achieve high-throughput transaction processing, meeting the needs of large-scale users. 2. Reducing transaction costs: By reducing the frequency of on-chain transactions, it lowers transaction fees, making micropayments feasible and efficient. 3. Improving transaction speed: The instant confirmation of off-chain transactions provides a split second payment confirmation experience suitable for various instant payment scenarios. 4. Multi-asset support: Fiber Network supports payments in a variety of digital assets, offering users a broader range of payment options. 5. Interoperability: Fiber Network supports interoperability with the Bitcoin Lightning Network, providing support for cross-chain payments and asset transfers. ## Architecture Design ### Overall Architecture The overall architecture of Fiber Network includes the following core modules: 1. Off-Chain Payment Channels (Fiber Channels) 2. On-Chain Contracts (HTLC) 3. Multi-Hop Routing 4. Watchtower Service ### Off-chain Payment Channels Off-chain payment channels are the core of Fiber Network, enabling multiple off-chain transactions with on-chain settlement only when the channel is closed. This mechanism significantly reduces the number of on-chain transactions, improves transaction speed, and lowers transaction fees. The general workflow is as follows: 1. Opening a Channel: Two parties open a payment channel on-chain, locking a certain amount of CKB or RGB++ assets. 2. Off-chain transactions: When the channel is open, both parties can conduct an unlimited number of off-chain transactions, updating the channel state with each transaction without immediate broadcasting to the chain. 3. Closing the Channel: When either party decides to close the channel, the final channel state is broadcasted on-chain for settlement, ensuring the final balances of both parties are confirmed. The message interaction format can be referenced in the Fiber Network P2P Message Protocol. ### On-Chain Contracts Currently, we use Hash Time-Locked Contracts (HTLC) to ensure the security of off-chain transactions and maintain compatibility with the Lightning Network. This mitigates counterparty risk, ensuring that even if off-chain transactions fail, users can still secure their funds through on-chain contracts. The general workflow is as follows: 1. Transaction initiation: The payment initiator creates a transaction with hashlock and timelock, and locks a certain amount of CKB. 2. Hash verification: The payment recipient must provide the correct hash preimage within the specified time to unlock the transaction and complete the fund transfer. 3. Timeout refund: If the recipient fails to provide the correct hash preimage within the specified time, the transaction will automatically unlock and refund to the payment initiator. Thanks to CKB's Turing completeness, we can implement more flexible and secure on-chain contracts. We will further expand the contract's functionality in the future, such as introducing a version-based revocation mechanism and more secure Point Time-Locked Contracts. ### Multi-hop Routing Multi-hop routing allows users to complete payments through multiple intermediate nodes without establishing direct payment channels with the counterparty. This mechanism enhances the network's flexibility and coverage. The general workflow is as follows: 1. Path discovery: The payment initiator discovers the optimal path from themselves to the payment recipient through the routing module. 2. Path locking: Each node on the path creates corresponding HTLC contracts, ensuring secure fund transfers. 3. Payment completion: The payment recipient unlocks the HTLC, and funds are transferred sequentially to each node on the path. We will also implement cross-chain payments here using HTLC contracts, supporting interoperability with the Lightning Network through the cross-chain hub service. For more details, please refer to Payment Channel Cross-Chain Protocol with HTLC. ### Watchtower Service The watchtower service is an essential component of Fiber Network, responsible for monitoring the state of off-chain payment channels and ensuring the security of channels and funds. Its functions and roles are as follows: 1. Channel monitoring: Real-time monitoring of the payment channel state of all participating users, including opening, updating, and closing channels. 2. Anomaly detection: Detecting abnormal activities in channels, such as malicious users attempting to close channels with old states or double-spending attacks. 3. Proactive response: When anomalies are detected, promptly broadcasting the latest channel state to the blockchain network to prevent fund losses due to malicious behavior. ## Current Progress and Future Plans We have currently completed a prototype of Fiber Network, implementing basic functions of opening, updating, and closing channels between two nodes, and also verifying cross-chain functionality with the Bitcoin Lightning Network. The project code can be found in the following GitHub repositories: 1. https://github.com/nervosnetwork/fiber 2. https://github.com/nervosnetwork/fiber-scripts Our next steps include completing multi-hop routing and watchtower services, as well as improving the RPC interface and SDK to facilitate easier access for developers to Fiber Network. The multi-hop routing protocol is based on the Dijkstra algorithm to search for payment paths, thereby reducing routing fees and improving the success rate of multi-hop path payments. After Fiber Network goes live, we will optimize the routing algorithm based on network traffic and operational conditions. We expect to provide 2 or 3 path search strategies to adapt to users' different routing preferences and needs. Fiber Network will also introduce multi-path payment strategies, dividing larger payment amounts into multiple parts, each transmitted through different paths, further increasing the probability of successful payments. The watchtower service will be provided by some nodes in Fiber Network. These nodes will stay online, monitor abnormal situations in the network, and help protect assets in channels. The monitoring service will also track the cross-chain hub service. Even if users are offline for a period, the monitoring service can ensure successful exchanges with the Lightning Network. Additionally, we will consider adding more features to Fiber Network, such as implementing privacy protection algorithms leveraging CKB's programmability, and based on this, optimizing routing algorithms and watchtower services to enhance the security and privacy of users’ payment information. --- ## Fiber Architecture & Module Source: https://www.fiber.world/docs/res/high-level ## Overview Fiber is a Lightning-compatible peer-to-peer payment and swap network built on CKB, the base layer of Nervos Network. Fiber is designed to enable fast, secure, and efficient off-chain payment solutions, particularly for micropayments and high-frequency transactions. Inspired by Bitcoin’s Lightning Network, Fiber leverages CKB’s unique architecture and offers the following key features: - Multi-Asset Support: Fiber is not limited to a single currency; it supports transactions involving multiple assets, paving the way for complex cross-chain financial applications. - Cross-Chain Interoperability: Fiber is natively designed to interact with Lightning Networks on other UTXO-based blockchains (such as Bitcoin), improving cross-chain asset liquidity and network compatibility. - Flexible State Management: Thanks to CKB’s Cell model, Fiber efficiently manages channel states, reducing the complexity of off-chain interactions. - Programmability: Built on CKB’s Turing-complete smart contracts architecture, Fiber enables more complex conditional execution and transaction rules, extending the use cases of payment channels. This article presents a source code-level exploration of Fiber's architecture, key modules, as well as an overview of its future development plans. ## Prerequisites - Rust and Actor Framework: Fiber is entirely implemented in Rust and follows the Actor Model programming paradigm. It relies on the community-maintained slawlor/ractor framework. - Lightning Network: Fiber follows the core principles of Lightning Network. Resources such as Mastering the Lightning Network and BOLTs are highly recommended for understanding the concepts. - CKB Transactions and Contracts: Fiber interacts with CKB nodes via RPC, making a solid understanding of CKB contract development essential. ## Key Modules At a high level, a Fiber node consists of several key modules: !image ### Overview - Network Actor: Facilitates communication between nodes and channels, managing both internal and external messages along with related management operations. - Network Graph: Maintains a node’s view of the entire network, storing data on all nodes and channels while dynamically updating through gossip messages. When receiving a payment request, a node uses the network graph to find a route to the recipient. - PaymentSession: Manages the lifecycle of a payment. - fiber-sphinx : A Rust library for Onion packet encryption and decryption. In Fiber, this ensures sensitive payment details are hidden from intermediate nodes, enhancing security and anonymity. - Gossip: A protocol for sharing channel/node information, facilitating payment path discovery and updates. - Watchtower: Monitors channels for fraudulent transactions. If a peer submits an outdated commitment transaction, the watchtower issues a revocation transaction as a penalty. - Cross Hub: Enables cross-chain interoperability. For example, a payer can send Bitcoin through the Lightning Network, while the recipient receives CKB. The cross hub handles the conversion, mapping Bitcoin payments and invoices to Fiber’s system. - Fiber-Scripts: A separate repository containing two main contracts: - Funding Lock: A contract for locking funds, utilizing the ckb-auth library to implement a 2-of-2 multi-signature scheme for channel funding. - Commitment Lock: Implements the Daric protocol as Fiber’s penalty mechanism to achieve optimal storage and bounded closure. ### Efficient Channel Management with the Actor Model The Lightning Network is essentially a peer-to-peer (P2P) system, where nodes communicate via network messages, updating internal states accordingly. The Actor Model aligns well with this setup: !image One potential concern with the Actor Model is its memory footprint and runtime efficiency. We conducted a performance test, showing that 0.9 GB of memory can support 100,000 actors (each with a 1 KB state), processing 100 messages per actor within 10 seconds—demonstrating acceptable performance. Unlike rust-lightning, which relies on complex locking mechanisms to maintain data consistency, Fiber’s Actor Model simplifies implementation by eliminating the need for locks to protect data updates. Messages are processed sequentially in an actor’s message queue. When a message handler completes its tasks, the updated channel state is written to the database, streamlining the persistence process. Almost all modules in Fiber use the Actor Model. The Network Actor handles communication both within and across nodes. For example, if Node A wants to send an "Open Channel" message to Node B, the process follows these steps: 1. The Channel Actor in Node A (Actor 0 in this case) sends the message to the Network Actor in Node B. 2. The Network Actor transmits the message using Tentacle, a lower-level networking layer. 3. The Network Actor in Node B receives the message and forwards it to the corresponding Channel Actor(Actor 0/1/…/n). !image For each new channel, Fiber creates a corresponding ChannelActor, where the ChannelActorState maintains all the necessary data for the channel. Another major advantage of the Actor Model is its ability to map HTLC (Hash Time-Locked Contracts)-related operations directly to specific functions. For example, in the process of forwarding an HTLC across multiple nodes: - Node A’s Actor 0 handles the AddTlc operation via handleaddtlccommand. - Node B’s Actor 1 handles the corresponding peer message via handleaddtlcpeer_message. !image The HTLC management within channels is one of the most complex aspects of the Lightning Network, primarily due to the dependency of channel state changes on peer interactions. Both sides of a channel can have simultaneous HTLC operations. Fiber adopts rust-lightning’s approach of using a state machine to track HTLC states, where state transitions occur based on commitmentsign and revokeand_ack messages. The AddTlc operation and state transitions for both peers are as follows: !image ### Optimized Payment Processing and Multi-Hop Routing Each Fiber node maintains a representation of the network through a Network Grap, essentially a bidirectional directed graph, where: - Each Fiber node represents a vertex. - Each channel represents an edge. For privacy reasons, the actual balance partition of a channel is not broadcasted across the network. Instead, the edge weight represents the channel capacity. Before initiating a payment, the sender performs pathfinding to discover a route to the recipient. If multiple paths available, the sender must determine the optimal one by considering various factors. Finding the best path in a graph with incomplete information is a complex engineering challenge. A detailed discussion of this issue can be found in Mastering Lightning Network. !image In Fiber, users initiate payments via RPC requests. When a node receives a payment request, it creates a corresponding PaymentSession to track the payment lifecycle. The quality of pathfinding directly impacts network efficiency and payment success rates. Currently, Fiber uses a variant of Dijkstra’s algorithm. The implementation can be found here. However, unlike the standard Dijkstra algorithm, Fiber’s routing expands backward from the target toward the source. During the search, the algorithm considers multiple factors: - Payment success probability - Transaction fee - HTLC lock time Routes are ranked by computing a distance metric. Probability estimation is derived from past payment results and analysis, implemented in the eval_probability module. Once the path is determined, the next step is to construct an Onion Packet. Then the source node sends an AddTlcCommand to start the payment. The payment status will be updated asynchronously. Whether the HTLC succeeds or fails, the network actor processes the result via event notifications. ### Reliable Payment Retries and Failure Handling Payments in Fiber may require multiple retries due to various factors, with a common failure scenario being: - The channel capacity used in the Network Graph is an upper bound. - The actual available liquidity might be insufficient to complete the payment. When a payment fails due to liquidity constraints: - The system returns an error and updates the Network Graph. - The node automatically initiates a new pathfinding attempt. This dynamic retry mechanism ensures that payments have a higher chance of success despite fluctuating network conditions. ### Peer Broadcasting with Gossip Protocol Fiber nodes exchange information about new nodes and channels by broadcasting messages. The Gossip module implements the routing gossip protocol defined in BOLTs 7. The key technical decisions were documented in the PR: Refactor gossip protocol. When a node starts for the first time, it connects to its initial peers using addresses specified in the configuration file under bootnodeaddrs. Fiber supports three types of broadcast messages: - NodeAnnouncement - ChannelAnnouncement - ChannelUpdate The raw broadcast data received is stored in the storage module, allowing messages to be efficiently indexed using a combination of timestamp + messageid. This enables quicker responses to query requests from peer nodes. When a node starts, the Graph module loads all stored messages using loadfromstore to rebuild its network graph. Fiber propagates gossip messages using a subscription-based model. 1. A node actively sends a broadcast message filter (BroadcastMessagesFilter) to a peer. 2. When the peer receives this filter, it creates a corresponding PeerFilterActor, which subscribes to gossip messages. This subscription model allows nodes to efficiently receive newly stored gossip messages after a specific cursor, enabling them to dynamically update their network graph, because the network graph also subscribes to gossip messages. The logic for retrieving these messages is implemented in this section. ### Enhancing Privacy with Onion Encryption & Decryption For privacy and security consideration, payments’ TLC is propagated across multiple nodes using Onion encryption. Each node only accesses the minimal necessary details, such as: - The amount of the received TLC - The expiry of the TLC - The next node in the payment route This approach ensures that a node cannot access other sensitive details, including the total length of the payment route. The payment sender encrypts the payment details using onion encryption, and each hop must obfuscate the information before forwarding the TLC to the next node. In case of an error occurs at any hop during payment forwarding, the affected node sends back an error message along the reverse route to the sender. This error message is also onion-encrypted, ensuring that intermediate nodes cannot decipher its content—only the sender can decrypt it. We examined the onion packet implementation in rust-lightning and found it to be tightly coupled with rust-lightning’s internal data structures, limiting its generalization. Therefore, we built fiber-sphinx from scratch. For more details, refer to the project spec and the developer’s presentation slides. The key Onion Encryption & Decryption steps in Fiber include: - Creating the Onion Packet for Sending Payments Before sending a payment, the sender creates an onion packet, included in the AddTlcCommand sent to the first node in the payment route. - Onion Decryption at Each Hop - When a node in the payment route receives a TLC, it decrypts one layer of the onion packet, similar to peeling an onion. - If the node is the final recipient, it processes the payment settlement logic. - If the node is not the recipient, it continues processing the TLC and then forwards the remaining onion packet to the next hop. - Generating an Onion Packet for Error Messages If an error occurs during TLC forwarding, the node creates a new onion packet containing the error message and sends it back to the previous node. - Decrypting Error Messages at the Payment Sender When the sender receives a TLC fail event, it decrypts the onion packet containing the error. Based on the error details, the sender can decide whether to resend and update the network graph accordingly. !image ### Preventing Channels from Fraud via Watchtower Watchtower is an important security mechanism in the Lightning Network, primarily used to protect offline users from potential fund theft. It maintains fairness and security by real-time monitoring on-chain transactions and executing penalty transactions when violations are detected. Fiber's watchtower implementation is in the WatchtowerActor. This actor listens for key events in the Fiber node. For example: - When a new channel is created, it receives a RemoteTxComplete event, while the watchtower inserts a corresponding record into the database to start monitoring this channel. - When the channel is closed through upon mutual agreement, it receives a ChannelClosed event, while the watchtower removes the corresponding record from the database. During TLC interactions in the channel, the watchtower receives RemoteCommitmentSigned and RevokeAndAckReceived events, updating the revocationdata and settlementdata stored in the database respectively. These fields will be used later to create revocation and settlement transactions. Watchtower's penalty mechanism ensures that old commitment transactions are not used in a on-chain transaction by comparing the commitment_number. If a violation is detected, the watchtower constructs a revocation transaction and submits it on-chain to penalize the offender. Otherwise, it constructs and sends a settlement transaction. ## Other Technical Decisions - Storage: We use RocksDB as the storage layer, leveraging its scheme-less storage design to simplify encoding and decoding structs with serde. Data migration remains a challenge, which we address by this standalone program. - Serialization: Messages between nodes are serialized and deserialized using Molecule, bringing efficiency, compatibility, and security advantages. It ensures determinism, meaning the same message serializes identically on all nodes, which is crucial for signature generation and verification. ## Future Prospects Fiber is still in the early stages of active development. Looking ahead, we plan to make further improvements in the following areas: - Fix unhandled corner cases to enhance overall robustness; - Improve the cross-chain hub (currently in the prototype verification stage) by introducing payment session functionality to make cross-chain transactions more user-friendly; - Refine the payment routing algorithm, potentially introducing multi-path feature and other path-finding strategies to accommodate diverse user preferences and needs; - Expand contract functionality, including version-based revocation mechanisms and more secure Point Time-Locked Contracts. --- ## What is Payment Channel Network Source: https://www.fiber.world/docs/res/payment-channel Layer 2 is a popular solution to the blockchain scalability problem, which, unlike implementing alternative consensus schemes and side chains, avoids the risk of forking the blockchain. A main approach in layer 2 is to open off-chain channels where two parties communicate for transactions and smart contracts outside of the underlying layer 1 blockchain. The most prominent examples of payment channels include the lightning network on Bitcoin and Raiden on Ethereum. Payment channels can be unidirectional or bidirectional, depending on the flow direction of the payments. In unidirectional channels, the payment is only from one party to another, not the other way around. It works like a top-up card, where you deposit some money off-chain, and transfer a certain amount within the fund limit to the receiver with your signature. When closing the channel, or the channel going depletion, the final balance information is updated on-chain hence completes the payments. In bidirectional channels, both party make a deposit to the channel at opening (this procedure is called funding). The problem for directly using a unidirectional channel is that, every transaction between the parties is authenticated, therefore a malicious party is able to claim previous transactions where the balance is in favor of oneself. The lightning network solved this problem by preventing any user to redeem older states, i.e., when a new transaction is signed in a bidirectional channel, the players reveal a secret information to the previous transaction for revocation. Off-chain bidirectional payment channels with a multi-hop protocol form a payment channel network (PCN) to handle off-chain payments between two players who have no direct channel themselves. If Alice wants to send coins through an intermediary Ingrid, a crucial difficulty is the lack of trust between Alice and Ingrid. Ingrid receives a fee for doing the favor, but she worries that Alice might refuse to pay after she pays Bob. Similarly, Alice couldn't simply give the money to Ingrid and pray that Ingrid would not take the money and disappear. This is why Hash Time Lock Contract (HTLC) was invented. In an HTLC between Alice and Ingrid, there are two locks, a time lock and a hash lock. Ingrid gets the coins if she can solve the hash lock puzzle, which requires the pre-image of a predefined hash digest (chosen by the receiver Bob) before timeout; while Alice gets the coins in the contract after the end of the time lock if Ingrid fails to provide a correct pre-image in time. In payment channel network protocols with HTLC-like schemes, the collateral that the parties have to pay as a cost is often defined as the product of the deposited fund and the total time. Therefore, a considerable amount of assets are locked in the channels, which poses a non-negligible negative influence on the liquidity. For instance, 30 percent of the lightning network locks approximately 890BTC of collateral with a value of 39M USD (value estimated by the time the paper on sleepy channels was written). In addition, the network topology of a payment channel network is mostly hidden for protecting the anonymous relationship and balance privacy of the players. So routing in PCN, that is to find a payment path with minimal fees and optimized efficiency, is challenging in general. To mitigate the routing problem, payment channel hub (PCH) is a promising alternative. In a PCH, a trusted tumbler plays a centralized role to forward the payments from a sender to a receiver. One of the first PCH schemes was proposed by Heilman et al. at NDSS 17, which is unidirectional, unlinkable, privacy-enhanced and Bitcoin-compatible. Here, the trust between Alice and the tumbler is a reversed version of the HTLC, in which the sender Alice needs to solve a hash puzzle to prove that she has paid the tumbler. Perun proposed by Dziembowski et al. at S&P19 presented a PCH scheme for constructing virtual channels. Perun is implemented on Ethereum with Turing-complete smart contracts. A more recent PCH protocol is A2L designed by Tairi et al. at S&P21. A2L gives the first PCH construction that requires minimal properties of the blockchain with only digital signatures and time-locks. PCN players are expected to monitor the channel state regularly and frequently to detect frauds in time. However, in practice, this might bring a unbearable cost for the players, and fund lose is likely to happen if the frauds are overlooked accidentally. Especially, when an intermediary in a payment path goes offline for a long time, it greatly decreases the efficiency and success rate of the transactions. One solution is to hire watchtowers to supervise the channels for players when they are offline, and resolve possible disputes with the snapshots stored by the watchtowers. We will talk more about watchtowers later. Another approach is to build virtual channels based on payment channels. Virtual channels are off-chain channels that allow players with no direct payment channel to communicate off-chain without an intermediary. Only the open and closure of virtual channels require the participation of an intermediary node, . Perun presented such virtual channels with smart contracts. Later at S&P21, a bitcoin-compatible virtual channel scheme was proposed by Aumayr et al. to remove the dependency on smart contracts. An interesting question here is whether it is possible to build virtual channels based on virtual channels. A generalization of payment channels is studied to deploy off-chain smart contracts, those are called state channels or generalized channels, e.g., the general state channel networks presented at CCS18 by Dziembowski et al.. State channels can be extended in a virtual multi-party setting as studied by Dziembowski et al. in this Eurocrypt19 paper. Hydra, proposed by Chakravarty et al. at FC21 takes advantage of the extended UTxO model to reuse the on-chain smart contract system. The studies on state channels and virtual channels are fewer comparing with those on lightning networks and PCN protocols. In the following, we will discuss on several highlighted topics in the research of payment channel networks. ## Hash Time Lock Contracts HTLC is a vital component in the implementation of lightning network and cross-chain atomic swap protocols. The core idea is that the players deposit a certain amount of collaterals in the channel and claims the money under conditions in order to prevent frauds. However, HTLC has vulnerabilities that jeopardize the security and privacy of the network under a malicious exploration. In this section, we will talk about the security issues including bribery attacks, DoS and congestion attacks. Then we discuss the improvements on HTLC for multi-hop multi-party use cases. ### Bribery Attack Bribery attack is a common threat to blockchain users by encouraging dishonest strategies with malicious incentives. In the honest case, when Alice sends coins to Bob through Ingrid, we have two HTLCs for Alice with Ingrid, and, Ingrid with Bob. When claiming the money, Ingrid receives the pre-image of the hash lock from Bob before the expiration of the second contract, then Ingrid gets redeemed from Alice by forwarding the correct pre-image to her before timeout. To include a transaction on-chain, transaction fees are paid to miners so they pack the transactions and mine a block. In a bribery attack, Alice refuses to pay Ingrid even if Ingrid shows the correct pre-image. Alice achieved so by bribing the miners such that they ignore Ingrid's transaction and leave it unpacked till timeout. The fund lose of Ingrid incentivizes the miners and Alice to collude and gain. This attack breaks the atomicity principle of the payment channel network. The idea of such a bribery attack is originated from this EuroS&PW19 paper on temporary censorship attacks by Winzer et al.. The authors presented three types of briberies on smart contracts. The bribery per miner strategy is the most expensive one with a quadratic collateral to the number of participants, it requires to pay all the miners a fixed amount of money. Pay per block is less costly but there is a risk that the other miners who are not paid choose not to cooperate. Pay-per-commit ignores the honest transaction through a public commit by a high-mining power miner to a malicious mining strategy. Such a bribery to miners is studied in HTLC by Tsabery et al. in the SP21 paper MAD-HTLC. It considered the incentives of the miners to act in an evil strategy, and provided a game-theoretical analysis of the participants with sub-game perfect equilibrium. An improved HTLC protocol MAD-HTLC is proposed to punish the briber, which gives the confiscate coins to the miner if the sender intends to bribe. A similar idea was proposed in a concurrent paper by Nadahalli et al. at FC21 with a timelocked bribing. HE-HTLC, proposed by Wadhwa et al., further extended the bribery attacks in MAD-HTLC by considering a reversed bribery, meaning that the miners bribes Alice for her to trust and collude. Then at FC22, the incentives for the bribery attacks in MAD-HTLC are generalized in Suborn by Avarikioti et al., the aim is to adjust the transaction fees to disincentivize bribes. ### DoS Attack Similar to bribery, an attacker can ignore or exclude an honest transaction by disabling the network with DoS or congestion attacks. For instance, in the flood&loot attack presented by Harris et al. at AFT20, an attacker floods the payment channel network by starting many small transactions all at once, such that the system doesn't have enough power to take care of the honest transactions before timeout. Such an attack is dangerous for lightning network, because the attack gets a loot by disabling merely 85 channels. Congestion attack proposed by Mizrahi et al. at FC21 takes advantage of the Max HTLC limit in the protocol, sending transactions to the attacker itself, to block the channels with high liquidity. ### Atomicity Another often mentioned HTLC vulnerability is the partial atomicity problem, which leads to the wormhole attack presented at NDSS19 by Malavolta et al.. Although HTLC maintains the atomicity in one-hop transactions, a malicious attacker can take advantage of the multi-hop transaction by participating in two or more nodes in the multi-hop path. Then, once the pre-image is known to the attacker, all the nodes between attacker's nodes are skipped and they lose coins to the attacker. ### Multi-Hop Multi-Party HTLCs Improved HTLC schemes have been proposed to overcome the vulnerabilities of HTLC and to provide advanced properties. For instance, at the conference CCS17 by Malavolta et al., concurrency and privacy are formalized in the universal composability framework and two protocols were design to achieve the properties. In addition, they presented an improved multi-hop HTLC contract, which protects the privacy of the players in a payment path with multiple intermediaries. For each player, he or she only knows who is directly talking to in the path, but has no idea about the identity of the sender and receiver in the payment. In atomic cross-chain swap exchanges, players from different blockchains trade their tokens in a fair and trustless way. Narayanam et al. constructed an HTLC contract for multi-party cross-chain exchange at AFT22. What I find interesting is that such an HTLC contract allows multiple players to own and trade assets jointly with the help of MPC. It is called MP-HTLC. ## Security and Privacy Topics in PCN In this section, we will focus on the vulnerabilities of current PCN protocols in security and privacy. Being a communication network, PCN protocols suffer from typical attacks on network topology. In addition, the economics incentives in PCN play a significant role in the participants of the protocol. ### Privacy Attacks In FC21, Kappos et al. defined four types of privacy notions in payment channel networks. - Private channels. When two users share a channel, other participants in the network have no idea about the existence of the channel and identity information. - Third-party secrecy. The balance of the channel is kept secret from any third party. - On-path relationship anonymity. For a honest but curious participant on a payment path, the payment relationship should be anonymous. In lightning network, this might be a vulnerable problem because of its centrality. - Off-path payment privacy. Nodes outside the payment path should know nothing about the payment value. The attacks on privacy we discuss here are mostly on the second and third properties. There is a timing attack by Rohrer et al. at AFT20 for finding the sender and receiver in a payment path by exploring the related transaction amounts in the payment path and the decreasing deadline in the time locks of the HTLC. Channel balance is also a frequent attacking target. Revealing the balance might lead to unfair fees and malicious attacks on the network. For instance, a lockdown attack by Pérez-Solà at FC20 showed that, using a DoS attack, it is possible to freeze the balances in many channels, so that the critical nodes in the network are blocked thus paralyzes the PCN. Later, a probing attack is proposed by Biryukov et al. at FC22, the authors show geometric strategies to optimize the success probability and attack speed in probing the balance in the channels. A probing attacker sends invalid transactions or transactions to another address owned by the attacker, to find out if the balance of a channel player is larger or smaller than the probing value. This strategy is more efficient than a default binary search. Also, it works when there are multiple channels between the attacker and the probed node. Due to the privacy concerns, the network topology is unknown in the lightning network and many other PCN networks, which raises new challenges like routing issues in the next subsection. The question studied in Sigmetric20 paper by Tang et al. answers whether it is possible to achieve a tradeoff between privacy and utility/efficiency. ### Routing Issue When Alice sends Bob coins through the network, she prefers a path that every channel in the path contains enough balance for the transfer and the total transaction fee of the path is as small as possible. This task is difficult because of the unknown network topology, potential depleted channels, and constantly changing channel balances. A simple strategy is to try many channels and detect one that happens to work. An early routing algorithm as used in SlientWhispers by Malavolta et al. at NDSS17 and SpeedyMurmurs by Roos et al. at NDSS18 are based on landmarks. The idea of a landmark routing protocol is to calculate a path between sender and receiver through an intermediary node called a landmark. In more detail, landmarks are selected nodes which are well known to every other node in the network. FSTR routing proposed at DSN20 by Lin et al. is based on the fund skewness properties, which considers the imbalance in the channels. This is at the cost of privacy. To reduce the unsuccessful probability of transaction in a routing procedure, it is possible to split the transaction value to smaller portions and transfer them through multiple paths, so that the balances in the paths are more likely to match the transaction. Spider by Sivaraman et al. at NSDI20 presented a multi-path routing protocol to packetizes the transactions. Later on, in Boomerang designed by Bagaria et al. at FC20 and Spear by Rahimpour et al. at FC21, a redundant payment path strategy is studied. To show the idea with an example, say, instead of sending 4 coins through 4 channels each with one coin, the sender sends 8 coins through 8 channels each with one coin, and the protocol terminates when 4 out of 8 coins are received. It is ensured by verifiable secret sharing that the remaining 4 coins won't be stolen in the channels. Spear is an improved scheme on boomerang, it has lower latency and smaller computation because it doesn't use the cryptographic primitives as those in boomerang. Lastly, presented at AFT20 by Tochner et al., an attacker can strategically influence and explore the pattern that transactions are routed in the network. Through a DoS attack, an attacker is able to hijack some routes like in the bitcoin hijacking attack by Apostolaki et al. at S&P17 and forces the payment to go through the routes preferred by the attacker. The gain of the attacker might be the liquidity locked in the network, or transaction fees through attacker's network (even though the fee is higher than others thus not preferable when not attacked). ### Rebalancing In payment channel networks, the initial collaterals in each channel might deplete after a while, because one party pays more to the other. When a channel depletes, a naive solution is to close the channel and reopen a new one on-chain. For instance, Loop on the lightning network achieves such a goal. However, it takes time and money to do so. A better solution is rebalancing, as first proposed in Revive by Khalil et al. at CCS17. The idea is as follows. When Alice's balance ran out in channel 1, and she has enough balance in channel 2 with another party, she can make a transfer to herself into channel 1 to top-up through channel 2 without going on-chain. Revive has several practical problems, first, a delegate is required to handle the rebalance process, in addition, it requires the cooperation of other participants in the network, lastly, it is difficult to route the rebalancing path in a large network with an unknown topology. What's worse, the rebalanced amount by Revive is bounded by the smallest channel balance in the path. Hide&seek by Avarikioti1 et al. at FC22 studied a linear programming problem in scheduling the rebalancing amount and direction to achieve minimum cost. Cycle by Hong et al. at DSN22 proposed to constantly rebalancing to make sure that channels don't go depletion. To avoid a cyclic path to rebalance, Shaduf by Ge et al. at NDSS22 gives a non-cycle rebalancing protocol, it doesn't require the rebalancing path to form a cycle. This process is done by binding two channels together for moving the coins between them, and declare such a binding on-chain. Of course, there is another question to answer, how much money should the players deposit in the channel at the opening phase? Li et al. gives a balance planning service at infocom20 called PnP to decide the initial balance to put in the channel. ### Watchtowers Lastly, we come back at the solutions to deal with offline users or intermediaries in payment channel networks. If a user doesn't want to keep an eye on the channel all the time, it is recommended to hire a watchtower to detect potential frauds. Watchtower has been designed in the lightning network, but it encounters a lack of incentives for the watchtowers to work properly, because the watchtowers are only paid when they find a fraud, but they don't expect to gain a lot because most of the users are honest. So McCorry presented Pisa at AFT19 proposed to pay watchtowers regularly. A drawback is that this scheme is vulnerable to bribery attack. Also it is not friendly to bitcoin because it relies on smart contracts. The second problem is fixed by Mirzaei et al. at FC21 with FPPW to make the watchtower service compatible with the bitcoin. Outpost, proposed by Khabbazian et al. at AFT19, is one of the first watchtowers that is lightweight (meaning that the watchtower doesn't need to store too much data) and compatible with bitcoin. Cerberus by Avarikioti et al. at FC20 and Brick by Avarikioti et al. at FC21 are schemes with incentives and penalty on watchtowers to encourage them to work properly. And punish the watchtowers if they play evil. Fail-safe watchtower by Liu et al. at AsiaCCS20 considers attacks like DoS on the watchtower and improved the scheme to protect the service against such attacks. Lastly, Aumayr et al. at CCS22 presented sleepy channel for users to go offline without hiring watchtowers. ## Improved PCN Protocols In the final section of this post, I want to give a quick overview on several improved PCN protocols. PCN protocols to improve certain properties, such as (global) atomicity, path restriction (for a certain network topology), interoperability(whether smart contracts are required), per party collateral, privacy and so on. Per party collateral is linear in the lightning network, as well as Anonymous Multi-Hop Locks (AMHL) by Malavolta et al. at NDSS19. Recent protocols can achieve constant per party collateral, including Sprites by Miller et al. at FC19, Atomic Multi-Channel Updates (AMCU) by Egger et al. at CCS19, Blitz by Aumayr et al. at Usenix security22, and Thora by Aumayr et al. at CCS22. Collateral is defined by the payment amount multiplied by the locktime, and it reflects the liquidity of a scheme. Large collaterals make the system vulnerable to griefing attacks, where the attacker's aim is to spoil the network by freezing as much collaterals as possible. Path restriction tells whether a protocol is applicable to other topology like a star shape. I hope to mention that AMCU is vulnerable to channel closure attack as discussed by Jourenko et al. in a scheme called payment trees at FC21 . It worths mentioning that Blitz is without HTLC contracts, which means that it is suitable for blockchains without HTLC. Also, it solves the staggered collateral problem in multi-hop HTLCs, which is, the locktime in MP-HTLC is often too long, because you have to leave out enough time for each users to react after one receives the secret. ## About the Author ✍🏻 Yunwen Liu Yunwen Liu is a cryptographer at Cryptape. She completed her Ph.D at COSIC, KU Leuven. She published 20+ research papers in venues including Eurocrypt and Journal of Cryptology. She has served on the program committees of Eurocrypt 2022 and ToSC 2022/2023. --- ## Understanding Fiber Liquidity Source: https://www.fiber.world/docs/res/liquidity Fiber uses payment channels to move value off-chain frequently and at low cost. Once a channel is opened, assets are locked in the channel. Later payments update the channel state off-chain instead of submitting every payment to CKB Layer 1. This creates the central constraint of payment channels: whether a payment can be completed depends not only on how many assets were locked when the channel was opened, but also on how those assets are currently distributed between the two sides and whether every hop along the payment path has enough usable liquidity. Understanding liquidity is therefore essential when building Fiber wallets, payment-acceptance applications, routing nodes, and payment services. ## Core Concepts Suppose Alice and Bob open a bidirectional channel with a total capacity of 1,000 CKB. Its current balances are: - Alice's side: 800 CKB - Bob's side: 200 CKB From Alice's perspective: - Outbound liquidity is approximately 800 CKB. It represents how much Alice can send toward Bob through this channel. - Inbound liquidity is approximately 200 CKB. It represents how much Alice can receive from Bob through this channel. From Bob's perspective, the directions are reversed: Bob has approximately 200 CKB of outbound liquidity and 800 CKB of inbound liquidity. After Alice pays Bob 100 CKB, the balances become 700 CKB for Alice and 300 CKB for Bob. The payment does not change the channel's total capacity, but it does change how much liquidity remains available in each direction. For clarity, this example omits channel reserves, fees, in-flight TLCs, and other policy limits. The amount that can actually be paid may be lower than the local balance shown in an interface. ## Why Having Funds Does Not Guarantee That You Can Pay or Receive On-chain balance, total channel capacity, and usable liquidity are three different concepts. For example, a merchant may hold enough assets at a CKB address and already run a Fiber node. But if no other node has placed funds on the side of a channel that can flow toward the merchant, the merchant still lacks inbound liquidity and cannot receive a large payment through that path. Similarly, a user may be connected to several nodes, but a payment can still fail when funds are concentrated on the wrong side of those channels for the intended direction. Common causes include: - The first hop does not have enough outbound liquidity. - The recipient's last hop does not provide enough inbound liquidity. - No single path has enough capacity. - Fees on viable paths exceed the sender's configured limit. - Too much channel liquidity is temporarily committed to in-flight TLCs. - Capacity in the network graph describes only a channel's total capacity, while real-time usable liquidity in each direction may be unavailable or stale. - A peer is offline, has disabled one direction, or has not yet brought the channel into a usable state. Applications should therefore never treat “sufficient balance” as a guarantee that a payment will succeed. ## How Multi-Hop Payments Use Liquidity Fiber does not require the sender and recipient to share a direct channel. A payment can pass through one or more intermediate nodes as long as the network contains a path with the correct direction and enough capacity. Suppose the path is: [text code block] Every hop along the path must have enough outbound liquidity in the payment direction. If any hop falls short, the entire path cannot carry the payment. Multi-hop routing makes existing liquidity more reachable, but it does not create new liquidity. ## What Multi-Path Payments Can Solve When no single path can carry the full amount, Fiber can use a multi-path payment (MPP) to split one payment across several paths. For example, a 300 CKB payment could be completed by three paths that can carry 120, 100, and 80 CKB respectively. Multi-path payments can: - Aggregate usable capacity distributed across several channels. - Increase the chance of finding viable routes for a large payment. - Reduce an application's dependence on one high-capacity channel. However, MPP still uses only existing liquidity. If there is not enough inbound liquidity near the recipient, splitting the payment into more parts cannot solve the problem. MPP may also use more paths or longer paths, with a routing fee charged along each one. Its total fee can therefore be higher than that of a single-path payment. The actual cost depends on each path's hop count, fee rates, and the way the payment is split. ## Common Ways to Manage Liquidity Different approaches solve problems at different levels. Developers should first determine whether they need to use existing liquidity, move existing liquidity, or introduce new liquidity. ### 1. Open a Direct Channel The most direct approach is for a payer, partner, or liquidity node to open a channel with the recipient and place funds on the side from which they can flow toward that recipient. This approach works well for: - Parties with a stable transaction relationship. - Merchants and frequent customers. - Businesses that can estimate payment direction and amount in advance. - Testnet demonstrations and early product validation. Its main costs are the on-chain transactions needed to open and close the channel and the capital tied up during the channel's lifetime. For a point-to-point use case that only needs customers to pay a merchant, the customer can fund and open a Fiber one-way channel to the merchant. A one-way channel is funded by the initiator, and funds can flow only from the initiator to the acceptor. It is always private, cannot route payments for third parties, and is unsuitable when bidirectional payments or channel rebalancing are required. ### 2. Use Network Liquidity Through Multi-Hop and MPP If both sender and recipient are already connected to liquid public nodes, an application can use multi-hop routing and MPP instead of opening a direct channel for every pair of users. This reduces the number of channels, but payment success still depends on network topology, directional liquidity at every hop, fee policies, and node availability. ### 3. Rebalance Channels After routing one-way traffic for an extended period, a routing node can gradually exhaust the local balance of some channels. Rebalancing uses a circular payment path: funds leave through one of the node's channels and return through another, changing the distribution of balances across the node's channels. For example, Alice operates a routing node and maintains two bidirectional channels, each with a total capacity of 1,000 CKB: [text code block] Alice has plenty of funds to send toward Bob but can barely continue forwarding payments toward Carol. If a viable path exists between Bob and Carol, Alice can construct a circular route from her existing channels and make a payment to herself: [text code block] Suppose the circular payment is 300 CKB. After it succeeds, the balances would be approximately: [text code block] Alice's total funds have not increased. She has moved approximately 300 CKB of local balance from the Bob channel to the Carol channel and paid routing fees along the way. She can now forward payments more evenly in both directions. Rebalancing works well for: - Routing nodes that maintain several bidirectional channels. - Nodes with enough total funds but with those funds distributed across the wrong channels. - Operators who want to avoid immediately closing and reopening channels. Rebalancing does not increase a node's total funds, does not guarantee that an affordable circular route exists, and incurs routing fees. One-way channels do not participate in this kind of rebalancing. ### 4. Just-in-Time Channels or Liquidity Service Providers A common service model is for a liquidity service provider (LSP) to open or supply a channel on demand when a user is about to receive a payment but lacks inbound liquidity. This is often called a just-in-time (JIT) channel or LSP service. For example, Alice has just registered as a merchant. Bob wants to pay her 500 CKB, but Alice has no inbound liquidity that can carry the payment. Alice's wallet forwards the payment request to Carol, a liquidity service provider: 1. Carol checks the incoming-payment request and the state of Alice's node. 2. Carol uses her own funds to open a channel with Alice and places at least 500 CKB of usable balance on the side from which it can flow toward Alice. 3. Once the channel is usable, Bob's payment reaches Carol either through a direct Bob-Carol channel or a multi-hop path, then completes its final hop through Carol's new channel with Alice. 4. Alice pays Carol an agreed channel-opening fee or service fee, or the fee is deducted from the payment she receives. Alice can obtain inbound liquidity when she needs to receive a payment, without finding a channel counterparty in advance or paying long-term service fees before demand exists. Carol, in turn, provides a service that requires her to lock capital, keep a node online, and bear the risk that channel opening fails. This can lower the barrier to a new user's first incoming payment, but the service design must make several points explicit: - Who supplies the channel funds. - How service fees are calculated. - How long the channel must remain open. - How the provider validates the incoming-payment demand. - What happens if opening fails or the channel closes early. - How much the user must trust the provider. ### 5. Liquidity Markets A liquidity market treats inbound liquidity as a time-limited service. A party that needs receiving capacity publishes its desired capacity, duration, and price. A liquidity provider opens or maintains a channel for that party in return for a fee. For example, Alice runs a store and expects to need an additional 1,000 CKB of inbound liquidity for the next 30 days. She does not need one particular payment to arrive immediately; she wants reliable receiving capacity throughout that period. She publishes the following order in the market: [text code block] After Bob accepts the order, he uses his own funds to open or maintain a channel that provides Alice with approximately 1,000 CKB of inbound liquidity. Alice is paying for the right to use that liquidity for 30 days, not buying the 1,000 CKB that Bob locked in the channel. When the period ends, Bob can close the channel and recover any funds that have not moved to Alice. This arrangement still needs enforcement rules. If Bob maintains the channel for only 10 days before closing it, the system must decide how to refund the unused rent. If Alice has paid but Bob never provides the agreed capacity, the system also needs cancellation, refund, challenge, or penalty mechanisms. What a matching platform, an on-chain contract, and the participants can each verify determines the market's trust model. Compared with ordinary channel management, a liquidity market must also address: - How buyers and sellers discover and match with each other. - How rent payment is coordinated with channel creation. - How to prove that the provider supplied the agreed channel. - How to settle the unfulfilled portion if the channel closes early. - How to limit misconduct by buyers, sellers, or the matching platform. - Which states an on-chain contract can verify and which must be confirmed by participants, services, or other mechanisms. CKB's programmable Cell model provides design space for orders, escrow, time-based release, and challenge mechanisms. An on-chain contract, however, does not automatically know every off-chain Fiber state. Designers must still define the trust boundary between on-chain and off-chain systems. ## Choosing an Approach | Need | Consider first | Main limitation | |---|---|---| | Frequent one-way payments between two fixed participants | One-way channel | Cannot pay in reverse or route for third parties | | General users paying through the public network | Multi-hop routing and MPP | Depends on usable liquidity along the entire path | | A routing node has enough total funds but imbalanced channels | Channel rebalancing | Requires a circular route and incurs routing fees | | A new user's first incoming payment | JIT/LSP service | Depends on provider availability, pricing, and trust model | | A merchant needs stable receiving capacity for a period | Pre-opened channels or rented inbound liquidity | Capital lockup plus enforcement and settlement design | | A participant wants to supply network liquidity for revenue | Public bidirectional channels or a liquidity market | Requires continuous monitoring, capital management, and risk control | These approaches are not mutually exclusive. A wallet can use MPP to improve everyday payment success and request a channel from an LSP when inbound liquidity is insufficient. A routing node can rebalance periodically and add new funds when needed. ## Common Misconceptions ### “Channel capacity is the amount I can send” No. Total capacity is distributed between the two sides of a channel. Sending capacity depends on the usable balance in the sending direction and is further limited by reserves, fees, and TLC state. ### “MPP can solve every liquidity shortage” MPP can only combine liquidity that already exists across multiple paths. It cannot create inbound liquidity that does not exist near the recipient. ### “Rebalancing increases a node's funds” It does not. Rebalancing only changes where a node's funds are located across its channels, and it normally incurs routing fees. ### “One large channel is enough” A large channel can increase capacity in one direction, but it can also create a single point of dependency. Connection quality, peer uptime, fee policy, and alternative paths also affect payment success. ### “An on-chain contract can directly verify every off-chain service” It cannot. A contract can verify only facts explicitly supplied by a transaction and provable under on-chain rules. When combining Fiber channels with on-chain orders, designers should specify the source of every state and the associated trust assumptions. ## Further Reading - Fiber: Unidirectional Channel - Fiber: Channel Lifecycle - Fiber: Channel Rebalancing - Fiber: Payment Lifecycle and Multi-Path Payments - Fiber: Multi-Hop Payments - Fiber Liquidity Solutions Research - Community discussion: Can CKB Give Fiber a Better Amboss? - Community project log: Building Opticrum on Fiber --- ## Fiber P2P Message Protocol Source: https://www.fiber.world/docs/res/p2p-message This document describes the protocol between nodes of the fiber network on CKB, used to establish payment channels, construct transactions, close channels, and perform payment operations. Essentially, it is an adaptation and simplification of [BOLT 02] to suit the transaction structure of CKB. Please note that BOLT 02 uses HTLC (Hashed Time Locked Contract) for payment operations, and many message definitions use HTLC as field names and descriptions. In this document, we will use TLC instead of HTLC to facilitate future support for PTLC (Point Time Locked Contract). Additionally, this protocol will use [Molecule] to define message formats, making it easier to integrate with CKB. This document is a work in progress and may be updated at any time. ## Channel Establishment We use a protocol similar to BOLTS 02 Channel Establishment v2 to establish payment channels, an example process is as follows: [code block] ### OpenChannel OpenChannel is sent by the initiator of the channel to the receiver of the channel to request the establishment of a payment channel. [code block] - chainhash: Chain genesis block hash - channelid: The ID of the channel, which is a temporary ID derived from the tlcbasepoint before the channel is officially established. After the channel is established, it will be replaced with the actual channel ID, derived from a blake2b hash of the sorted tlcbasepoints of both parties. - fundingtypescript: Specifies the asset type of the channel. If empty, it indicates using CKB native token as the asset. - fundingamount: The amount of assets the channel initiator wants to contribute. - fundingfeerate: Funding transaction fee rate, in shannons per kilo-bytes. - commitmentfeerate: Commitment transaction fee rate, in shannons per kilo-bytes. - maxtlcvalueinflight: The maximum total value of unconfirmed TLCs (Time Locked Contracts) that the channel initiator can accept in this channel. - maxtlcnumberinflight: The maximum number of unconfirmed TLCs that the channel initiator can accept in this channel. - mintlcvalue: The minimum value of TLCs that the channel initiator can accept. - toselfdelay: The delay time for the channel initiator to unlock the outputs from the commitment transaction, in EpochNumberWithFraction. - fundingpubkey: The pubkey of the channel initiator, used for generating 2-2 multisig contracts. - tlcbasepoint: The master key used to derive child keys required for tlcs, we will use the same method as lightning network to derive these keys, see [Secret Derivations] for more details. - firstpercommitmentpoint: - secondpercommitmentpoint: - nextlocalnonce: Used for generating partial signatures for unlocking 2-2 Schnorr multisig. - channelflags: Channel flags, currently only using one bit to indicate whether to broadcast this channel information on the P2P network. ### AcceptChannel AcceptChannel is sent by the receiver of the channel to the initiator of the channel to accept the establishment of a payment channel. [code block] - channelid: The ID of the channel, must match the channelid in OpenChannel. - fundingamount: The amount of assets the channel receiver wants to contribute, can be 0, indicating no contribution. - maxtlcvalueinflight: The maximum total value of unconfirmed TLCs that the channel receiver can accept in this channel. - maxtlcnumberinflight: The maximum number of unconfirmed TLCs that the channel receiver can accept in this channel. - mintlcvalue: The minimum value of TLCs that the channel receiver can accept. - toselfdelay: The delay time for the channel receiver to unlock the outputs from the commitment transaction, in EpochNumberWithFraction. - fundingpubkey: The pubkey of the channel receiver, used for generating 2-2 multisig contracts. - tlcbasepoint: See the description in OpenChannel message. - firstpercommitmentpoint: - secondpercommitmentpoint: - nextlocalnonce: Used for generating partial signatures for unlocking 2-2 Schnorr multisig. ### CommitmentSigned After both parties establish the funding transaction and complete the signing of the commitment transaction in the Transaction Collaboration process, they will send CommitmentSigned messages to each other. [code block] The meaning of each field is as follows: - partialsignature: The partial signature for unlocking the 2-2 Schnorr multisig. - nextlocalnonce: Used for generating the next commitment transaction partial signature. ### TxSignatures After both parties have signed the commitment transaction and verified the correctness of each other's signatures, they need to send TxSignatures messages to each other to complete the signing of the funding transaction. [code block] Here, txhash is the hash of the corresponding funding transaction, and witnesses corresponds to the signed witnesses of all inputs of the contributor. If a party's contribution is 0 (i.e., no inputs), an empty witnesses message should also be sent to complete the message exchange. In addition, in order to simplify the message interaction process, we defined that the party with the lesser amount of funding must send the TxSignatures message first, in the case of the same amount, the one with the smaller fundingpubkey must send the TxSignatures message first. This avoids deadlocks caused by both parties waiting for the other party's TxSignatures message at the same time. ### ChannelReady After completing the signing and broadcasting the funding transaction, both parties send ChannelReady messages to each other to indicate the channel is ready. [code block] ## Transaction Collaboration In the fiber network, the channel initiator begins the transaction construction protocol using the TxUpdate message. The responder replies with either TxUpdate or TxComplete messages. The transaction construction process is completed when both nodes have sent and received consecutive TxComplete messages. Here is the Dual Funding example, A initially funds part of the channel (2 inputs), then B adds their contribution (1 input). A replies with TxComplete, and B responds with TxComplete, completing the transaction construction process. [code block] Since CKB's transaction structure is more complex than Bitcoin's, the message structure is simplified compared to BOLT 02 interactive transaction construction. We use the full CKB transaction structure for transaction collaboration, without defining separate messages like txaddinput, txaddoutput, txremoveinput, and txremoveoutput. Nodes are required to parse the inputs of the transactions and do not need to provide previous tx in the messages. The specific message definitions are as follows: ### TxUpdate The TxUpdate message is used by the channel initiator to start the transaction construction protocol. [code block] Both parties must save the funding tx field of the previous message to compare it with the latest message field. They must also mark which inputs/outputs belong to their side. If a TxUpdate message from the other party removes or modifies inputs/outputs from their side, it is considered an illegal operation, and the entire process should be terminated. ### TxComplete After successfully exchanging TxComplete messages, both parties should have constructed the transaction and move to the next part of the protocol to exchange signatures for the commitment transaction. [code block] ### TxAbort During the transaction collaboration process, a node can send a TxAbort message to terminate the collaboration before sending the TxSignatures message. [code block] ### TxInitRbf After broadcasting the funding transaction, if the channel initiator finds that the fee is insufficient, they can send a TxInitRbf message to request the other party's cooperation in performing RBF (Replace-By-Fee) operation to increase the fee and rebroadcast the funding transaction. [code block] ### TxAckRbf Upon receiving a TxInitRbf message, the channel responder can send a TxAckRbf message to agree to the RBF operation. [code block] After receiving the TxAckRbf message from the other party, the channel initiator can restart the process of funding transaction collaboration with the new fee rate. It should be noted that the new funding transaction must have overlapping inputs with the previous funding transaction to ensure it meets the RBF rules. ## Channel Closing Nodes can negotiate to close a channel mutually, unlike a unilateral close, which allows nodes to immediately obtain funds. The process of closing a channel is as follows: [code block] ### Shutdown Any node can send a Shutdown message to request the closure of the channel. [code block] The closescript specifies the lock script to which the assets will be sent when the channel is closed. ### ClosingSigned After completing all pending Time Locked Contracts (TLCs) in the channel, either party can send a ClosingSigned message to sign the close transaction. [code block] If the receiver verified the correctness of the signature, they will respond with a ClosingSigned message, completing the channel closure. ## Payment Operation After establishing a channel, nodes can perform payment operations by sending AddTlc messages for payment requests and then updating the commitment transactions through CommitmentSigned and RevokeAndAck messages. Here is an example process: [code block] ### AddTlc Either node can send an AddTlc message to the other party to initiate a payment operation. This message can also be used to forward payment requests from other nodes. [code block] - channelid: ID of the channel. - tlcid: ID of the TLC (Time Locked Contract), used to uniquely identify a TLC. The first TLC in the channel has an ID of 0, and subsequent IDs increment by 1. - amount: Amount of assets requested for payment. - paymenthash: Hash value used to identify the payment request for subsequent payment verification. - expiry: Expiry time of the payment request, specified as an absolute timestamp. When forwarding a payment request, this field should be decremented appropriately. ## RevokeAndAck Upon receiving the CommitmentSigned message and verifying the signature, the node may reveal the previous commitment transaction secret to the other party by sending a RevokeAndAck message. [code block] - percommitmentsecret: Secret used to generate the revocation secret key for the previous commitment transaction. - nextpercommitmentpoint: Point used to generate the next revocation public key. - nextlocalnonce: Used for generating the partial signature for the next commitment transaction. Upon receiving the RevokeAndAck message, the node should update the remote commitment transaction. ## RemoveTlc To simplify the implementation, only the recipient of the AddTkc message can remove the TLC. [code block] - channelid: ID of the channel. - tlcid: ID of the TLC being removed. - reason: Reason for removing the TLC, which can be either RemoveTlcFulfill or RemoveTlcFail. - RemoveTlcFulfill: Contains the paymentpreimage required to fulfill the payment. - RemoveTlcFail: Contains an errorcode indicating the reason for failure. [BOLT 02]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#channel-establishment-v2 [Molecule]: https://github.com/nervosnetwork/molecule [Secret Derivations]: https://github.com/lnbook/lnbook/blob/54453c7b1cf82186614ab929b80876ba18bdc65d/07paymentchannels.asciidoc#revocationsidebar --- ## Gossip Protocol Source: https://www.fiber.world/docs/res/gossip-protocol This note documents the Fiber gossip subsystem from an engineering perspective. ## 1. What Gossip Solves Fiber nodes need a shared(ish) view of the network to do routing and validation: - Node information (identity, addresses, feature bits). - Public channels (existence of an edge between two nodes). - Channel state updates (fee policy, HTLC constraints, enabled/disabled, direction). In a decentralized network: - Each node connects to only a small subset of peers. - Peers can disconnect, partitions happen, nodes restart. - Peers can be malicious (spam, eclipse-style isolation). So gossip must provide: - Catch-up (history sync) after downtime or partitions. - Incremental updates (subscription) once caught up. - Verification and dependency handling so "received" does not automatically mean "accepted/applied". - Resource bounds (rate limiting, peer targeting) for safety and scalability. ## 2. Key Concepts in Fiber ### 2.1 Broadcast messages and the Cursor Fiber treats gossip as an ordered stream of broadcast messages. - Messages are stored with a monotonic ordering key called a Cursor. - Conceptually: Cursor = (timestamp, message_id). This enables a clean primitive: - "Give me messages after cursor X, up to N items." This is implemented by the GossipMessageStore trait in gossip.rs, backed by the DB store. ### 2.2 Two sync modes: active vs passive Fiber tracks per-peer sync state via PeerSyncStatus (in gossip.rs). - Active syncing (pull history) - State: ActiveGet(...) - Mechanism: repeatedly send GetBroadcastMessages(after_cursor, count) and advance the cursor. - Completion: when the peer returns an empty batch, the peer is considered "caught up". - Passive syncing (subscribe/push updates) - State: PassiveFilter(cursor) - Mechanism: send BroadcastMessagesFilter(after_cursor) so the peer pushes updates. - Goal: reduce silos and keep the node up-to-date without constant pulling. These are not competing modes; they are typically used in phases: 1. Active sync with a limited number of peers to catch up. 2. Passive subscriptions to keep up continuously. ## 3. Architecture: Main Components ### 3.1 Actors and responsibilities Fiber splits the gossip subsystem into a few cooperating actors: - GossipActor (control plane) - Tracks connected peers and their PeerSyncStatus. - Decides which peers to actively sync vs passively subscribe. - Routes incoming protocol messages to the right internal component. - Triggers periodic maintenance (network tick, pruning stale messages). - GossipSyncingActor (data plane for active syncing) - Dedicated per-peer worker for pulling history. - Sends GetBroadcastMessages and processes GetBroadcastMessagesResult. - On completion, notifies GossipActor. - PeerFilterProcessor (subscription management) - Manages subscription filters for peers. - Updates the store subscription cursor when a peer changes aftercursor. - ExtendedGossipMessageStore (store actor + fan-out) - Receives batches of messages from GossipActor and saves them. - Performs dependency/ordering handling (important for correctness). - Fans out GossipMessageUpdates to subscribers. ### 3.2 Where NetworkGraph fits NetworkActor subscribes to gossip store updates and applies them into NetworkGraph. This is the primary pipeline that turns "gossip messages" into "routing graph state". ## 4. Relationship Diagram (Network + Gossip + Store) [mermaid code block] ## 5. Primary Data Flows ### 5.1 Local broadcast (originating messages) When Fiber produces a local broadcast message (e.g., node announcement or public channel messages), NetworkActor sends it into gossip via GossipActorMessage::TryBroadcastMessages(...). GossipActor passes these to ExtendedGossipMessageStore (save + fan-out) and to peers. ### 5.2 Active syncing (pull history) Active syncing is driven by GossipSyncingActor: 1. Start with a cursor. 2. Send GetBroadcastMessages(aftercursor). 3. Receive GetBroadcastMessagesResult(messages). 4. Save messages into store, advance cursor. 5. Repeat until empty result. ### 5.3 Passive syncing (subscribe/push) Passive syncing is filter-based: 1. Send BroadcastMessagesFilter(aftercursor) to a peer. 2. The peer responds over time with BroadcastMessagesFilterResult(messages). 3. Save messages into store. This is how a node stays updated after catching up. ## 6. Notes and Pitfalls ### 6.1 "Received" does not mean "Applied" The observable pipeline is: 1. Protocol receives a gossip message. 2. GossipActor routes it. 3. Store verifies / checks dependencies / persists. 4. Subscribers receive GossipMessageUpdates. 5. NetworkGraph applies updates. If any step drops or delays a message, you can see logs like "received X" without seeing the graph change. ### 6.2 Immutable vs mutable messages Some messages are effectively immutable and may be broadcast only once (e.g., channel announcements). If your sync strategy starts from a cursor that is too new, you can miss these older-but-still-relevant messages permanently. By contrast, node announcements or channel updates tend to refresh over time, so missing one update is often healed by later updates. ### 6.3 Cursor selection and "safe" cursors Fiber uses the concept of a "safecursor" to avoid syncing from genesis forever. This is practical, but it must be chosen carefully: - Too old → bandwidth waste. - Too new → can miss important older messages. This trade-off becomes visible when bridging two previously disconnected clusters. ### 6.4 Dependency and ordering Channel-related updates can depend on: - Having a channel announcement present. - Having chain/onchain info available. - Satisfying timestamp and signature constraints. A robust store layer typically needs to handle out-of-order arrival and cache/resolve dependencies. ### 6.5 Eclipse-resistance considerations Fiber distinguishes targeted outbound passive syncing peers to reduce eclipse risk. Practical notes: - Prefer maintaining subscriptions with multiple independent outbound peers. - Limit concurrent active syncing peers to bound bandwidth and CPU. ## 7. References (code) - Gossip protocol + actors: crates/fiber-lib/src/fiber/gossip.rs - Network startup + subscription wiring: crates/fiber-lib/src/fiber/network.rs - Persistent storage for broadcast messages: crates/fiber-lib/src/store/store_impl/mod.rs - Graph application of gossip updates: crates/fiber-lib/src/fiber/graph.rs --- ## Cross-Chain HTLC Source: https://www.fiber.world/docs/res/cross-chain-htlc This document describes the Cross-Chain Hub (CCH) feature in Fiber, which enables atomic swaps between CKB (via Fiber) and Bitcoin (via Lightning Network). If you want to integrate cross-chain payments or run a CCH node, you are in the right place. ## What Is CCH? CCH (Cross-Chain Hub) is a service built into Fiber that bridges the CKB ecosystem and the Bitcoin Lightning Network using Hash Time-Locked Contracts (HTLCs). It lets users atomically swap wrapped BTC on CKB for native BTC on Lightning, and vice versa — without trusting a third party with custody. The key guarantee is atomicity: either both legs of the swap settle, or neither does. The CCH operator (commonly called Ingrid in the literature) cannot steal funds because the same payment hash / preimage locks both sides. ## Why It Matters For developers building on Fiber, CCH unlocks several possibilities: - Payment interoperability: A Fiber user can pay compatible Lightning invoices, and a Lightning user can pay compatible Fiber invoices. - Liquidity bootstrapping: CCH operators can earn fees by providing swap liquidity between the two networks. - No custodial risk: The swap is enforced by HTLCs on both chains, not by legal contracts. ## Core Concepts ### Shared Payment Hash Both sides of the swap use the same SHA-256 payment hash. The incoming invoice is locked with this hash; the outgoing payment is also locked with the same hash. When the outgoing payment succeeds, the CCH learns the preimage and uses it to settle the incoming invoice. ### Time-Lock Safety The CCH must have enough time to claim the outgoing preimage and then settle the incoming invoice before the incoming time lock expires. This is enforced by two rules: 1. Static check at order creation: The outgoing invoice's minimum final expiry must be less than half of the incoming invoice's final expiry. 2. Dynamic check before sending outgoing payment: After the incoming payment is accepted, the CCH computes the remaining time on the incoming HTLC and caps the outgoing route expiry to at most half of that remaining time. If there is insufficient time, the order is failed automatically. ### Wrapped BTC CCH swaps use a wrapped BTC UDT on CKB. The invoice generated by the CCH for CKB payments includes the udttypescript of this wrapped BTC, ensuring the payer knows exactly which token to transfer. The CCH operator configures the wrapped BTC type script via: - wrappedbtctypescriptargs — the script args (used when Fiber runs in-process) - wrappedbtctype_script — the full JSON script (required in standalone mode) Testnet cWBTC Faucet: Get free cWBTC test tokens at faucet-cwbtc.ckb.dev to try CCH swaps on testnet without touching mainnet BTC. See Network Resources for more testnet tools. ## Architecture CCH is implemented as a set of cooperating actors inside the Fiber node. Here is the high-level architecture: [code block] ### Key Components | Component | Role | |-----------|------| | CchActor | Main actor. Handles RPC requests, manages order lifecycle, subscribes to store changes and tracking events. | | CchFiberAgentRef | Unified interface to Fiber. Can operate in-process (direct actor calls) or over HTTP RPC (standalone mode). | | LndTrackerActor | Tracks Lightning invoices and payments via LND gRPC. Emits CchTrackingEvents back to CchActor. | | Scheduler | Schedules order expiry and final-order pruning using a priority queue. | | ActionDispatcher | Dispatches concrete actions (TrackIncomingInvoice, SendOutgoingPayment, TrackOutgoingPayment, SettleIncomingInvoice) based on order state. | | CchOrderStateMachine | Pure state machine that transitions orders between statuses based on events. | ### Order State Machine An order progresses through these statuses: [code block] | Status | Meaning | |--------|---------| | Pending | Order created. Waiting for the incoming invoice to be paid / accepted. | | IncomingAccepted | Incoming HTLC has been accepted (e.g., LND hold invoice is Accepted, or Fiber invoice is Received). Ready to send outgoing payment. | | OutgoingInFlight | Outgoing payment is in flight. | | OutgoingSuccess | Outgoing payment settled. Preimage obtained. Ready to settle incoming invoice. | | Success | Both sides settled. Order complete. | | Failed | Something went wrong (expired, payment failed, invalid invoice, etc.). | State transitions are validated by CchOrderStateMachine::allow_transition. Invalid transitions are rejected and logged. ### Action Lifecycle Actions are triggered by state changes: 1. On order creation (on_starting): TrackIncomingInvoice is scheduled. 2. On IncomingAccepted (on_entering): SendOutgoingPayment + TrackOutgoingPayment are scheduled. 3. On OutgoingInFlight (on_entering): TrackOutgoingPayment is scheduled (ensures we keep watching). 4. On OutgoingSuccess (on_entering): SettleIncomingInvoice is scheduled. 5. On final states (Success / Failed): No further actions. Final orders are pruned after their scheduled order expiry time plus an additional 21-day retention period. Actions are executed with exponential backoff retry (1 second base, capped at 10 minutes). Permanent errors fail the order immediately; transient errors are retried. ## Two Swap Directions ### Send BTC (CKB → Lightning) Alice wants Bob (on Lightning) to receive BTC. 1. Bob creates a Lightning invoice for, say, 10,000 sats. 2. **Alice calls send_btc** with Bob's btcpayreq. 3. CCH validates: - The BTC invoice network matches the CKB network (mainnet ↔ mainnet, testnet ↔ testnet, regtest ↔ regtest). - The BTC invoice has an amount. - The BTC invoice's minfinalcltvexpirydelta is safe relative to CKB's final TLC expiry. - The invoice has not expired and has enough remaining expiry. 4. CCH computes the fee: fee = base_fee + amount * feerate / 1000_000. 5. CCH creates a Fiber invoice for amount + fee sats, locked with the same payment_hash, using the wrapped BTC type script. 6. Alice pays the Fiber invoice through the Fiber network. 7. CCH detects the incoming payment (via store changes or LND tracking). 8. CCH sends the outgoing Lightning payment to Bob's invoice. 9. Upon success, CCH obtains the preimage and settles the Fiber invoice internally. 10. Order reaches Success. ### Receive BTC (Lightning → CKB) Alice (on Fiber) wants to receive BTC from Bob (on Lightning). 1. Alice creates a Fiber invoice for, say, 10,000 wrapped-BTC-sats, using the wrapped BTC type script and hash_algorithm = sha256. 2. **Alice calls receive_btc** with her fiberpayreq. 3. CCH validates: - The Fiber invoice currency matches the configured network. - The Fiber invoice has an amount. - The Fiber invoice's finaltlcminimumexpirydelta is safe relative to BTC's final CLTV expiry. - The invoice's UDT type script matches the configured wrappedbtctype_script. - The hash algorithm is sha256 (required for LND compatibility). 4. CCH computes the fee: fee = base_fee + amount * feerate / 1000_000. 5. CCH creates a Lightning hold invoice via LND for (amount + fee) * 1000 millisats, locked with the same payment_hash. 6. Bob pays the Lightning invoice through the Lightning network. 7. LND detects the incoming payment and emits an Accepted event. 8. CCH sends the outgoing Fiber payment to Alice's invoice. 9. Upon success, CCH obtains the preimage from the Fiber payment. 10. CCH settles the Lightning hold invoice with the preimage. 11. Order reaches Success. ## Running a CCH Node ### Configuration To run CCH, add cch to the services list and configure the cch section in your Fiber config.yml (or provide the equivalent CLI flags / environment variables): [yaml code block] | Config Key | Default | Description | |-----------|---------|-------------| | lndrpcurl | https://127.0.0.1:10009 | LND gRPC endpoint. | | lndcertpath | — | Path to LND TLS certificate. Omit for well-known CAs. | | lndmacaroonpath | — | Path to LND macaroon for authentication. | | wrappedbtctypescriptargs | — | Args for the wrapped BTC UDT type script. Must have 8 decimal places. | | wrappedbtctype_script | — | Full JSON type script. Required in standalone mode. | | orderexpirydeltaseconds | 129600 (36h) | How long an order remains active before auto-failure. | | btcfinaltlcexpirydeltablocks | 360 (~60h) | Final hop CLTV expiry for outgoing Lightning invoices. | | ckbfinaltlcexpirydeltaseconds | 216000 (60h) | Final hop TLC expiry for outgoing Fiber invoices. | | minoutgoinginvoiceexpirydeltaseconds | 21600 (6h) | Minimum acceptable expiry for outgoing invoices. | | basefeesats | 100 | Flat fee per swap. | | feeratepermillionsats | 3000 | Proportional fee rate (parts per million, so 3000 = 0.3%). | | maxoutgoingfeepercentage | 80 | Maximum share of the collected fee available for the outgoing route; the default leaves at least 20% for the operator. | | fiberrpc_url | — | When set, CCH runs as a standalone service talking to Fiber over HTTP/WebSocket. | ### Standalone Mode CCH can run as a separate process from the Fiber node. In this mode: - fiberrpcurl must point to a running Fiber node's HTTP RPC endpoint. - wrappedbtctypescript must be provided as full JSON (the contracts context is not initialized). - CCH subscribes to Fiber store changes via WebSocket (subscribestorechanges). - Reconnects automatically on WebSocket failure. This is useful for scaling or isolating the swap service from the payment routing node. ### In-Process Mode When CCH and Fiber run in the same process (the default if both are configured): - CCH talks to the NetworkActor directly via actor messages. - Store changes are propagated through an OutputPort, so CCH reacts instantly to invoice and payment updates. - No HTTP/WebSocket overhead. ## RPC API CCH exposes three JSON-RPC methods. For detailed schema, see the CCH API Reference. ### sendbtc Create a CCH order to pay a Lightning invoice from CKB. [json code block] ### receivebtc Create a CCH order to receive BTC via a Fiber invoice. [json code block] ### getcchorder Poll an order by payment hash to monitor its status. [json code block] ## Security & Safety Mechanisms ### Preimage Verification When the outgoing payment succeeds, the state machine verifies that the returned preimage hashes to the original paymenthash using SHA-256. If there is a mismatch, the order is failed. This prevents a malicious network from tricking the CCH with an incorrect preimage. ### Expiry Cascade The CCH guarantees that it always has a time buffer to settle the incoming side after the outgoing side succeeds: - Static validation at order creation ensures the outgoing invoice's final expiry is less than half of the incoming invoice's final expiry. - Dynamic validation before sending the outgoing payment computes the actual remaining time on the incoming HTLC and caps the outgoing route expiry to half of that. If the remaining time is too short, the order is failed. ### Permanent vs. Transient Errors Actions distinguish between permanent and transient failures: - Permanent (e.g., invalid payment request, invoice expired, no path found, payment hash mismatch): The order is failed immediately. - Transient (e.g., network timeout, LND temporary error): The action is retried with exponential backoff. ### Order Pruning Final orders (Success or Failed) are kept in the database for 21 days (configurable via PRUNEDELAYSECONDS in the scheduler) and then automatically deleted. This prevents unbounded database growth. ## Error Reference Common errors you may encounter when creating or processing orders: | Error | When It Happens | |-------|-----------------| | BTCInvoiceNetworkMismatch | The Lightning invoice is for a different BTC network than the CKB network maps to. | | CKBInvoiceNetworkMismatch | The Fiber invoice currency does not match the CCH node's configured network. | | BTCInvoiceFinalTlcExpiryDeltaTooLarge | The Lightning invoice's CLTV expiry leaves insufficient margin for the CKB side. | | CKBInvoiceFinalTlcExpiryDeltaTooLarge | The Fiber invoice's TLC expiry leaves insufficient margin for the BTC side. | | OutgoingInvoiceExpiryTooShort | The invoice expires sooner than minoutgoinginvoiceexpirydeltaseconds. | | WrappedBTCTypescriptMismatch | The Fiber invoice's UDT type script does not match the configured wrapped BTC. | | CKBInvoiceIncompatibleHashAlgorithm | The Fiber invoice uses a hash algorithm other than SHA-256. | | SendBTCOrderAmountTooLarge / ReceiveBTCOrderAmountTooLarge | Amount or fee calculation overflowed. | | SettledPaymentMissingPreimage | Outgoing payment reported success but no preimage was returned. | | PreimageHashMismatch | The returned preimage does not hash to the expected payment hash. | ## Building on Top of CCH If you are a developer integrating cross-chain swaps into your wallet or dApp, here are some practical patterns: ### Polling Loop for Order Status After creating an order, poll getcchorder until the status is terminal: [rust code block] ### Fee Estimation Before creating an order, you can estimate the fee locally: [rust code block] Both basefeesats and feeratepermillionsats are exposed via the CCH operator's config. A well-designed UI should display the estimated fee to the user before they confirm the swap. ### Wrapped BTC Discovery The CCH response includes wrappedbtctypescript. If you are building a wallet that pays CCH-generated Fiber invoices, verify that the invoice's udttypescript matches a known wrapped BTC token. Do not blindly pay invoices with unknown type scripts. ## Source Code Pointers If you want to dive deeper into the implementation, here are the key files in the Fiber repository: | File | What It Contains | |------|------------------| | crates/fiber-lib/src/cch/mod.rs | Module exports and public types. | | crates/fiber-lib/src/cch/actor.rs | Main CchActor: message handling, sendbtc, receivebtc, store-change mapping. | | crates/fiber-lib/src/cch/order/statemachine.rs | CchOrderStateMachine: validates state transitions. | | crates/fiber-lib/src/cch/actions/ | Action dispatchers and executors for each step of the swap. | | crates/fiber-lib/src/cch/trackers/lndtrackers.rs | LndTrackerActor: LND invoice/payment tracking with concurrency limits. | | crates/fiber-lib/src/cch/scheduler.rs | Order expiry and pruning scheduler. | | crates/fiber-lib/src/cch/cchfiberagent.rs | CchFiberAgentRef: abstracts in-process vs. HTTP RPC backends. | | crates/fiber-lib/src/cch/config.rs | CchConfig and default constants. | | crates/fiber-lib/src/rpc/cch.rs | JSON-RPC server implementation. | | crates/fiber-types/src/cch.rs | Core data types: CchOrder, CchOrderStatus, CchInvoice. | ## Future: PTLC Fiber plans to migrate from HTLC to Point Time-Locked Contracts (PTLC), which offer: - Improved privacy: No shared payment hash across hops (the hash is hidden via adaptor signatures). - Reduced on-chain footprint: Smaller witness data when channels are force-closed. - Enhanced security: Resistance to wormhole attacks in multi-hop routing. PTLC will replace the SHA-256 hash lock with Schnorr adaptor signatures. Until then, CCH requires hash_algorithm = Sha256 for LND compatibility. ## Related Topics - Invoice Guide — How Fiber invoices work - Hold Invoice — Deferred settlement for conditional payments - Multi-Hop Routing — How payments route through intermediate nodes - Payment Channel Network — HTLC concepts and security considerations - Glossary — Definitions of HTLC, PTLC, and other Fiber terms - CCH API Reference — Complete RPC schema --- ## Fiber RPC API Reference Source: https://github.com/nervosnetwork/fiber/blob/main/crates/fiber-lib/src/rpc/README.md The complete RPC API is maintained in the Fiber GitHub repository. See the link above for the latest version.