CKB KeyWayDocs

Fiber node

Call the user's Fiber node with Fiber's own method names, and know which calls the user approves.

Every KeyWay user has their own Fiber node. useFiber().node gives your app that node with fiber-js's official method names and parameters, so Fiber code you already have works unchanged.

import { useFiber } from "@ckb-keyway/react";

const { node } = useFiber();
if (!node) throw new Error("Start Fiber first"); // set once status is "running"

const info = await node.nodeInfo();
const { channels } = await node.listChannels({});
const { payments } = await node.listPayments({ limit: "0xa" });

const { invoice_address } = await node.newInvoice({
  amount: "0x5f5e100", // 1 CKB in shannons; currency defaults to Fibt
  description: "Coffee",
});

await node.sendPayment({ invoice }); // no dialog; may ask for the emailed code

node is available once useFiber().status is "running". The same node object and the same rules apply whether the node is hosted by KeyWay or runs in the browser.

What each method does

Every method is sorted into one of four kinds. Methods that aren't in the table don't exist on node, and the hosted backend refuses them with UNSUPPORTED_METHOD.

KindMethodsWhat happens
FreenodeInfo, listPeers, graphNodes, graphChannels, listChannels, getPayment, listPayments, buildRouter, newInvoice, getInvoice, parseInvoice, settleInvoice, cancelInvoiceNo dialog. These read state or create and manage invoices, so money only ever comes in. sendPayment with dry_run: true is free too.
PaymentsendPayment, sendPaymentWithRouterNo built-in dialog, because Fiber is for fast payments. Your app can add its own with approveFiberPayment. With a hosted node, payments count toward the user's 24-hour email-confirmation total.
ApproveopenChannelWithExternalFunding, shutdownChannel, connectPeer, disconnectPeer, updateChannelKeyWay shows a dialog first. Your app can replace it with its own approval prop. submitSignedFundingTx is part of opening a channel and runs inside openChannelWithExternalFunding.
BlockedopenChannel, acceptChannel, abandonChannelNever available. The first two spend the node's internal key instead of the user's account; abandonChannel discards channel state.

The table is exported as FIBER_METHODS, so your app can check a method at runtime:

import { FIBER_METHODS } from "@ckb-keyway/react";

FIBER_METHODS.sendPayment.kind; // "payment"

Approvals

MethodBuilt-in dialog showsReplace it withPreview type
openChannelWithExternalFundingAmount, token, CKB reserve, peer and network feeapproveFiberChannelOpenFiberChannelOpenPreview
shutdownChannelThe user's side that returns, the peer, and whether it's a force closeapproveFiberChannelCloseFiberChannelClosePreview
connectPeer, disconnectPeerThe peer, and for a disconnect, how many open channels go offlineapproveFiberPeerConnectFiberPeerPreview
updateChannelEach setting that changes, from old to newapproveFiberChannelSettingsFiberChannelSettingsPreview
sendPayment, sendPaymentWithRouterNo dialogapproveFiberPayment (adds one)FiberPaymentPreview

Each prop goes on KeyWayProvider and returns true or false, or a promise of either. When it returns false, the call rejects and nothing reaches the node.

<KeyWayProvider
  appId="keyway_..."
  approveFiberPayment={(preview) =>
    window.confirm(`Pay ${preview.amountText} with a fee of about ${preview.feeText}?`)
  }
>
  <App />
</KeyWayProvider>

openChannelWithExternalFunding builds, signs and submits the funding transaction from the user's CKB account in one call. Channel funding counts toward the email-confirmation total like any other transfer, and past the daily amount it needs the emailed code: a browser node's payments never pass through KeyWay's server, so the funding is the last point where KeyWay sees the money leave.

Parameters

KeyWay passes Fiber's documented parameters through after checking them. Anything Fiber doesn't document for a method is refused with "Unsupported parameter".

  • Amounts are 0x-prefixed hex strings in the asset's smallest unit: shannons for CKB (1 CKB = 100,000,000 shannons), or the token's own unit.
  • Invoices. currency defaults to Fibt (testnet) and expiry to 24 hours (0x15180 seconds). Pass payment_preimage, or payment_hash for a hold invoice you later settle or cancel, but not both. fallback_address is refused, so a payer's money can't be redirected on-chain. Descriptions can be up to 640 characters.
  • Payments take a testnet invoice, or keysend: true with target_pubkey and amount. sendPaymentWithRouter takes a router from buildRouter. custom_records can hold up to 2 KB.
  • Channels. KeyWay sets the funding and close scripts to the user's own account, so shutdown_script, funding_lock_script and close_script are refused. Forwarding fees (tlc_fee_proportional_millionths) can be at most 10%, tlc_expiry_delta at most 14 days, and a closing fee_rate at most 100,000 shannons per KB.
  • Peers connect by pubkey or by multiaddr address. They're never saved to the node's permanent peer store (save can only be false).
  • Pages. graphNodes and graphChannels return up to 256 entries per call, listPayments up to 100.

Hosted nodes also refuse peer addresses that resolve to private or internal networks, accept up to 32 connected peers, and hide the host's own network addresses from nodeInfo.

Tokens

Tokens are first class. Fiber channels and payments work in any token the peer accepts, such as RUSD on testnet. The tokens a node accepts, with their type scripts, are listed in nodeInfo().udt_cfg_infos.

const info = await node.nodeInfo();
const rusd = info.udt_cfg_infos.find((token) => token.name === "RUSD")?.script;
if (!rusd) throw new Error("This node doesn't accept RUSD");

// A 20 RUSD invoice (8 decimals) and a token payment.
await node.newInvoice({ amount: "0x77359400", udt_type_script: rusd, description: "Order 42" });
await node.sendPayment({ invoice, udt_type_script: rusd });

To open a token channel, pass udtTypeScript to useFiber().openChannel(); see Fiber payments. Payment previews and dialogs show token names and decimals for tokens KeyWay knows, and a short token ID and raw amount for others.

Email confirmation

Users can set a daily amount per asset, in CKB and in each token. Once what leaves their account in a rolling 24 hours reaches it, KeyWay emails a code that your app can't skip. With a hosted node, Fiber payments, including routing fees, count toward that total together with on-chain transfers. See Email confirmation.

A browser node runs in your app's page, so its payments never pass through KeyWay's server. They aren't counted toward the daily amount and never ask for the emailed code. In browser mode, FiberPaymentPreview.requiresEmailCode is always false.

Where the rules are enforced

  • Hosted node (fiberNode="hosted", the default): KeyWay's backend holds the method table and checks every call before it reaches the node. A compromised page can't call anything outside it. Calls to free methods are limited to 120 a minute per user.
  • Browser node (fiberNode="browser"): the SDK applies the same table in the page. Because the node runs in your app's page, code that compromises your app could bypass it. Protect the page with a strict Content Security Policy.

In browser mode, listPayments lists the payments sent from this device through the SDK, because browser nodes keep no payment history of their own.

Key custody

  • Hosted node. The node runs on KeyWay's servers, which hold that node's Fiber key, its CKB key and its channel state.
  • Browser node. The node runs in the user's browser and keeps its channel state in IndexedDB. Its Fiber key is created inside Lit and, each time the node starts, re-encrypted inside Lit to a one-time key generated in the user's browser. KeyWay's server only relays ciphertext and never sees the browser node's key. It is still trusted to authorize Lit to release the key to the signed-in user.

The user's CKB account key never leaves Lit in either mode.

Closing channels

A normal (cooperative) close needs the peer online and returns the user's side to their own address.

A force close (shutdownChannel({ channel_id, force: true })) settles on-chain without the peer, after a delay of one epoch, about 4 hours. In Fiber 0.9.1, a force-closed channel settles to the node's own key rather than to the channel's close address. As an interim measure until Fiber pays these settlements to the close address, KeyWay moves them to the user's address:

  • Browser node: Fiber's browser build has no watchtower of its own, so the SDK runs one in the page. The node hands it each channel's settlement data, which the SDK keeps in IndexedDB next to the node's own database. While the node runs, the SDK checks force-closed channels every minute and, once the delay has passed, claims the user's share to a key derived from the Fiber key, then sweeps that key to the user's address (it also sweeps when the node starts and every 10 minutes). A browser node only settles while it runs, so after the delay the channel settles the next time the user starts Fiber in that browser. The settlement data lives in that browser's IndexedDB until the user signs out, which backs it up to KeyWay encrypted. The in-page watchtower doesn't yet settle a channel that was force-closed with payments still in flight (pending TLCs); Fiber's native watchtower on a hosted node does.

    Punishing a peer that publishes an old channel state can't wait for the tab: it must happen within the channel's delay (about 4 hours). So the SDK also registers each browser channel with KeyWay's watchtower and sends it the newest revocation after every payment. That data is a signature both parties made over one output that pays the whole channel to the user's own address; KeyWay can broadcast it (paying the fee itself) but can't send the money anywhere else. The SDK sends only the channel's two funding public keys and that revocation data: the node key, the settlement key, TLC keys, preimages and balances stay in the browser. While an update hasn't reached KeyWay yet, closing the tab asks the user to confirm. Token channels aren't watched by KeyWay yet: keep the tab open, or close them, while they hold funds. What browser mode still needs from the user: open the wallet now and then, so the in-page watchtower can claim their share after a peer force-closes honestly (there is no deadline for that), and keep the tab open while a payment is in flight (an expiring in-flight payment is settled only by a running node). The node hands over a channel's data only when the channel is funded, so browser channels opened before the SDK had the in-page watchtower, including any force-closed before upgrading, are not covered and their force-closed share isn't claimed: close those channels cooperatively.

  • Hosted node: the node settles to its own key on KeyWay's servers, and KeyWay sweeps it to the user's address the next time the user uses Fiber.

Fiber's watchtower also needs a little CKB on the node's own key to pay the fee for claiming those funds. So the first channel a user opens also sends a one-time fee reserve of 62 CKB from their account to their node's key, after the channel's funding transaction confirms. The channel-open dialog shows it (FiberChannelOpenPreview.feeReserveCkb). The sweep leaves the reserve in place while any channel is open or still settling, and returns it to the user once every channel is fully closed. The reserve is an ordinary send from the user's account, so it counts toward their daily email-confirmation amount.

The sweep moves CKB and standard tokens. Prefer cooperative closes; use force only when the peer is unreachable.

Errors

Pass any rejection to toKeyWayError() to get a KeyWayError with a code and a retryable flag, instead of parsing messages. estimatePayment(), payInvoice() and closeChannel() on useFiber() already reject with one.

import { toKeyWayError } from "@ckb-keyway/react";

try {
  await node.sendPayment({ invoice });
} catch (cause) {
  const error = toKeyWayError(cause);
  if (error.retryable) scheduleRetry();
  showMessage(error.message);
}
CodeMeaning
UNSUPPORTED_METHODThe method isn't available through KeyWay.
NO_ROUTE, INSUFFICIENT_ROUTE_LIQUIDITYNo route can carry this payment right now.
FEE_LIMIT_EXCEEDEDThe best route costs more than max_fee_amount or max_fee_rate.
INVOICE_EXPIRED, SELF_PAYMENTThe invoice has expired, or belongs to the same user.
PEER_OFFLINE, PAYMENT_TIMEOUTA peer is offline, or the payment didn't settle in time.
PAYMENT_FAILED, CHANNEL_FAILEDAnother payment or channel failure.
DEVICE_IN_USE, CROSS_ORIGIN_ISOLATION_REQUIREDBrowser node only: another browser holds the node, or the page isn't cross-origin isolated.
UNKNOWNAnything else; read error.message.

A declined approval rejects with a message such as "The payment was not approved", and a cancelled email code with "Email confirmation was cancelled".

On this page