⚠️ Alpha · testnets only. ChainRender is in active alpha and deployed on testnets only — Ethereum Sepolia, the Robinhood Chain Testnet, and the Circle Arc Testnet — not on any mainnet. Contracts, addresses, and APIs may change. Do not use with real funds or production assets.
Resources

Gotchas, Do's & Don'ts

The practical wisdom for building ChainRender apps well. Read this before you spend gas — most of these save you from a redeploy, a dropped grade, or a confusing render.

The big ones (read these first)

✅ DO grade and preview locally before you deploy

The SDK's grade() equals what the chain computes (drift-locked). Grade, preview, and validate locally, for free, before registering anything. Deploying to "see if it works" wastes gas — you can see everything first. → Build, Grade & Style Before Deploy

❌ DON'T redeploy to change how it looks

Colors, spacing, fonts, and chart dimensions are render-time parameters. Changing them is free and needs no redeploy — pass a presentation context. A redeploy is only for changing what the app is (text, structure, actions), not how it looks. If you're about to redeploy for a cosmetic tweak, stop. → Theme Without Redeploying

✅ DO understand what's baked vs. what's render-time

  • Baked into the hash (change = new version): text/content, structure, roles, declared actions, resolver targets. These are the app's identity.
  • Render-time (change = free): theme, palette, chart dimensions, spacing/fonts. These are presentation.

Getting this wrong is the #1 source of "why did my hash change?" confusion. A one-character text change is a new app; a color change is not.

❌ DON'T expect a resolver to return HTML

A resolver returns a plain string; the interpreter escapes it. Don't try to return markup — it will be escaped, not rendered. This is a safety feature (a resolver can never inject markup), not a limitation to work around.

Trust & grade gotchas

  • ✅ DO watch your grade as you add features. Adding an off-chain image or a CLIENT_FETCH drops you from A to C. The breakdown tells you exactly what did it. Decide the trade-off before users see it.
  • ❌ DON'T embed off-chain assets if you want Grade A. An external image = a live dependency = Grade C. Use the on-chain media resolvers (generative art, NFT media, SDF scenes) to stay A. → Visual & Media
  • ✅ DO declare exactly the capabilities you use. Over-declaring (claiming a power you don't use) is refused by the enforcer (rail R2 — dishonest envelope). Under-declaring (reaching a power you didn't declare) is also refused. Declare precisely what you reach.
  • ❌ DON'T try to hide a value transfer in an action. Moving ETH requires an explicit VALUE declaration. An action that moves value without it is refused (R4) — you can't sneak it in.

Action & signing gotchas

  • ✅ DO rely on construct-for-sign. A button's calldata is built from its declaration — it can only be the declared selector + args. Trust this; don't hand-build calldata.
  • ❌ DON'T put arbitrary calldata in a button. There's no path for it — actions are declared, not raw. A {to, selector} not in the declared call set is refused (R5).

Rendering & data gotchas

  • ✅ DO design for fail-closed. A resolver that reverts falls back to "unavailable"; a chart with no data shows an axes-only frame. Design your UI so the fallback still reads sensibly — don't assume the data is always there.
  • ❌ DON'T expect to enumerate "everything" in one call. Reading a specific value or a bounded range is a direct on-chain call; listing all of something at scale needs an indexer. Plan for that boundary. → Live Data & Events
  • ✅ DO paginate large outputs. Big tables/atlases support chunked reads that concatenate byte-identically. Use them rather than trying to render everything in one call.
  • ❌ DON'T use a font size below 12px. The legibility floor rejects it. (And it's too small to read anyway.)

Theming gotchas

  • ✅ DO reference tokens, not hardcoded values. var(--cr-space-md), not 12px. Token-driven styles re-flow when the theme changes; hardcoded ones don't.
  • ❌ DON'T ship a low-contrast theme. The contrast invariant (WCAG AA) rejects it at build time. Fix the token pair, don't bypass the check. → Themes in Depth

Deployment & naming gotchas

  • ✅ DO verify after deploy. Render your deployed hash against multiple RPCs and diff — byte-identical confirms it's live and honest. → Verifying an App
  • ❌ DON'T treat the stake as a fee. The subname/submission stake is a refundable anti-spam deposit — you get it back after the lock window. It's not a payment. → Submitting + Staking
  • ❌ DON'T look for your submission by your EOA if you use a smart wallet. The registrar records the submitter as msg.sender. If you submit through a smart-contract wallet / account abstraction (ERC-4337/7579), that's your smart-account address, not the EOA you signed with — so searching the Registered event logs filtered by your EOA returns nothing, even though the submit succeeded. Classic "I submitted but can't find it under my account." ✅ DO look it up by the label (the registrar enumerates submissions by label, account-agnostic), or filter by your smart-account address. The catalog's Verified only filter is immune because it enumerates by label; a future "my apps" view must match the smart account, not the EOA.
  • ✅ DO use versioning for updates. Don't try to "change" a deployed app — publish a new version (timelocked, observable). The old version stays resolvable forever. → Update a Deployed App
  • ❌ DON'T assume your RPC is private. The rendered output is trustless, but the RPC sees which app/address you query. Use a private/light-client RPC if metadata privacy matters. → No Hosting

The meta-principle

When something surprises you, the answer is almost always one of these two:

  1. "Is this identity or presentation?" — if the hash changed unexpectedly, you changed identity; if a visual change didn't stick, you changed something that needs a render-time context or a re-seed.
  2. "Does this preserve fail-closed + the grade?" — if a feature dropped your grade or broke a render, check what dependency it introduced.

Related

Rich-media & on-chain library gotchas

three.js must be the r160 UMD build (global THREE), not ESM

On-chain three has to be the UMD build (three.min.js, r160) that sets window.THREE. Modern three (r161+) is ESM-only with no global -- loaded as a plain script it produces THREE is not defined, and loaded via an importmap+blob it's blocked in a strict-CSP sandboxed iframe. Sketches use the global THREE, never import.

No CDN in the deploy/render path

External scripts (p5/three/gzip from a CDN) are blocked in the sandboxed iframe under a strict CSP. Everything loads on-chain, as plain scripts, decompressed with the browser's native DecompressionStream. If you see a "CDN failed / CSP blocking" message, the app was likely deployed before the on-chain-library fix (a zero libHash) -- re-deploy it to capture the on-chain library.

Discovery is newest-wins

If you re-upload a library (e.g. swapping ESM three for UMD three), discovery picks the newest matching entry by name -- so the new upload supersedes the old. Confirm on-chain (enumerate the DependencyRegistry) before re-deploying apps that should use the new one.