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

Guide: The Viewer's Own Wallet and Transfer Histories

Goal: show the person viewing your app their own balance, token balance, ENS name or identicon, and a list of recent transfers, without making the app look different to different wallets.

A resolver's arguments are sealed into your app's content hash when you register it. Your users' addresses are not known then, so an ordinary slot cannot read "the connected wallet". ChainRender has two kinds of slot for this. A ChainRender viewer fills both of them in the browser, above the on-chain floor (⚙).

A read of the viewer's wallet

Write "$viewer" as the account argument, and leave out resolver and arg:

{ "type": "slot", "bind": "erc20balance", "argValues": { "token": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", "account": "$viewer" } }
{ "type": "image", "bind": "gidenticon", "argValues": { "account": "$viewer" }, "alt": "Your identicon" }
Kind Node Args
balance, balanceWei slot account
erc20balance, erc20balanceRaw slot token, account
ensName slot who
identicon, gidenticon image account

No other kind can read the viewer's address (there is no viewer portfolio total).

What happens:

  1. The builder compiles the slot as a client slot whose arg is the text cr:viewer;token=0x…;account=$viewer (viewerReadArg). The on-chain render shows its fallback text, "Connect your wallet to see this", to everyone. That text is the floor.
  2. In the viewer, a bar says what the app can show and that the person's address goes to the page's RPC endpoints to read it (at least two of them where the chain lists two, because the reads are cross-checked). Nothing is read until the person chooses Show my wallet's data. The choice is remembered for that app on that device until they choose Stop showing my data.
  3. The viewer then calls this chain's published resolver for the kind (from chains.json) with their address, and puts the result in the slot as text, or as a validated image. An address the app supplies is never used as the resolver. ensName is filled only where the chain's EnsRecordResolver reads the ENS registry.

The address is never written into the app, the link or the provenance export. It can never decide what the app shows or does: "$viewer" in an action, a signature or a condition is a review error.

A transfer history

{ "type": "slot", "bind": "transfers",
  "argValues": { "kind": "token-transfers", "account": "$viewer", "token": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", "blocks": "5000", "limit": "20" } }
  • kind: token-transfers, erc20-transfers, nft-transfers or native-transfers.
  • account and token: account is "$viewer" or an address; token is an address. Give at least one of the two. A chain-wide history is mostly other people's spam.
  • blocks: 1..45000 (default 5000).
  • limit: 1..50 (default 20).

The viewer reads the chain's logs with paged eth_getLogs and states the block window it actually read. Say next to the table that it covers recent blocks, not all time.

The logs are read the way every value the viewer shows is read (docs/179 §14.11): each page must come back the same from two of the chain's endpoints, at one block both already have. The caption names the source, for example "Source: 2 independent endpoints agreed at block N", or "one public endpoint is known for this chain: not cross-checked". If the endpoints disagree, no row is shown: the slot keeps its fallback and says why. The builder's preview reads its one configured RPC and says so.

A history is where an attacker can write straight into your page: anyone can emit Transfer logs to an address. So the viewer:

  • Hides zero-value and dust transfers (an incoming amount below 0.0001 of a token) and counts them. A "Show them" button reveals them, flagged.
  • Flags look-alike addresses: a counterparty with the same first four and last four hex digits as another address in view.
  • Marks tokens your app did not name as unverified, with their contract address.
  • Shows every address in full, never shortened.

Grade

Both are ⚙ above the floor. grade() lists viewer-read and transfer-history in aboveFloor.kinds, and the review reports the app as ⚙ over a floor that grades A, B or C. Report it as ⚙, never as the floor's letter.

Previewing in the builder

Preview on-chain floor and Render deployed fill these slots the way the viewer will. If you have connected a wallet in the builder, they use it. Otherwise they use a sample address, and every filled slot carries a sample address tag. The design-time preview marks them ⚙.

See Wallet Connection & Signing for actions, and Live Data & Events for fixed-address reads.