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.
customerNode not startedsolverNode not startedinvoiceNonepaymentNot sentBehind the scenes
See how the result rules, Hold Invoice, and payment decision map to the Fiber SDK calls used by the demo.
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.
The 1 CKB Hold Invoice payment runs on Fiber Testnet. The 300 CKB allocation is a simulated work result; no 300 CKB transfer occurs.
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–10Keep 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.
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.
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.
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.
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.
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 validates the Invoice and preimage. Your application defines the rules, authenticates the submitted result, and decides when the preimage may be released.
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.
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:
npm install
npm run devThen 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}