⚠️ 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.
Concepts

Content Addressing

Content addressing is the foundation everything else in ChainRender rests on. Understand this one idea and the rest of the system follows naturally.

The core idea

A ChainRender app is its hash. When you compile a component, you get a keccak256 hash of its exact bytes. That hash is the app's permanent, global identity. To render it, you call renderHash(hash) — a view function that deterministically produces the same output every time, forever.

compile(component)  →  bytes  →  keccak256(bytes)  =  the app's identity
renderHash(hash)    →  always the same HTML

There is no "current version" pointer that someone can change, no server that can serve different content, no mutable state in the render path. The hash and the output are bound together immutably.

Why this matters: the anti-spoofing guarantee

Consider a claim button that says "Claim 1 ETH" and calls claim(uint256) with 1000000000000000000. In a normal web app, the server could serve you a different button — same label, but calling claim with a larger amount, or calling a different contract entirely. You'd have no way to know.

With content addressing, that button's bytes — its label, its target, its selector, its arguments — are all part of the hash. Change any of them and the hash changes. A viewer that wants to show you a malicious variant cannot do so under the same hash; it would be a different app with a different identity. If you (or your wallet) pin to a known-good hash, you are guaranteed the exact app you expect.

This is the same guarantee that content addressing gives IPFS or Git — but applied to a live, interactive, transaction-signing UI, enforced on-chain.

Why it's built this way (the design reasoning)

The alternative — a mutable app that an owner can update in place — was deliberately rejected for the base layer, because a mutable pointer is precisely the trust surface content addressing exists to eliminate. If "app.eth" could be repointed at any bytes instantly, whoever controls that name could swap the app under its users. That's the problem, not the solution.

So ChainRender makes immutability the default and the floor, and layers controlled, observable mutability on top only where it's safe:

  • Presentation (how it looks) is a render-time parameter — changeable at zero gas without changing the hash, because appearance doesn't affect what the app is or does. See Theming.
  • Versioning (evolving what the app is) is handled by an explicit, timelocked, append-only version registry — so an update is a public, delayed, immutable event, never a silent swap. See Versioning.

The rule that ties it together: how an app looks is free and mutable; what it is, says, and does is baked into its hash. That division is what lets ChainRender be both immutable (trustworthy) and evolvable (usable).

The practical consequences for you

  • Deploying is registering bytes. You compile locally, get a hash, register the bytes once (paying gas once), and render for free forever after.
  • The same app renders identically on any chain. Because rendering is deterministic and content-addressed, an app deployed to multiple chains has the same hash and same output everywhere.
  • You can verify any app. Given a hash, recompile the source and check it matches; render against any RPC and check the output matches. Nothing is hidden.

Related