Tour: Your First Grade-A App — Settlement Desk
You'll build a Settlement Desk: a card that reads a settlement agreement's live on-chain state (status, pooled principal, percent repaid) and shows its self-declared trust facets. Every value is an on-chain read, so the whole app is Grade A. Along the way you'll hit — and fix — the one import/compile pitfall every builder user meets once.
Time: ~10 minutes to a rendering preview; add the deploy/seed steps to see live data.
1. Import the app
Open the Visual Builder (/builder/index.html) → ⋯ More → Import JSON → paste this file.
It's a bare node tree — exactly what Import accepts and what Export JSON produces.
{
"type": "box",
"direction": "column",
"children": [
{ "type": "text", "content": "Settlement Desk", "role": "heading" },
{ "type": "text", "content": "A fully on-chain settlement agreement, rendered straight from chain state." },
{ "type": "text", "content": "Live settlement state", "role": "heading" },
{
"type": "box",
"direction": "row",
"children": [
{ "type": "slot", "bind": "settle-status", "argValues": { "agreement": "0x1111111111111111111111111111111111111111", "assetDecimals": "6", "fracDigits": "2" } },
{ "type": "slot", "bind": "settle-principal", "argValues": { "agreement": "0x1111111111111111111111111111111111111111", "assetDecimals": "6", "fracDigits": "2" } },
{ "type": "slot", "bind": "settle-repaid", "argValues": { "agreement": "0x1111111111111111111111111111111111111111", "assetDecimals": "6", "fracDigits": "2" } }
]
},
{ "type": "text", "content": "Trust, declared on-chain", "role": "heading" },
{ "type": "slot", "bind": "trust-facets", "argValues": { "subject": "0x1111111111111111111111111111111111111111" } }
]
}
The canvas renders the layout immediately. The 0x1111…1111 addresses are placeholders —
you'll replace them with your deployed agreement in step 5. Until then, the slots read nothing,
which is exactly what step 4 is about.
2. A heading is a text node with a role — not type: "heading"
This is the gotcha. It is tempting to write a heading as { "type": "heading", … }, and the
canvas will even draw it — so it looks fine. But when you preview on-chain or compile,
you get:
On-chain preview failed: u8 out of range: undefined.
That error is not about your RPC or interpreter, even though the builder suggests checking
them. It is thrown client-side, during compile, before any eth_call. The compiler turns
each node's type into a one-byte tag, and the set of real node types is:
text · box · button · input · icon · link · slot · image · ref
heading is not one of them. The canvas renderer tolerates type: "heading" for display,
but the compiler doesn't recognize it, so the tag comes back undefined and the byte writer
throws u8 out of range: undefined. The renderer and the compiler disagreed, and the compiler
is the one that matters for anything that touches the chain.
The fix (already applied in the JSON above): a heading is a text node carrying the ARIA
heading role.
{ "type": "heading", "content": "Settlement Desk" } // ✗ compiles to u8 out of range
{ "type": "text", "content": "Settlement Desk", "role": "heading" } // ✓ a real, compilable heading
Rule of thumb: if a value isn't one of the nine node types, it belongs in an attribute
(role, variant, provenance, direction), not in type. See
Gotchas, Do's & Don'ts.
3. Make sure the builder knows the resolver kinds
settle-* and trust-facets must appear in the slot's resolver kind picker. If they don't,
your builder's embedded vocabulary is an older snapshot. Regenerate it from the one source of
truth and rebuild the site:
cd /h/chainrender_deploy/chainrender-project
npx tsx scripts/sync-builder-vocab.mjs # VOCAB.resolverKinds <- chainc/resolverkinds.ts (edits only that array)
# then your normal site build regenerates builder/index.html from builder/index-next.html
Confirm the kinds are in the source of truth first:
npx tsx -e "import('./chainc/resolverkinds.js').then(m=>console.log(m.RESOLVER_KINDS.map(k=>k.kind).join(' ')))" \
| tr ' ' '\n' | grep -E 'settle-|trust-facets'
4. Preview, and read the fail-closed state
Hit Compile / Preview. With the fix from step 2, compile succeeds and the builder calls the
interpreter's render(bytes) via eth_call, using the Interpreter address + RPC in the
config bar (the gear). Two honest outcomes:
- Interpreter/RPC not set → now you get a real interpreter/RPC error (this time the builder's hint is right). Set a valid interpreter for your chain and an RPC URL, then retry.
- Set, but placeholder addresses → the layout, headings, and the grade badge render, and each slot shows "unavailable." That is the design: a Grade-A read fails closed rather than inventing a value. Nothing is wrong — you simply haven't pointed it at a real agreement yet.
Why "unavailable" and not an error?
settle-statusis a view read. With no real agreement at the address, there's nothing to read, so the honest render is "unavailable." See The Floor.
5. Point it at a real agreement (deploy + seed)
To see live numbers, deploy the stateless resolvers once per chain and a real agreement to read. These contracts are ownerless and immutable — additive, no migration. Never put your deployer key in shell history:
read -rs DEPLOYER_PK ; export DEPLOYER_PK # hidden input; or: npx hardhat vars set DEPLOYER_PK
echo
# deploy SettlementCardResolver + TrustDisclosureResolver to your target chain (see the deploy guide
# for the exact script/flags for your setup), then deploy/seed an AttestedLoanChannel (or any
# ISettlementView) so there is real state to read.
Then, in the builder, replace 0x1111…1111 in all four slots with your deployed agreement
address, set assetDecimals to the escrow asset (6 = USDC, 18 = native), and preview again. The
status word, principal, and repaid-percent now render from chain, and trust-facets shows the
agreement's declared custody / exit / relayer-binding.
See Deploying an App and Reading On-Chain for the per-chain specifics.
6. Confirm the grade
Check the grade badge: it should read Grade A — every slot is a READ_CHAIN resolver with
an on-chain data edge. If you add an off-chain bind later (an event feed, a client fetch), the
badge drops to ≤ C by construction and the honest label appears. That guard is the point:
the badge can never over-claim.
You've now done the full loop — import → fix → preview → deploy → live → grade. The next two tours reuse this spine.
Next: Proof & Compute →