> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs-sepolia.modularium.art/api/mcp` to find what you need.

# On-chain metadata

Modularium collections store metadata on-chain as a compact binary document, called **KV**, instead of pointing `tokenURI` at an off-chain JSON file. Media files are linked with `ipfs://` URIs inside the document.

## Reading

| Call | Returns |
| --- | --- |
| `tokenURI(id)` / `uri(id)` | `data:application/json;base64,…`, the document rendered as JSON. Works with any ERC-721/1155 consumer. |
| `contractURI()` | The collection document as a JSON data URI. |
| `getTokenAttribute(id, traitType)` | One `attributes[]` value as a string, or `""` if it's missing. |
| `getTokenDocument(id)` / `getContractDocument()` | Raw KV bytes. |
| `tokenURICemented(id)` / `contractURICemented()` | Whether the metadata is permanently locked. |

## Writing

| Call | Who |
| --- | --- |
| `create(document, …)` | Creator |
| `setTokenMetadataRaw(id, document)` / `setTokenMetadataBatch(ids, documents)` | Editor, until the token is cemented |
| `setDefaultTokenMetadata(document)` | Editor. Sets a collection-wide default document that is merged under every token's own document. `{id}` in a string is replaced with the token ID. |
| `setContractMetadataRaw(document)` | Owner, until cemented |

The document argument is always KV bytes.

## Document shape

The app writes standard NFT metadata keys:

```json
{
  "name": "Seed #1",
  "description": "Markdown is rendered on the token page",
  "image": "ipfs://…",
  "animation_url": "ipfs://…",
  "license": "CC0 (Public Domain)",
  "tags": ["generative", "nsfw"],
  "attributes": [{ "trait_type": "Background", "value": "Blue" }],
  "assets": [
    { "name": "Front", "image": "ipfs://…", "formats": [{ "uri": "ipfs://…", "mime_type": "image/png" }] }
  ]
}
```

`animation_url` is set for files that aren't images. `assets` is set for gallery tokens.

Collection documents use `name`, `description`, `image`, `banner_image` and `external_link`.

## Encoding

KV is a TLV encoding of JSON-like values. Each value starts with a kind byte:

| Kind | Byte | Payload |
| --- | --- | --- |
| null | `0x00` | none |
| bool | `0x01` | `0x00` or `0x01` |
| number | `0x02` | zigzag varint of an `int256`. Integers only; store fractional values as strings. |
| string | `0x03` | varint length + UTF-8 bytes |
| array | `0x04` | varint count + values |
| object | `0x05` | varint count + (varint key length + key bytes + value) for each entry |

Varints are unsigned LEB128. Nesting is limited to 64 levels.

TypeScript encoder and decoder: `packages/contracts/scripts/lib/kv.ts` in the monorepo (`encodeKVHex`, `decodeKV`). Solidity: `@modularium-art/sdk/contracts/utils/KV.sol`.

```ts
import { encodeKVHex } from './kv'

const document = encodeKVHex({ name: 'Seed #1', image: 'ipfs://…' })
```
