Fiber LogoFiber Docs
Interactive tutorial · About 25 minutes

Pay for a Verified Result

Place a 1 CKB Testnet payment on hold, verify a sample 300 CKB route allocation, and release or cancel payment from the result.

Customer AReady to start
Solver CReady to start
InvoiceNone
Result
1
Start Customer A and Solver CStarts two local browser nodes and connects both to Bottle.
4
Prepare Solver C to receiveMove 5 CKB to Bottle's side so Solver C has inbound liquidity.
Runtime events and results
customerNode not started
solverNode not started
invoiceNone
paymentNot sent
Node, channel, result, and payment events will appear here.
Implementation walkthrough

Behind the scenes

See how the result rules, Hold Invoice, and payment decision map to the Fiber SDK calls used by the demo.

How it works

Pay for a result your application can verify

A customer needs exactly 300 CKB allocated across three routes. The payment is placed on hold before Solver C submits an answer. Deterministic code checks the answer, then the application either releases the payment or cancels it.

What happens in the demo?

The 1 CKB Hold Invoice payment runs on Fiber Testnet. The 300 CKB allocation is a simulated work result; no 300 CKB transfer occurs.

1 Define the acceptance rules

Make the result unambiguous

The result must contain the three required route amounts. Route A can receive at most 120 CKB, Route B at most 100 CKB, and Route C at most 80 CKB. Together they must equal 300 CKB.

These constraints matter more than the kind of software or person producing the result. Any solver can participate as long as it returns the agreed structure.

lib/job.ts · lines 1–10
2 Create the payment lock

Keep the release key outside the Invoice

The application generates a random preimage K and puts only SHA256(K) in the Hold Invoice. Solver C can see that a 1 CKB payment is waiting, but cannot claim it without the key.

lib/verifier.ts · lines 3–11
3 Create the Hold Invoice

Encode the payment condition

createResultInvoice() requests 1 CKB and includes the payment hash, sha256 algorithm, one-hour expiry, and trampoline-routing support. Because the preimage stays in the application, the Invoice can reach Received but cannot settle automatically.

lib/job-invoice.ts · lines 1–10
4 Route the held payment

Commit the payment before accepting work

Customer A submits the Invoice with a private Bottle → Solver C last-hop hint. This lets the browser nodes use the newly opened receiver channel without waiting for public gossip, while max_parts: '0x1' keeps the example to one payment part.

lib/job-invoice.ts · lines 13–20
5 Track the held state

Keep payment and result states separate

The receiver Invoice becomes Received while Customer A's payment remains Inflight. That combination proves the payment is committed; it does not say whether the result is valid. The application therefore tracks the result independently and unlocks a decision only after the Invoice reaches Received.

app/page.tsx · lines 4–15
6 Verify the result

Keep work delivery separate from payment transport

Solver C returns the route values through the application; Fiber carries only the payment. Customer A runs verifyResult() against the selected values, and the interface groups those checks into result format, route limits, and required total. Result #1 and Result #2 are hard-coded samples rather than output from a live solver.

lib/job.ts · lines 12–25
7 Release or cancel payment

Map the verdict to one payment action

The handler first requires the Invoice to be Received. When every check passes, it reveals K and releases 1 CKB to Solver C; when any check fails, it cancels the Invoice and makes the held 1 CKB available to Customer A again. The final Outcome step is a read-only receipt.

Fiber does not judge the result

Fiber validates the Invoice and preimage. Your application defines the rules, authenticates the submitted result, and decides when the preimage may be released.

app/page.tsx · lines 17–27
More ways to use it

Where verified-result payments fit

This pattern works when acceptance can be decided from explicit evidence:

  • Computation: release payment when an output matches a reproducible calculation or proof.
  • Automated quality checks: pay after a build, test suite, or data-validation job passes.
  • Milestone delivery: release a tranche after signed artifacts and required checks arrive.
  • Oracle-backed outcomes: pay when an authenticated data source confirms the agreed condition.

It is a poor fit for work that is mainly subjective unless the parties also define a reviewer, dispute process, and timeout policy.

Optional local setup

Run the complete project locally

Select Download project in the top-right corner, or download here. The archive already contains the Next.js application, Fiber integration, browser headers, and interface shown here. After extracting it, open the project directory and run:

Terminal
npm install
npm run dev

Then open http://localhost:3000 in your browser.

1export type AllocationResult = {2  routeA: number;3  routeB: number;4  routeC: number;5};6 7export const job = {8  amount: 300,9  limits: { routeA: 120, routeB: 100, routeC: 80 },10};11 12export const verificationSamples = [13  { id: 'within-limits', result: { routeA: 120, routeB: 100, routeC: 80 } },14  { id: 'over-route-a', result: { routeA: 150, routeB: 100, routeC: 50 } },15];16 17export function verifyResult(result: AllocationResult) {18  return [19    { label: 'Exact result shape', passed: Object.keys(result).sort().join(',') === 'routeA,routeB,routeC' },20    { label: 'Route A within 120', passed: result.routeA <= job.limits.routeA },21    { label: 'Route B within 100', passed: result.routeB <= job.limits.routeB },22    { label: 'Route C within 80', passed: result.routeC <= job.limits.routeC },23    { label: 'Total equals 300', passed: result.routeA + result.routeB + result.routeC === job.amount },24  ];25}
lib/job.ts typescript