# Metadata

ABX stores a token's metadata as a set of fields on-chain. Each field maps to one representation and a
value. A field can be stored fully on-chain, located off-chain, or committed on-chain by a hash. The model is
deterministic: for a field it can resolve, any resolver reading the same chain produces the same value.

## Fields, representations, and values

Metadata is a key-value store at two scopes:

- **Token scope**: `tokenId -> field -> { representation, value }`.
- **Collection scope**: `field -> { representation, value }`, contract-wide. This is what ERC-7572 `contractURI` reads.

A `field` is the JSON key it produces, such as `image`, `description`, or `attributes`. A
`representation` says how the value is carried. A field has exactly one active representation; setting
it again replaces the previous one. Field resolution reads token scope first and falls back to
collection scope.

The contract stores the representation as an opaque value and does not act on it. The representation
vocabulary is a protocol convention, defined in the SDK rather than in Solidity, so new representations
are additive.

## Representations

| Representation | Value | Where the content lives |
| --- | --- | --- |
| `inline` | the content bytes | on-chain, in the field |
| `inline-gzip` | `gzip(content)` | on-chain, decompressed off-chain |
| `reader` | a reader contract and a storage pointer | on-chain, behind a reader contract |
| `renderer` | a field-renderer contract address | computed on read, nothing stored |
| `arweave` | an Arweave transaction id | off-chain, content-addressed |
| `ipfs` | an IPFS CID | off-chain, content-addressed |
| `url` | a URL | off-chain |
| `url-template` | a URL with an `{id}` placeholder | off-chain; the resolver substitutes the token id |
| `keccak256` / `sha256` | a hash of the content | off-chain, verified against the on-chain hash |

Three mechanisms sit behind these:

- **On-chain storage (`reader`)** returns bytes that were written to chain storage. The field stores a reader contract and a pointer, and `IAbxOnChainReader.read(pointer)` returns the stored content. Storage details such as SSTORE2, compression, and chunk stitching live inside the reader. The content is fixed.
- **On-chain compute (`renderer`)** produces the value at read time. The field stores only a field-renderer address, and `IAbxFieldRenderer.render(token, tokenId, field)`, a view function, computes the value from current chain state. Nothing is stored for the value, so a renderer's output can depend on the token's state, such as its parameters, seed, or owner, while staying deterministic and reproducible.
- **Off-chain compression** is signaled by the `-gzip` suffix, because gzip cannot be reversed on-chain. A `-gzip` field is not on-chain-renderable.

## How tokenURI resolves

How the `tokenURI` and `contractURI` documents are produced is configurable per contract:

- **Off-chain pointer (default).** `tokenURI` returns a URL to a resolver, which assembles the JSON from the fields.
- **On-chain renderer.** `tokenURI` returns a `data:application/json` document assembled on-chain by a contract behind `IAbxMetadataRenderer`.

The toggle is the stored renderer address: non-zero resolves on-chain, zero uses the stored pointer.
Resolution precedence is the same for `tokenURI` and `contractURI`:

1. If a renderer is set, resolve on-chain through it.
2. Otherwise, if a per-token override URL is set, return it.
3. Otherwise, if a base URL is set, return `{base}/{chainId}/{address}/{tokenId}`.
4. Otherwise, return an empty string.

An on-chain renderer can serve only the representations it can produce on-chain: `inline`, `reader`,
`renderer`, and `url`. A field that points off-chain (`ipfs`, `arweave`) or is committed by hash
(`keccak256`, `sha256`) is served by the off-chain resolver instead. A project that resolves fully
on-chain therefore uses on-chain representations for every field.

## Locking

Two locks make metadata permanently immutable: a per-field lock, on `(scope, field)`, freezes a field's
representation and value; a URI-config lock freezes the pointer and renderer. With both set, the
resolved metadata cannot change.

## Provenance

Served metadata includes an `abx_provenance` entry per field. It records the `source` (for example
`inline`, `ipfs`, `renderer`, or `effect:<key>`) and a `status`: `on-chain`, `verified`, `mismatch`,
`anchored`, `off-chain`, or `n/a`. This states, per field, where a value came from and whether the
chain commits to it.
