# Building Correctly — Your App IS the On-Chain Component

The single most common mistake when building on ChainRender is treating it as *a library a React app calls to
produce a hash*, instead of *the framework the app is built in and rendered from*. This page draws that line sharply,
because getting it wrong produces an app that looks like ChainRender but isn't on-chain at all.

> **Building with an AI agent?** Point it at the **[ChainRender MCP server & app-builder skill](../reference/mcp-and-skill.md)**
> (`@chainrender/mcp` + the `chainrender-app-builder` SKILL.md). The skill teaches the correct
> compose → grade → compile → validate → render workflow and the anti-patterns below, so the agent builds a real
> on-chain app instead of a conventional dashboard. This is the recommended path for AI-assisted builds.

## A complete, copyable example

This is the whole correct loop in one place. The balance is read **and rendered on-chain** by the `Slot` — you never
fetch it yourself. Clone this shape (a full runnable version ships in the app-builder skill as
`examples/correct-react-app.tsx`):

```tsx
import { compileJsx } from "@chainrender/client";
import { renderComponent } from "@chainrender/client/runtime";

const DEPLOYMENT = { rpc: "<your-rpc>", router: "<RenderRouter>", registry: "<ComponentRegistry>" };

// 1. COMPILE — the Slot declares an on-chain read; the interpreter resolves it at render time.
const { hex, hash } = compileJsx(`
  <Box direction="column" variant="surface">
    <Text role="heading">Account Balance</Text>
    <Slot bind="balance" arg="0xd8dA...6045" />
  </Box>`);

// 2. REGISTER — write the bytes on-chain once (wallet tx to the ComponentRegistry with `hex`).
//    After this, the component lives on-chain forever, addressed by `hash`.

// 3. RENDER — one eth_call returns the fully on-chain-rendered HTML, balance included. Display THAT.
const html = await renderComponent(DEPLOYMENT, hash);
document.getElementById("app")!.innerHTML = html;   // ← the on-chain UI. Not hand-built React.
```

The balance in that output was read and rendered **on-chain** during step 3 — there is no `getBalance` call in your
code. That is the difference between a ChainRender app and a viem dashboard.

## Nuance: three things the ethos does NOT mean (avoid over-compliance)

Understanding "the component is the app" is the first step. Over-applying it produces an app that is rigid and barely
functional. Three clarifications:

### A client-side resolver is NOT Grade A — an on-chain resolver contract is
Routing data through a resolver does **not** make it on-chain. There are two kinds of resolver, and they grade
differently:
- **Client-side resolver** (a JS function you write that calls `publicClient.getBlock()` / `getBalance()`, e.g. the
  `makeProviderResolvers` helpers) — this is the **CLIENT_FETCH** path. The data is fetched off-chain in the browser
  and dropped into the slot. It is convenient and legitimate, but it grades **C (Live)**, not A. Writing
  `blockHeight: async () => publicClient.getBlock()` is still an off-chain fetch — moving it into a resolver
  function does not make it trustless.
- **On-chain resolver contract** (a resolver **address** in the Slot, e.g. `<Slot resolver="0xEthBalanceResolver…"
  arg="0x…" />`) — the value is read **and rendered on-chain** by that contract during the `eth_call`. This is the
  **Grade A** path.

So: for real trustlessness, bind a Slot to an **on-chain resolver contract address**, not a client-side JS function.
Client-side resolvers are a pragmatic fallback (some data, like an arbitrary wallet's ENS name, is inherently
client-side) — use them knowingly, and let the grade tell the truth (they land at C).

### Ordinary client-side UI is fine — misrepresenting off-chain data as on-chain is the violation
The ethos does **not** forbid all React state. A unit converter that does arithmetic in the browser, form input
state, tab switching, a wallet-connect button — these are ordinary client concerns and are perfectly fine as normal
React/HTML. You do **not** need to turn a client-side calculator into an on-chain transaction to be "compliant."
The actual violation is narrower: **fetching on-chain data with viem and hand-rendering it as if it were rendered
on-chain**, while claiming the app is fully on-chain. Pure client-side computation isn't on-chain data and never
claimed to be — so it's not a violation. Don't contort ordinary UI into on-chain submissions; just don't dress up
off-chain data as on-chain.

### Rendering requires a registered component — that's by design, not a bug
`renderComponent` / `<ChainRenderView>` render **by hash via an `eth_call`**, so the component must be **registered
on-chain first**. There is no "render from raw source" — that's the whole point (the chain is the renderer). During
authoring you preview with `grade()` / `compileComponent()` and the visual builder; to see the *rendered* component
you register it (once) and then it renders for free forever. So "I have to register before the preview shows
anything" is correct behavior, not a defect — budget one cheap registration per component.

## The one idea to internalize

**A ChainRender app is a component that is compiled to bytes, registered on-chain, and rendered by an `eth_call`.**
The component *is* the app. Your surrounding code (React, plain HTML, whatever) is a thin shell that calls
`renderComponent(...)` and displays the on-chain HTML it returns — it does not re-implement the UI.

If your React code fetches data with viem and hand-renders it in JSX, and ChainRender is only used to print a hash on
the side, **you have not built a ChainRender app.** You have built a conventional Web3 dashboard that imports
ChainRender. The rendering is happening in your browser, not on-chain.

## The correct flow: compile → register → render

```ts
import { compileJsx } from "@chainrender/client";
import { renderComponent } from "@chainrender/client/runtime";

// 1. COMPILE — turn your component into on-chain bytes + a content hash.
const { hex, hash } = compileJsx(`
  <Box direction="column" variant="surface">
    <Text role="heading">Account Balance</Text>
    <Slot bind="balance" arg="0xd8dA...6045" />
  </Box>
`);

// 2. REGISTER — store the bytes on-chain, ONCE. (Uses your wallet + the ComponentRegistry.)
//    After this, the component lives on-chain forever, addressed by `hash`.
//    (See the registry helpers; this is a one-time write, not part of rendering.)

// 3. RENDER — anyone renders it for free with a single eth_call. The HTML is computed ON-CHAIN.
const html = await renderComponent({ rpc, router }, hash);
// `html` is the fully-rendered component, including the live balance the Slot resolved on-chain.
document.getElementById("app").innerHTML = html;
```

Step 3 is the whole point: the balance in that `Slot` is **read and rendered on-chain** during the `eth_call`. You do
**not** fetch the balance yourself and paste it into JSX — the on-chain interpreter does it, and the result is
identical against any RPC, with no server.

## The anti-patterns (what "wrong" looks like)

These are the exact mistakes that turn a ChainRender app into a conventional dashboard. Avoid all of them:

### ❌ Fetching on-chain data with viem and hand-rendering it
```tsx
// WRONG — this is a normal React app, not ChainRender.
const bal = await publicClient.getBalance({ address });   // off-chain fetch
return <div>{formatEther(bal)} ETH</div>;                 // hand-rendered in React
```
```tsx
// RIGHT — declare a Slot; the on-chain interpreter reads + renders the balance.
compileJsx(`<Slot bind="balance" arg="${address}" />`);   // → register → renderComponent
```
The difference: the wrong version renders in your browser (nothing on-chain); the right version renders on-chain and
returns HTML. If you're calling `publicClient.getBalance`/`readContract` and dropping the result into JSX, you've
bypassed ChainRender entirely.

### ❌ Compiling but never registering
`compileJsx` gives you a hash of bytes that **do not exist on-chain yet**. A hash of un-deployed bytes is not an
on-chain app — it's a preview. A real ChainRender app **registers** the bytes (step 2) so `renderComponent` can find
them. If you only ever call `compileJsx` and display the hash, nothing is on-chain.

### ❌ Importing the render component but never rendering with it
Importing `ChainRenderView` / `renderComponent` and then only displaying `compiled.hash` as text — with the render
component absent from your returned JSX — means **nothing renders**. The app becomes a compiler-output viewer with no
live UI and no interactivity. You must actually place `<ChainRenderView deployment={...} hash={...} resolvers={...} />`
in your JSX (and register the hash on-chain), or call `renderComponent` and insert the returned HTML. Importing it is
not using it.

### ❌ Displaying the hash instead of rendering the component
Showing "Content Hash: 0x…" in a sidebar is a nice debug view, but it is not using ChainRender. The app should
**render the component** (step 3) and show *that* — the actual on-chain-rendered UI — as the primary content.

### ❌ Reaching for an off-chain indexer/API
Fetching contract source from a block-explorer API, prices from a REST endpoint, or data from any Web2 service
re-introduces the off-chain trust ChainRender exists to remove. If you need on-chain data, read it with a
[resolver](./resolvers.md) inside a `Slot`, so it's rendered on-chain and grades honestly. If you genuinely need an
off-chain source, it must be declared (`provenance: "external"` / `CLIENT_FETCH`) so the [grade](./trust-grade.md)
discloses it — it cannot silently masquerade as on-chain.

## The litmus test

Ask these about your app. A genuine ChainRender app answers "yes" to all:

1. **Is the UI a compiled ChainRender component** (not hand-written React/HTML that mirrors it)?
2. **Are the component's bytes registered on-chain** (not just compiled to a hash)?
3. **Is the UI produced by `renderComponent` / an `eth_call`** (not assembled in the browser)?
4. **Is on-chain data read via `Slot` + resolver** (not fetched with viem and pasted in)?
5. **Does the [trust grade](./trust-grade.md) reflect reality** (off-chain dependencies declared, not hidden)?

If any answer is "no," you've built a conventional Web3 app that *uses* ChainRender, not a ChainRender app. That's a
valid thing to build — but it is not on-chain, and it must not be presented as such.

## Where React fits (correctly)

React is welcome — as the **shell**, not the renderer. The correct React usage: a component calls `renderComponent`,
gets on-chain HTML, and displays it (see [Using React](../guides/using-react.md) and the
[`@chainrender/client/react`](../reference/sdk-client.md) helpers). React handles routing, wallet connection, and
chrome; ChainRender handles the actual UI, rendered on-chain. React should never re-implement what the component
renders.

## Related
- **[The Render Pipeline](./render-pipeline.md)** — the compile → register → render path in detail.
- **[Render a Component](../guides/render-component.md)** — the one-call render, hands-on.
- **[Resolvers](./resolvers.md)** — read on-chain data into a `Slot` (instead of fetching with viem).
- **[Trust Grade](./trust-grade.md)** — why off-chain dependencies must be disclosed, not hidden.
- **[Overview & Mental Model](./overview.md)** — the foundation.
