> Documentation index: https://docs.squeekchain.com/llms.txt. Use it to find every page.

# DotGrid renderer

> The DotGrid renderer and page validator function by function — how Squeek images and metadata are rendered onchain, the metadata fields, how to verify the image identities, errors and limits, and the published ABIs.

Web version: https://docs.squeekchain.com/developers/dotgrid/

## TL;DR

- The DotGrid renderer turns stored visual data, the collection seed and a token ID into SVG and JSON metadata; SqueekNFT `tokenURI` calls it.
- Rendering is deterministic and read-only: ownership, level and rewards never change an image.
- Verify the renderer, validator and page identities before trusting a rendering.

## Overview

!!! warning "In active development"

    The Squeekchain contracts are in active development and are likely to change. Functions, events, errors, limits and behavior described here may change before a mainnet release, and the Sepolia deployment may be upgraded or replaced. Pin the ABI you integrate against, and re-verify the deployment before relying on it.

The renderer reads a DotGrid page, a 44-byte header and ten ordered sections stored as contract code in a page root and its chunks, and derives each token's traits and image from the collection seed and token ID. The page validator checks a page's full structure. Neither holds state or emits events.

| Network | Contract | Address | Kind |
|---|---|---|---|
| Sepolia | Renderer | `0x2dC7661D172fEf0FC900E0aD0f4ffCE68E8bfD75` | Immutable |
| Sepolia | Page validator | `0xA674943387967cEBEf3C2110026A56F0A78d7F24` | Immutable; created by the renderer |
| Sepolia | Squeeks page root | `0x8dd828dab7471589b0123a99747408bc04f81567` | Immutable; SqueekNFT `art()` |

## Functions

### Views

#### `tokenURI`

```solidity
function tokenURI(address page, bytes32 seed, uint256 tokenId) external view returns (string);
```

Strictly reads and fully validates `page`, then returns base64 JSON metadata with an embedded SVG for `tokenId`.

| Name | Type | Unit | Meaning |
|---|---|---|---|
| `page` | `address` | — | Page root address |
| `seed` | `bytes32` | — | Nonzero collection seed; SqueekNFT `collectionSeed()` |
| `tokenId` | `uint256` | token ID | `1..2^192−1`; the renderer does not check that the NFT exists |

Reverts with `DotGridInvalid(code)` for any page or input failure.

#### `tokenURIForPinnedPage`

```solidity
function tokenURIForPinnedPage(address page, bytes32 codehash, bytes32 seed, uint256 tokenId) external view returns (string);
```

Like `tokenURI`, but skips full validation when the page root's runtime hash equals `codehash`. SqueekNFT uses it after validating the page at construction. Supplying a codehash does not establish trust: validate that exact root first. Reverts with `DotGridInvalid(132)` on a mismatch.

#### `renderSVG`

```solidity
function renderSVG(address page, bytes32 seed, uint256 tokenId) external view returns (string);
function renderSVGForPinnedPage(address page, bytes32 codehash, bytes32 seed, uint256 tokenId) external view returns (string);
```

The SVG alone, strict or pinned.

#### `traitWord`

```solidity
function traitWord(address page, bytes32 seed, uint256 tokenId) external view returns (uint256);
function resolvedFieldHash(address page, bytes32 seed, uint256 tokenId) external view returns (bytes32);
```

The packed deterministic trait choices, and a commitment to the resolved emitter field. Both also appear in the metadata.

#### `validatePage`

```solidity
function validatePage(address page) external view returns (bytes32 pageHash, bytes32 assetSetHash);
```

Strict read plus full validation. Returns the logical page hash and the asset-set hash, which SqueekNFT stores as `sceneHash()` and `assetSetHash()`.

#### Identity

```solidity
function rendererId() external pure returns (bytes32);
function rendererVersion() external pure returns (string);
function pageValidator() external view returns (address);
```

`rendererId()` is `keccak256("sqk.dotgrid-renderer.v1")` and `rendererVersion()` is `sqk.dotgrid-renderer.v1`. `pageValidator()` is the validator the renderer created; its code is not covered by the renderer's runtime hash, so pin it separately.

#### `validate`

```solidity
function validate(bytes page) external pure returns (Layout layout);
```

On the page validator. Validates raw logical page bytes and returns the section directory. Most integrators call `validatePage` on the renderer instead.

### Metadata fields

| Field | Meaning |
|---|---|
| `name`, `description` | Page-defined text with a token-specific title |
| `image` | `data:image/svg+xml;base64,` embedded SVG |
| `attributes` | Deterministically selected trait names and values |
| `properties.renderer`, `rendererId` | Renderer version and identity |
| `properties.pageHash`, `assetSetHash` | Logical page and renderer-bound asset identity |
| `properties.traitWord`, `resolvedFieldHash` | Derivation and resolved-field evidence |
| `properties.emitters` | Active emitter count in the resolved result |

The asset-set hash binds page and renderer: `keccak256(ASSET_SET_DOMAIN || RENDERER_ID || pageHash)`, with domain `keccak256("sqk.dotgrid-asset-set.v1")`. It differs from the root codehash and the page hash.

## Events

The renderer and validator emit no events. SqueekNFT's `BatchMetadataUpdate` announces when readers should refresh metadata.

## Errors

| Error | Cause | Fix |
|---|---|---|
| `DotGridInvalid(code)` | A page, input or budget check failed; `code` identifies which | See the code families below |

| Code | Meaning |
|---|---|
| 1–5 | Page size, magic, format, header or directory |
| 10–14 | Strings, categories, options or section trailing bytes |
| 20–26 | Derivation counts, seeds, tags, weights or trait capacity |
| 30–31 | Palettes |
| 40–44 | Glyph count, flags, path length or characters, trailing bytes |
| 50–53 | Optics, passes or opacity |
| 60–64 | Emitter field records, positions, reserved bits or glyph references |
| 70–73 | Arrays, bit width or padding |
| 80–86 | Sets, kinds, padding, ordinals or duplicates |
| 90–97 | Lookup tables, tables, update modes or arguments, trailing bytes |
| 100–107 | Program order, length, opcodes, references, arguments, key terms or ranges |
| 110–112 | Resolve-cost, SVG or URI budget |
| 120–121 | Segment structure or creation; chunk hash mismatch |
| 130 | Token ID outside `1..2^192−1` |
| 131 | Zero collection seed |
| 132 | Pinned root codehash mismatch |

Not every integer in a range is assigned. Token input checks run before any page read.

## Integrator patterns

- **Render through SqueekNFT.** Call `SqueekNFT.tokenURI(id)`: it checks the NFT exists and uses the pinned page. The renderer alone renders any ID, which neither mints it nor proves ownership.
- **Verify identities once, then cache.** Rendering is deterministic for a given page, seed and ID; re-verify only after `BatchMetadataUpdate`.
- **Compare keccak256, not file digests.** Code and page hashes are keccak256; do not compare them with SHA-256 digests or a hosted raster image.
- **Expect RPC limits.** Rendering can exceed an RPC's `eth_call` gas or response limits even within DotGrid's own resolve-cost budget.
- **Treat output as untrusted.** Display the SVG as an isolated image, never as inline markup.

## Verifying identities

Read every value at one Ethereum block and compare it with the deployment record. A matching hash proves the bytes only if you trust the record.

1. Read SqueekNFT `renderer()`, `art()`, `artCodehash()`, `sceneHash()`, `assetSetHash()` and `collectionSeed()`. `art` is the page root address.
2. Compare `keccak256` of the renderer's runtime code with the expected hash, and `rendererId()` with `keccak256("sqk.dotgrid-renderer.v1")`.
3. Read the renderer's `pageValidator()` and compare its runtime hash separately.
4. Compare the root's runtime hash with `artCodehash()`; the root commits to its chunk hashes.
5. Call the renderer's `validatePage(art)` and require it to return `sceneHash()` and `assetSetHash()`.
6. Require a nonzero seed, then render an existing ID through SqueekNFT.

| Identity | What it proves |
|---|---|
| Renderer runtime hash | Exact rendering code |
| Validator runtime hash | Exact page-validation code |
| Root codehash | Root code, including its chunk hashes |
| Page hash (`sceneHash`) | Logical page bytes |
| Asset-set hash | Renderer identity and page together |
| Collection seed and token ID | The inputs that reproduce the same traits and image |

## Limits

| Resource | Maximum or requirement |
|---|---|
| Token ID | `1..2^192−1` |
| Seed | Nonzero `bytes32` |
| Page format | Magic `0x44475047`, format 1 |
| Logical page / chunks | 49,152 bytes / 3 chunks |
| Runtime | 24,576 bytes per contract |
| Canvas / coordinates | 4,096 pixels / 64 units per pixel |
| Emitters | 2,048 |
| Trait categories / options | 16 / 64 |
| Palettes / glyphs / glyph path | 64 / 64 / 1,024 bytes |
| Optics / passes | 16 / 4 |
| Arrays / sets / lookup tables / tables | 8 / 64 / 64 / 1,024 |
| Resolve cost | 6,000,000 under the format's cost accounting |
| SVG / JSON / URI output | 131,072 / 196,608 / 262,144 bytes |

The Squeeks page holds 25,800 logical bytes in one chunk.

## Usage example

Check the renderer, validator and page identities that SqueekNFT pinned.

```ts
// verify-art.ts
import { getAddress, keccak256, parseAbi, toBytes } from "viem";
import { blockNumber, client, required } from "./setup";

const nft = getAddress(required("NFT_ADDRESS"));
const nftAbi = parseAbi([
  "function renderer() view returns (address)",
  "function art() view returns (address)",
  "function artCodehash() view returns (bytes32)",
  "function sceneHash() view returns (bytes32)",
  "function assetSetHash() view returns (bytes32)",
  "function collectionSeed() view returns (bytes32)",
]);
const rendererAbi = parseAbi([
  "function rendererId() pure returns (bytes32)",
  "function pageValidator() view returns (address)",
  "function validatePage(address page) view returns (bytes32 pageHash, bytes32 assetSetHash)",
]);
const read = { address: nft, abi: nftAbi, blockNumber } as const;
const [renderer, art, artCodehash, sceneHash, assetSetHash, seed] = await Promise.all([
  client.readContract({ ...read, functionName: "renderer" }),
  client.readContract({ ...read, functionName: "art" }),
  client.readContract({ ...read, functionName: "artCodehash" }),
  client.readContract({ ...read, functionName: "sceneHash" }),
  client.readContract({ ...read, functionName: "assetSetHash" }),
  client.readContract({ ...read, functionName: "collectionSeed" }),
]);
const rendered = { address: renderer, abi: rendererAbi, blockNumber } as const;
const [rendererId, validator, [pageHash, assetSet]] = await Promise.all([
  client.readContract({ ...rendered, functionName: "rendererId" }),
  client.readContract({ ...rendered, functionName: "pageValidator" }),
  client.readContract({ ...rendered, functionName: "validatePage", args: [art] }),
]);
const codeHash = async (address: `0x${string}`) => keccak256((await client.getCode({ address, blockNumber })) ?? "0x");
const checks = {
  rendererId: rendererId === keccak256(toBytes("sqk.dotgrid-renderer.v1")),
  rootCode: (await codeHash(art)) === artCodehash,
  pageHash: pageHash === sceneHash,
  assetSet: assetSet === assetSetHash,
  seed: BigInt(seed) !== 0n,
};
if (Object.values(checks).includes(false)) throw new Error(`Art identity mismatch: ${JSON.stringify(checks)}`);
console.log({ renderer, rendererCode: await codeHash(renderer), validator, validatorCode: await codeHash(validator), art, pageHash });
```

Compare the printed renderer and validator code hashes with the values in your deployment record. To decode a Squeek's metadata, see the [SqueekNFT usage example](https://docs.squeekchain.com/developers/nft.md#usage-example).

## ABI

[`DotGridRenderer.json`](https://docs.squeekchain.com/assets/abi/DotGridRenderer.json) and [`DotGridPageValidator.json`](https://docs.squeekchain.com/assets/abi/DotGridPageValidator.json).

## Related pages

- [SqueekNFT](https://docs.squeekchain.com/developers/nft.md)
- [Getting a Miner](https://docs.squeekchain.com/miners/minting.md)
