Skip to content

Functions

DISCLAIMER // NFA // DYOR

This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.

⊙ generated by robots | curated by humans

METADATA
Contract Address 0x00000060...531524 (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-08-08

Function Selectors

SELECTOR FUNCTION SIGNATURE CATEGORY
0x6c37705d list(address,uint24,uint32,string,string,string) Admin
0x99c4f235 listForeign(uint8,uint64,bytes32,string,string,uint8,uint24,uint32,string) Admin
0x61c85c45 reserve(bytes32,string,string,uint8,uint24,uint32,string,string,string) Admin
0xc506a2a9 activateReserved(uint256,address) Admin
0x964bc33f delist(uint256) Admin
0xdf47d810 setArt(uint256,uint24,uint32,string,string,string) Admin
0x1ba41b3d setLogoSVG(uint256,string) Admin
0xf45d6b86 setRank(uint256,uint32) Admin
0xf5222379 setAudit(uint256,string) Admin
0xbc341f98 setExtra(uint256,bytes32,string) Admin
0x4a63bb9b setStandard(uint256,uint8) Admin
0x1419d229 setOnchainSvg(uint256,bool) Admin
0x1d66e5ab setForeignText(uint256,string,string,uint8) Admin
0xd7a78db8 freeze(uint256) Admin
0x56d3163d setRenderer(address) Admin
0x9c8a2bfd lockRenderer() Admin
0xf2fde38b / 0x715018a6 / 0xf04e283e transferOwnership(address) / renounceOwnership() / completeOwnershipHandover(address) Admin (Solady Ownable)
0xb1357bf9 sync(uint256) User (permissionless)
0xac9650d8 multicall(bytes[]) User (Solady Multicallable)
0x25692962 / 0x54d1f13d requestOwnershipHandover() / cancelOwnershipHandover() User (Solady Ownable)
0x095ea7b3 / 0xa22cb465 approve(address,uint256) / setApprovalForAll(address,bool) User (inert — transfers always revert)
0x23b872dd / 0x42842e0e / 0xb88d4fde transferFrom(...) / safeTransferFrom(...) ×2 Disabled (always reverts Soulbound())
0x9507d39a / 0xc2bc2efc get(uint256) / get(address) View
0x74e18e96 json(uint256) View
0xc87b56dd tokenURI(uint256) View
0xe8a3d485 contractURI() View
0xdf7ca268 / 0xbe9a4952 rankedIds() / rankedIdsPaged(uint256,uint256) View
0x9ca6a2bc summariesPaged(uint256,uint256) View
0x1b268898 search(string,uint256) View
0xd94fe832 / 0x7695a541 idOf(address) / idOf(uint8,uint64,bytes32) View
0xf90423fe / 0xdf7667d7 idFor(address) / reservationId(bytes32) View
0xb45a3c0e locked(uint256) View (ERC-5192)
0x6e798828 / 0x973d0833 getExtra(uint256,bytes32) / extraKeys(uint256) View
0xecbdd772 / 0x6ce273ac logoOf(uint256) / logoOf(address) View
0x0697b3ab / 0x7e6c8e24 themeOf(uint256) / themeOf(address) View
0x2ddbd13a / 0x4fe0d5c6 / 0xfcce4883 total() / idAt(uint256) / isListed(uint256) View
0x8ada6b0f / 0x6094a070 / 0x8da5cb5b renderer() / rendererLocked() / owner() View (auto-getter)
0x06fdde03 / 0x95d89b41 / 0x01ffc9a7 name() / symbol() / supportsInterface(bytes4) View
0x70a08231 / 0x6352211e / 0x081812fc / 0xe985e9c5 / 0xfee81cf4 balanceOf(address) / ownerOf(uint256) / getApproved(uint256) / isApprovedForAll(address,address) / ownershipHandoverExpiresAt(address) View (inherited)

Selectors were computed from the verified ABI via cast sig "<signature>".


Summary

CATEGORY COUNT
Total Functions 62
User Functions 9
Admin Functions 19
View Functions 34

Of the 9 user functions, five are the inherited ERC-721 transfer and approval surface, and every
transfer path reverts. Only sync performs a state change that a non-owner can meaningfully use.


User Functions

Function: sync(uint256 id)

Re-reads a local token's name, symbol, decimals and ERC-165 standard from the token
contract and writes them into the listing. This is the function that makes the registry's
provenance claim operational: the curator is never the reason a stale symbol persists, because
anyone can refresh it.

ATTRIBUTE VALUE
Selector 0xb1357bf9
Parameters id — the listing id
Access Public, permissionless, non-payable
FLAG OBSERVATION
☑ Deliberately not onlyOwner. Refreshing a fact from its own source is not treated as a privilege
☑ Exempt from the freeze gate — it uses _mustExist, not _mustEdit. Freezing seals what governance authored, not what the token says
☑ Re-checks token.code.length so a selfdestructed subject cannot have synced = true re-asserted against empty reads
△ _pull sets synced = true unconditionally at the end, even when every read returned empty. An unreadable token still reports as onchain-sourced
◇ Empty reads are treated as "no answer" rather than "the answer is empty", so a transient read failure cannot blank a good listing
CONDITION REQUIREMENT
Listing exists _position[id] != 0, else Unknown()
Listing is local EVM t.kind == Kind.EVM && t.chainId == block.chainid && t.account != 0, else NotSyncable()
Subject still has code token.code.length != 0, else NotSyncable()
STEP ACTION
1 Resolve the subject address from t.account
2 readName / readSymbol under a 60,000 gas cap, sanitise via _clean, write only if non-empty
3 readUint("decimals()"), range-check d != 0 && d <= 36 before narrowing to uint8
4 Probe ERC-165 for 0x80ac58cd (ERC-721) then 0xd9b67a26 (ERC-1155); fall back to ERC-20
5 Clear onchainSvg if the resolved standard is not a collection standard
6 Set synced = true, emit Updated(id, "sync") and MetadataUpdate(id)
VARIABLE CHANGE
_tokens[id].name Overwritten if the read returned non-empty
_tokens[id].symbol Overwritten if the read returned non-empty
_tokens[id].decimals Overwritten if 0 < d <= 36
_tokens[id].standard Overwritten if the probe resolved to a known standard
_tokens[id].onchainSvg Forced to false on non-collection standards
_tokens[id].synced Set to true
CONDITION REVERT MESSAGE
_position[id] == 0 Unknown()
Non-EVM kind, foreign chain id, or account == 0 NotSyncable()
Subject has no code NotSyncable()
function sync(uint256 id) public {
    Token storage t = _mustExist(id);
    if (t.kind != Kind.EVM || t.chainId != block.chainid || t.account == bytes32(0)) {
        revert NotSyncable();
    }
    address token = address(uint160(uint256(t.account)));
    if (token.code.length == 0) revert NotSyncable();
    _pull(t, token);
    _touch(id, "sync");
}

function _pull(Token storage t, address token) internal {
    string memory s = _clean(MetadataReaderLib.readName(token, NAME_MAX * 4, READ_GAS), NAME_MAX);
    if (bytes(s).length != 0) t.name = s;
    s = _clean(MetadataReaderLib.readSymbol(token, SYMBOL_MAX * 4, READ_GAS), SYMBOL_MAX);
    if (bytes(s).length != 0) t.symbol = s;
    uint256 d = MetadataReaderLib.readUint(token, abi.encodeWithSignature("decimals()"), READ_GAS);
    if (d != 0 && d <= 36) t.decimals = uint8(d);
    Standard s_ = _standardOf(token);
    if (s_ != Standard.UNKNOWN) {
        t.standard = s_;
        if (s_ != Standard.ERC721 && s_ != Standard.ERC1155) t.onchainSvg = false;
    }
    t.synced = true;
}

Function: multicall(bytes[] calldata data)

Solady's Multicallable. Batches several calls to this contract in one transaction, which is
the mechanism the contract's own comments point consumers at instead of providing struct-level
pagination — a caller batches get(id) or json(id) for exactly the rows on screen.

ATTRIBUTE VALUE
Selector 0xac9650d8
Parameters data — array of ABI-encoded calls
Access Public, payable
FLAG OBSERVATION
☑ The intended read path for dapps: one eth_call over an arbitrary set of ids
☑ Declared payable but rejects value — if (msg.value != 0) revert() runs before anything else, so this is not a route for stranding ETH in the contract
◇ Batches by self-delegatecall, so each inner call executes in this contract's storage context and sees the original msg.sender
function multicall(bytes[] calldata data) public payable virtual returns (bytes[] memory) {
    // Revert if `msg.value` is non-zero by default to guard against double-spending.
    if (msg.value != 0) revert();
    // `_multicallDirectReturn` returns the results directly and terminates the call context.
    _multicallDirectReturn(_multicall(data));
}

Functions: transferFrom / safeTransferFrom ×2

The ERC-721 transfer surface. Every one of these paths reverts. _beforeTokenTransfer rejects
any transition where both from and to are non-zero, which leaves mint (list) and burn
(delist) as the only state transitions a listing can undergo.

ATTRIBUTE VALUE
Selectors 0x23b872dd, 0x42842e0e, 0xb88d4fde
Access Public, payable, but unconditionally reverting
FLAG OBSERVATION
☑ Listings are permanently bound to their subject. A card cannot drift away from the token it describes
☑ Advertised correctly: supportsInterface(0xb45a3c0e) returns true and locked(id) returns true for every existing listing
◇ Locked is emitted at mint; Unlocked deliberately does not exist because no listing is ever unlocked
CONDITION REVERT MESSAGE
Any transfer between two non-zero addresses Soulbound()
function _beforeTokenTransfer(address from, address to, uint256) internal pure override {
    if (from != address(0) && to != address(0)) revert Soulbound();
}

Functions: approve / setApprovalForAll / requestOwnershipHandover / cancelOwnershipHandover

Inherited surface that remains callable but has no useful effect here. approve and
setApprovalForAll record approvals for transfers that can never succeed.
requestOwnershipHandover lets any address register a 48-hour handover request that only the
current owner can complete; cancelOwnershipHandover withdraws the caller's own request.

ATTRIBUTE VALUE
Selectors 0x095ea7b3, 0xa22cb465, 0x25692962, 0x54d1f13d
Access Public. approve, requestOwnershipHandover and cancelOwnershipHandover are payable; setApprovalForAll is not
FLAG OBSERVATION
◇ Approvals are inert given the soulbound check, but are not blocked
△ A pending handover request is not an ownership change; only completeOwnershipHandover by the current owner transfers control

Admin Functions

All functions in this section carry Solady's onlyOwner modifier and are therefore callable only
by 0x006CD14F...8549F2 (etherscan), the 2-of-3 Multisig.

Function: list(address token, uint24 color, uint32 rank, string logo, string url_, string description_)

Lists a token deployed on this chain. The signature is the clearest expression of the
contract's central design choice: there is no name, symbol or decimals parameter. Those
are read from the token contract itself. The owner supplies only presentation.

ATTRIBUTE VALUE
Selector 0x6c37705d
Parameters token, color (0xRRGGBB), rank (sort weight), logo, url_, description_
Access onlyOwner, non-payable, returns uint256 id
FLAG OBSERVATION
☑ Identity fields cannot be authored by the curator — they come from _pull
☑ Requires the subject to have code. A staticcall to a codeless address succeeds with empty returndata, which would produce a blank listing still flagged synced
☑ Uses _mint, not _safeMint, deliberately: the subject holds its own card and must not be able to refuse it via a receiver hook
☑ Double-listing is blocked on both the id and the address binding
△ The owner still authors logo, colour, rank, link and description — none of which have any on-chain backing
CONDITION REQUIREMENT
Caller is owner onlyOwner
Subject is a contract token.code.length != 0, else BadInput()
Not already listed _position[id] == 0 && _boundLocalId[token] == 0, else Exists()
Logo within bounds bytes(logo).length <= 24_576, else BadInput()
Logo scheme allowed data:image/, https:// or ipfs:// prefix, else BadInput()
STEP ACTION
1 Derive id = uint256(uint160(token))
2 Write account, chainId, kind = EVM, deployed = true
3 _setArt — validate and store colour, rank, logo URI, cleaned url and description
4 _pull — read name, symbol, decimals and standard from the token; set synced = true
5 _index — append to _ids and record _position
6 Bind _boundLocalId[token] = id
7 _mintListing — mint the NFT to token and emit Locked(id)
8 Emit Listed(id, account, chainId, symbol, name, decimals, synced)
VARIABLE CHANGE
_tokens[id] Fully populated
_ids Appended
_position[id] Set to _ids.length
_boundLocalId[token] Set to id
ERC-721 owner/balance token becomes holder of id
CONDITION REVERT MESSAGE
Caller is not owner Unauthorized()
token has no code BadInput()
Already listed or bound Exists()
Logo too long or bad scheme/characters BadInput()
function list(
    address token, uint24 color, uint32 rank,
    string calldata logo, string calldata url_, string calldata description_
) public onlyOwner returns (uint256 id) {
    if (token.code.length == 0) revert BadInput();
    id = idOf(token);
    if (_position[id] != 0 || _boundLocalId[token] != 0) revert Exists();

    Token storage t = _tokens[id];
    t.account = bytes32(uint256(uint160(token)));
    t.chainId = uint64(block.chainid);
    t.kind = Kind.EVM;
    t.deployed = true;
    _setArt(t, color, rank, logo, url_, description_);
    _pull(t, token);
    _index(id);
    _boundLocalId[token] = id;
    _mintListing(token, id); // deliberately not _safeMint: the subject is the holder
    _emitListed(id, t);
}

Lists an asset whose metadata cannot be read from this chain — another chain's token, a Solana
mint, a Bitcoin-rooted asset, or this chain's native asset at address(0). Because no
on-chain source exists, the text is curator-supplied and the listing is stored with
synced = false so consumers can render the distinction.

ATTRIBUTE VALUE
Selector 0x99c4f235
Parameters Namespace tuple plus curator-authored text and presentation
Access onlyOwner, non-payable, returns uint256 id
FLAG OBSERVATION
☑ Cannot be used to bypass list — a live local token is rejected with NotSyncable()
☑ Enforces chainId == 0 for non-EVM namespaces so one asset cannot be listed twice under two different chain ids
☑ Card is minted to the registry itself, not to the current owner, so an ownership handover does not leave the previous curator as ownerOf forever
△ Every text field here is a curator assertion with no verification whatsoever
CONDITION REQUIREMENT
Not a live local token !(kind == EVM && chainId == block.chainid && account != 0), else NotSyncable()
Non-EVM implies no chain id kind != EVM implies chainId == 0, else BadInput()
Decimals sane decimals_ <= 36, else BadInput()
EVM account fits 160 bits enforced inside idOf, else BadInput()
Not already listed _position[id] == 0, else Exists()
CONDITION REVERT MESSAGE
Live local token supplied NotSyncable()
Non-EVM kind with non-zero chain id BadInput()
decimals_ > 36 BadInput()
Already listed Exists()
function listForeign(
    Kind kind, uint64 chainId, bytes32 account,
    string calldata name_, string calldata symbol_, uint8 decimals_,
    uint24 color, uint32 rank, string calldata logo
) public onlyOwner returns (uint256 id) {
    if (kind == Kind.EVM && chainId == block.chainid && account != bytes32(0)) {
        revert NotSyncable();
    }
    if (kind != Kind.EVM && chainId != 0) revert BadInput();
    if (decimals_ > 36) revert BadInput();
    id = idOf(kind, chainId, account);
    if (_position[id] != 0) revert Exists();

    Token storage t = _tokens[id];
    t.account = account;
    t.chainId = chainId;
    t.kind = kind;
    t.deployed = true;
    t.decimals = decimals_;
    t.name = _clean(name_, NAME_MAX);
    t.symbol = _clean(symbol_, SYMBOL_MAX);
    _setArt(t, color, rank, logo, "", "");
    _index(id);
    _mintListing(address(this), id);
    _emitListed(id, t);
}

Function: reserve(bytes32 key, ...) / activateReserved(uint256 id, address token)

A two-stage flow for tokens that do not exist yet. reserve creates a listing with a stable
hash-derived id from a curator-chosen key, marked deployed = false.
activateReserved later binds the real address, pulls its facts, and moves the card from the
registry to the token — preserving the original id so galleries and governance references never
have to migrate.

ATTRIBUTE VALUE
Selectors 0x61c85c45, 0xc506a2a9
Access onlyOwner, non-payable
Usage to date Never called — no Reserved or Activated events exist
FLAG OBSERVATION
☑ activateReserved uses _mustExist, not _mustEdit, so a frozen reservation can still be bound to its deployed token. Freezing seals what governance authored, not whether the subject shipped
☑ Activation requires the target to actually resolve as ERC-20 via _standardOf, and to be unbound and unlisted
☑ Burns and re-mints in the same transaction, restoring the subject-as-holder invariant
◇ Reservations are current-chain only, because activation must read code at the eventual address
△ The reservation card renders a full-frame "RESERVED / NOT DEPLOYED" banner, but a consumer reading storage must check deployed itself
CONDITION REQUIREMENT
reserve: key non-zero key != bytes32(0), else BadInput()
reserve: decimals sane decimals_ <= 36, else BadInput()
activateReserved: still a reservation !t.deployed && t.kind == EVM && t.chainId == block.chainid && t.account == 0, else BadInput()
activateReserved: target usable has code, unbound, unlisted, and _standardOf(token) == ERC20, else BadInput()
function activateReserved(uint256 id, address token) public onlyOwner {
    Token storage t = _mustExist(id);
    if (t.deployed || t.kind != Kind.EVM || t.chainId != block.chainid || t.account != bytes32(0)) {
        revert BadInput();
    }
    if (
        token.code.length == 0 || _boundLocalId[token] != 0 || _position[idOf(token)] != 0
            || _standardOf(token) != Standard.ERC20
    ) revert BadInput();

    t.account = bytes32(uint256(uint160(token)));
    t.deployed = true;
    _pull(t, token);
    _boundLocalId[token] = id;
    _burn(id);
    _mintListing(token, id);
    _touch(id, "activate");
    emit Activated(id, token);
}

Function: delist(uint256 id)

Burns the listing NFT and removes the entry entirely — the record, the index position, the
address binding, and every extension key.

ATTRIBUTE VALUE
Selector 0x964bc33f
Parameters id
Access onlyOwner, non-payable
Usage to date Never called
FLAG OBSERVATION
☑ Remains available on frozen listings by design, so a project that later turns hostile can still be removed
☑ The extension key loop is bounded by the 32-key cap enforced in setExtra, so delisting always fits in a block
◇ Swap-and-pop removal means tied ranks are deterministic but not stable across removals — the last entry inherits the removed one's index
△ Delisting is silent to holders: the subject contract loses its card with no notice mechanism beyond the Delisted event
STEP ACTION
1 Read account before the record is wiped
2 Clear _boundLocalId if this was a deployed local EVM listing with a non-zero account
3 Swap the last id into this position, update _position[last], pop _ids
4 Delete _position[id] and _tokens[id]
5 Walk _extraKeys[id] in reverse, deleting each _extra and _extraPos entry, then delete the key array
6 _burn(id) and emit Delisted(id, account)
CONDITION REVERT MESSAGE
Caller is not owner Unauthorized()
_position[id] == 0 Unknown()
function delist(uint256 id) public onlyOwner {
    uint256 pos = _position[id];
    if (pos == 0) revert Unknown();
    Token storage t = _tokens[id];
    bytes32 account = t.account; // read before the entry is wiped
    if (t.deployed && t.kind == Kind.EVM && t.chainId == block.chainid && account != bytes32(0)) {
        delete _boundLocalId[address(uint160(uint256(account)))];
    }
    uint256 last = _ids[_ids.length - 1];
    _ids[pos - 1] = last;
    _position[last] = pos;
    _ids.pop();
    delete _position[id];
    delete _tokens[id];
    bytes32[] storage keys = _extraKeys[id];
    for (uint256 i = keys.length; i > 0; --i) {
        bytes32 key = keys[i - 1];
        delete _extra[id][key];
        delete _extraPos[id][key];
    }
    delete _extraKeys[id];
    _burn(id);
    emit Delisted(id, account);
}

Function: setArt(uint256 id, uint24 color, uint32 rank, string logo, string url_, string description_)

Rewrites the owner-authored presentation of a listing in one call: theme colour, sort weight,
logo URI, project link and description. This is the single most-used admin function in the
contract's history — 13 of the 19 Updated events emitted carry the field label "art", ahead
of nftArt and extra at 2 each and text and audit at 1 each.

ATTRIBUTE VALUE
Selector 0xdf47d810
Access onlyOwner, non-payable
FLAG OBSERVATION
☑ Gated by _mustEdit, so a frozen listing rejects it
☑ Logo must be data:image/, https:// or ipfs:// — a bare data: payload such as data:text/html is rejected, which stops markup being handed to whatever renders the card
☑ _uri rejects quote, angle bracket, ampersand, backslash and control characters outright rather than escaping them
△ Rewrites all five fields at once. Passing an empty logo clears it rather than leaving it unchanged
CONDITION REQUIREMENT
Listing exists and is editable _mustEdit(id) — else Unknown() or NotEditable()
Logo within bounds bytes(logo).length <= 24_576, else BadInput()
Logo scheme and charset valid _uri checks, else BadInput()
function setArt(
    uint256 id, uint24 color, uint32 rank,
    string calldata logo, string calldata url_, string calldata description_
) public onlyOwner {
    _setArt(_mustEdit(id), color, rank, logo, url_, description_);
    _touch(id, "art");
}

function _setArt(
    Token storage t, uint24 color, uint32 rank,
    string memory logo, string memory url_, string memory description_
) internal {
    if (bytes(logo).length > LOGO_MAX) revert BadInput();
    t.color = color;
    t.rank = rank;
    t.logo = _uri(logo);
    t.url = _clean(url_, URL_MAX);
    t.description = _clean(description_, DESC_MAX);
}

Function: setLogoSVG(uint256 id, string calldata svg)

Accepts raw SVG markup, base64-encodes it on-chain into a data:image/svg+xml URI, and stores
that as the logo. The convenience path for putting genuinely on-chain art on a card without the
caller having to encode it.

ATTRIBUTE VALUE
Selector 0x1ba41b3d
Access onlyOwner, non-payable
FLAG OBSERVATION
☑ Requires the markup to contain the SVG namespace string. A standalone SVG without xmlns renders as a broken image in every wallet, so this converts a silent list-wide display failure into a failed transaction
☑ The 24,576-byte cap is applied to the encoded result, not the raw markup — base64 costs four bytes per three, so bounding the input instead would let this path store roughly a third more than setArt accepts for the same field
◇ Emits under the "art" field label, same as setArt
CONDITION REVERT MESSAGE
Markup lacks http://www.w3.org/2000/svg BadInput()
Encoded URI exceeds 24,576 bytes BadInput()
Listing missing or frozen Unknown() / NotEditable()
function setLogoSVG(uint256 id, string calldata svg) public onlyOwner {
    if (!LibString.contains(svg, "http://www.w3.org/2000/svg")) revert BadInput();
    string memory uri = _dataURI("image/svg+xml", svg);
    if (bytes(uri).length > LOGO_MAX) revert BadInput();
    _mustEdit(id).logo = uri;
    _touch(id, "art");
}

Function: setForeignText(uint256 id, string name_, string symbol_, uint8 decimals_)

The only function that can write name, symbol or decimals directly, and the guard on it is
what makes the whole provenance model hold. It reverts on any listing where synced is true.

ATTRIBUTE VALUE
Selector 0x1d66e5ab
Access onlyOwner, non-payable
FLAG OBSERVATION
☑ if (t.synced) revert NotSyncable(); — the curator cannot overwrite a fact that was read from a token
☑ Since _pull sets synced = true on every local listing, this function is permanently unavailable for all 15 local entries
△ For foreign listings there is no verification at all; the text is whatever the curator types
CONDITION REVERT MESSAGE
Listing is onchain-sourced NotSyncable()
decimals_ > 36 BadInput()
Listing missing or frozen Unknown() / NotEditable()
function setForeignText(uint256 id, string calldata name_, string calldata symbol_, uint8 decimals_)
    public onlyOwner
{
    Token storage t = _mustEdit(id);
    if (t.synced) revert NotSyncable();
    if (decimals_ > 36) revert BadInput();
    t.name = _clean(name_, NAME_MAX);
    t.symbol = _clean(symbol_, SYMBOL_MAX);
    t.decimals = decimals_;
    _touch(id, "text");
}

Function: setExtra(uint256 id, bytes32 key, string calldata value)

Sets or clears an open-ended metadata field. This is the contract's escape hatch: the Token
struct is fixed at deployment, but the fields a token list gets asked for are not, so a new
field costs a transaction rather than a redeployment and a re-listing.

ATTRIBUTE VALUE
Selector 0xbc341f98
Access onlyOwner, non-payable
Usage to date 2 keys set, both on the Tacit listing: "etch tx" and "issued"
FLAG OBSERVATION
☑ Capped at 32 keys per listing specifically because delist walks every key — an unbounded key set would be an unbounded loop in the only path that can remove a listing
☑ Values pass through the same _clean filter as every other display string
☑ Clearing a key that was never set returns early rather than emitting events for a write that did not happen
◇ An empty value removes the key entirely, so extraKeys never reports a field that reads back empty
STEP ACTION
1 _mustEdit(id) and reject a zero key
2 Sanitise the value to at most 256 bytes
3 If empty and present, swap-and-pop the key out of _extraKeys and delete both mappings
4 If non-empty and new, enforce the 32-key cap, then push and record position
5 Write the value, emit ExtraSet and Updated(id, "extra")
function setExtra(uint256 id, bytes32 key, string calldata value) public onlyOwner {
    _mustEdit(id);
    if (key == bytes32(0)) revert BadInput();
    string memory v = _clean(value, EXTRA_MAX);
    uint256 pos = _extraPos[id][key];
    if (bytes(v).length == 0) {
        if (pos == 0) return;
        _dropExtra(id, key, pos);
    } else {
        if (pos == 0) {
            if (_extraKeys[id].length >= EXTRA_KEYS_MAX) revert BadInput();
            _extraKeys[id].push(key);
            _extraPos[id][key] = _extraKeys[id].length;
        }
        _extra[id][key] = v;
    }
    emit ExtraSet(id, key, v);
    _touch(id, "extra");
}

Function: setStandard(uint256 id, Standard standard_) / setOnchainSvg(uint256 id, bool onchainSvg_)

setStandard declares what a foreign listing represents, and is gated the same way as
setForeignText — a local listing derives its standard from the token and the owner must not
be able to overwrite it. setOnchainSvg marks an NFT collection whose selected token ids
resolve to on-chain SVG art.

ATTRIBUTE VALUE
Selectors 0x4a63bb9b, 0x1419d229
Access onlyOwner, non-payable
FLAG OBSERVATION
☑ setStandard reverts NotEditable() on any synced listing
☑ Both functions clear or gate onchainSvg on collection standards only, so a fungible token cannot pose as a per-token artwork source
☑ The enum cannot grow after deployment — an out-of-range value reverts at ABI decode, so unnamed asset types must be carried as extension fields
◇ onchainSvg is a rendering hint, not a trust bypass; a client still reads and validates the target tokenURI itself
◇ Currently true on two listings: zOrgz and Wei Name Service
CONDITION REVERT MESSAGE
setStandard on a synced listing NotEditable()
setOnchainSvg on a non-collection standard BadInput()
Listing missing or frozen Unknown() / NotEditable()
function setStandard(uint256 id, Standard standard_) public onlyOwner {
    Token storage t = _mustEdit(id);
    if (t.synced) revert NotEditable();
    t.standard = standard_;
    if (standard_ != Standard.ERC721 && standard_ != Standard.ERC1155) t.onchainSvg = false;
    _touch(id, "text");
}

function setOnchainSvg(uint256 id, bool onchainSvg_) public onlyOwner {
    Token storage t = _mustEdit(id);
    if (t.standard != Standard.ERC721 && t.standard != Standard.ERC1155) revert BadInput();
    t.onchainSvg = onchainSvg_;
    _touch(id, "nftArt");
}

Function: freeze(uint256 id)

Permanently seals a listing's owner-authored fields. After this the curator cannot touch art,
links, rank, audit, foreign text or extras for that id, ever.

ATTRIBUTE VALUE
Selector 0xd7a78db8
Access onlyOwner, non-payable, irreversible
Usage to date Never called — no listing is frozen
FLAG OBSERVATION
☑ Every owner-authored setter routes through _mustEdit, so a future setter cannot silently escape the seal
☑ Uses _mustEdit itself, so freezing twice reverts rather than emitting an event for a change that did not happen
☑ sync, activateReserved and delist deliberately still work on frozen listings
△ A freeze is only as strong as the renderer. While setRenderer remains open, the owner can still change what a frozen listing displays, even though it cannot change what the listing stores
function freeze(uint256 id) public onlyOwner {
    _mustEdit(id).frozen = true;
    emit Froze(id);
    _touch(id, "freeze");
}

Function: setRenderer(TokenListRenderer renderer_) / lockRenderer()

setRenderer points the registry at a new card renderer; lockRenderer gives up that power
permanently. Together these are the most consequential admin functions in the contract, because
the renderer authors everything a wallet or marketplace actually displays.

ATTRIBUTE VALUE
Selectors 0x56d3163d, 0x9c8a2bfd
Access onlyOwner, non-payable
Usage to date setRenderer called once (2026-08-05); lockRenderer never called
FLAG OBSERVATION
△ A renderer may print any text it likes regardless of storage. Swapping it changes what every listing appears to say, including frozen ones
☑ Code-length checked, for the same reason list checks it: an Externally Owned Account (EOA) here would make tokenURI return empty for every listing rather than revert — a silent, list-wide failure
☑ Emits BatchMetadataUpdate(0, type(uint256).max) and ContractURIUpdated() so clients refresh every cached card and the collection tile
☑ A replacement renderer must implement tokenURI, json and contractURI, or those reads revert
◇ Consumers that need unmediated facts should read get/json struct fields from storage rather than parsing the card
CONDITION REVERT MESSAGE
rendererLocked == true NotEditable()
Target has no code BadInput()
Caller is not owner Unauthorized()
function lockRenderer() public onlyOwner {
    rendererLocked = true;
    emit RendererLocked();
}

function setRenderer(TokenListRenderer renderer_) public onlyOwner {
    if (rendererLocked) revert NotEditable();
    if (address(renderer_).code.length == 0) revert BadInput();
    renderer = renderer_;
    emit RendererSet(address(renderer_));
    emit BatchMetadataUpdate(0, type(uint256).max);
    emit ContractURIUpdated();
}

Function: setRank(uint256 id, uint32 rank) / setAudit(uint256 id, string audit_)

Two narrow setters. setRank adjusts a listing's sort weight — higher sorts first, 0 is
unranked. setAudit points a listing at an audit or security report, which the card renders as
a footer link and omits entirely when empty.

ATTRIBUTE VALUE
Selectors 0xf45d6b86, 0xf5222379
Access onlyOwner, non-payable
FLAG OBSERVATION
☑ Seeded ranks are deliberately sparse (steps of 1,000 and 500) so a listing can be inserted between two others without renumbering
◇ Rank is the only ordering the contract promises; ties break by index position, which is deterministic but not stable across delistings
△ It is not the only ordering that exists over these listings. ZorgConviction, deployed three days after this registry, keeps a stake-weighted score per listing id and is what an interface sorting "by conviction" reads. That contract calls only isListed here and has no path to setRank, so the two orderings are independent and nothing on-chain reconciles them
△ audit is an unverified URL. The presence of an audit link on a card is a curator claim, not evidence a report exists or that it was favourable
◇ Only one listing currently carries an audit link — the Tacit entry
function setRank(uint256 id, uint32 rank) public onlyOwner {
    _mustEdit(id).rank = rank;
    _touch(id, "rank");
}

function setAudit(uint256 id, string calldata audit_) public onlyOwner {
    _mustEdit(id).audit = _clean(audit_, URL_MAX);
    _touch(id, "audit");
}

Functions: transferOwnership / renounceOwnership / completeOwnershipHandover

Solady Ownable administration. transferOwnership hands curation to a new address directly;
completeOwnershipHandover accepts a pending request; renounceOwnership sets the owner to
the zero address permanently.

ATTRIBUTE VALUE
Selectors 0xf2fde38b, 0x715018a6, 0xf04e283e
Access onlyOwner, payable
FLAG OBSERVATION
☒ renounceOwnership is inherited and deliberately left open. Calling it would permanently end all curation — no listing, no delisting, no correcting a bad link, by anyone, ever
△ The constructor rejects a zero initialOwner so the list cannot be born ownerless, but nothing prevents reaching that state later
△ Declared payable with no withdrawal path — attached ETH would be locked
◇ The stated trade-off is that renouncing declares the list final; the cost is that a project that later rugs becomes a permanent billboard nobody can take down
// Inherited unmodified from Solady's Ownable.
// _OWNER_SLOT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927

View Functions

Function: get(uint256 id) / get(address token)

Returns the full Token struct for a listing. The address overload routes through idFor so
an activated reservation resolves correctly despite keeping its hash-derived id.

ATTRIBUTE VALUE
Selectors 0x9507d39a, 0xc2bc2efc
Access Public view
FLAG OBSERVATION
☑ The recommended read for consumers that need unmediated facts — it bypasses the renderer entirely
◇ Returns the heavy fields including the logo, so batching many of these through multicall can produce large returndata
◇ There is deliberately no getMany(ids); Multicallable already batches over an arbitrary set, which is strictly more flexible
CONDITION REVERT MESSAGE
Listing does not exist Unknown()
function get(uint256 id) public view returns (Token memory) { return _mustExist(id); }
function get(address token) public view returns (Token memory) { return _mustExist(idFor(token)); }

function idFor(address token) public view returns (uint256 id) {
    id = _boundLocalId[token];
    if (id == 0) id = idOf(token);
}

Function: json(uint256 id) / tokenURI(uint256 id) / contractURI()

The three renderer-forwarded reads. json returns a compact machine-readable form with
single-letter keys, tokenURI returns the full ERC-721 metadata document with an embedded SVG
card, and contractURI returns ERC-7572 collection metadata.

ATTRIBUTE VALUE
Selectors 0x74e18e96, 0xc87b56dd, 0xe8a3d485
Access Public view
FLAG OBSERVATION
△ All three depend on the mutable renderer pointer. What these return is not constrained by what the registry stores
☑ The registry flattens the extension mapping via _extrasOf before forwarding, because the renderer is pure and cannot read state itself
☑ contractURI forwards in hand-written assembly, returning the callee's returndata verbatim rather than decoding and re-encoding a dynamic string — the contract had only a few hundred bytes of EIP-170 headroom
◇ The JSON v field reports whether name/symbol/decimals were read from the token. It is a statement about provenance, not about the token being trustworthy
◇ Cards are fully self-contained: the SVG is inlined and base64-encoded into a data URI with no external fetch
function json(uint256 id) public view returns (string memory) {
    return renderer.json(id, _mustExist(id), _extrasOf(id));
}

function tokenURI(uint256 id) public view override returns (string memory) {
    return renderer.tokenURI(id, _mustExist(id), _extrasOf(id));
}

function contractURI() public view returns (string memory) {
    address r = address(renderer);
    assembly ("memory-safe") {
        let m := mload(0x40)
        mstore(m, 0xe8a3d485) // `contractURI()`
        let ok := staticcall(gas(), r, add(m, 0x1c), 0x04, codesize(), 0x00)
        returndatacopy(m, 0x00, returndatasize())
        if iszero(ok) { revert(m, returndatasize()) }
        return(m, returndatasize())
    }
}

Function: rankedIds() / rankedIdsPaged(uint256 start, uint256 count) / summariesPaged(uint256 start, uint256 count)

The list-building reads. rankedIds returns every id sorted by descending rank;
rankedIdsPaged slices that; summariesPaged returns listings without the unbounded fields —
no logo, no description, no links — which is what a token picker or dropdown actually needs.

ATTRIBUTE VALUE
Selectors 0xdf7ca268, 0xbe9a4952, 0x9ca6a2bc
Access Public view
FLAG OBSERVATION
☑ rankedIds packs rank and index into one word so a single LibSort.sort orders both keys, then reverses for descending order
☑ summariesPaged is bounded per row, so page size is predictable. A whole-struct page would be megabytes once 24 KB logos are counted, and would hit provider eth_call limits
◇ Intended for eth_call, not on-chain use — rankedIds allocates and sorts the full set on every invocation
◇ Ties break by array position, which changes when a delisting swap-pops the last entry into the removed slot
function rankedIds() public view returns (uint256[] memory out) {
    uint256 n = _ids.length;
    uint256[] memory keys = new uint256[](n);
    for (uint256 i; i < n; ++i) {
        keys[i] = (uint256(_tokens[_ids[i]].rank) << 32) | (n - 1 - i);
    }
    LibSort.sort(keys);
    LibSort.reverse(keys);
    out = new uint256[](n);
    for (uint256 i; i < n; ++i) {
        out[i] = _ids[n - 1 - (keys[i] & 0xffffffff)];
    }
}

Function: search(string calldata query, uint256 limit)

Case-insensitive substring search over every listing's symbol and name, capped at limit
results.

ATTRIBUTE VALUE
Selector 0x1b268898
Access Public view
FLAG OBSERVATION
◇ Linear scan. The list is curated, so it is expected to stay small enough that an index would cost more to maintain than this costs to run
☑ Allocation is bounded by min(limit, n) rather than by list size, and the result array is truncated in assembly to the number actually found
△ Cost grows with list size and would become impractical to call on-chain if the registry grew large
function search(string calldata query, uint256 limit) public view returns (uint256[] memory out) {
    string memory needle = LibString.lower(query);
    uint256 n = _ids.length;
    uint256[] memory hits = new uint256[](limit < n ? limit : n);
    uint256 found;
    for (uint256 i; i < n && found < limit; ++i) {
        uint256 id = _ids[i];
        Token storage t = _tokens[id];
        if (LibString.lower(t.symbol).contains(needle) || LibString.lower(t.name).contains(needle)) {
            hits[found++] = id;
        }
    }
    out = hits;
    assembly ("memory-safe") { mstore(out, found) }
}

Function: idOf(address) / idOf(Kind, uint64, bytes32) / reservationId(bytes32)

The three id derivation rules. A local EVM listing's id is literally its address as a
uint256; everything else is a keccak256 hash with bit 255 set; reservations use a separate
domain tag.

ATTRIBUTE VALUE
Selectors 0xd94fe832, 0x7695a541, 0xdf7667d7
Access Public pure / view
FLAG OBSERVATION
☑ The two id spaces are disjoint by construction — local ids are below 2^160, foreign ids have bit 255 set
☑ The native asset resolves to id 0 under both overloads, so get(address(0)) and idOf(address(0)) agree
☑ EVM accounts must fit in 160 bits, else BadInput(). Without that check a foreign EVM listing could be created whose address rendering reverts, leaving a listing that get returns but json and tokenURI fail on
◇ The identity rule is the one part of the schema that cannot be migrated once consumers cache it
function idOf(address token) public pure returns (uint256) {
    return uint256(uint160(token));
}

function idOf(Kind kind, uint64 chainId, bytes32 account) public view returns (uint256) {
    if (kind == Kind.EVM) {
        if (uint256(account) >> 160 != 0) revert BadInput();
        if (chainId == block.chainid) return uint256(account);
    }
    return uint256(keccak256(abi.encode(kind, chainId, account))) | FOREIGN_FLAG;
}

function reservationId(bytes32 key) public view returns (uint256) {
    if (key == bytes32(0)) revert BadInput();
    return uint256(keccak256(abi.encode(RESERVATION_TAG, block.chainid, key))) | FOREIGN_FLAG;
}

Function: locked(uint256 id) / supportsInterface(bytes4 interfaceId)

ERC-5192 and interface advertisement. locked returns true for every existing listing.
supportsInterface reports ERC-4906 (0x49064906), ERC-5192 (0xb45a3c0e) and, through
Solady's base implementation, ERC-721 and ERC-165.

ATTRIBUTE VALUE
Selectors 0xb45a3c0e, 0x01ffc9a7
Access Public view
FLAG OBSERVATION
☑ Advertising ERC-5192 matters: a wallet that cannot detect the lock renders transfer controls that always revert
☑ Locked is emitted at every mint site, so an indexer classifying soulbound collections from logs alone sees the lock without probing every id
◇ locked reverts Unknown() on a non-existent id rather than returning false
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
    return interfaceId == 0x49064906 || interfaceId == 0xb45a3c0e || super.supportsInterface(interfaceId);
}

function locked(uint256 id) public view returns (bool) {
    _mustExist(id);
    return true;
}

Remaining View Functions

These are direct accessors with no logic worth expanding. All revert Unknown() on a
non-existent id where applicable.

FUNCTION SELECTOR RETURNS
total() 0x2ddbd13a Number of listings — currently 17
idAt(uint256 index) 0x4fe0d5c6 Id at an index in _ids, reverts Unknown() if out of range
isListed(uint256 id) 0xfcce4883 _position[id] != 0 — the membership test, does not revert
idFor(address token) 0xf90423fe The id an address actually resolves to, honouring reservation bindings
getExtra(uint256 id, bytes32 key) 0x6e798828 One extension value; empty string if unset
extraKeys(uint256 id) 0x973d0833 The key set on a listing, so a consumer can discover fields without knowing them
logoOf(uint256) / logoOf(address) 0xecbdd772 / 0x6ce273ac The logo URI alone, for cheap embedding in another card
themeOf(uint256) / themeOf(address) 0x0697b3ab / 0x7e6c8e24 Theme colour as both the raw uint24 and a #rrggbb string
renderer() / rendererLocked() 0x8ada6b0f / 0x6094a070 Auto-getters for slot 7
owner() 0x8da5cb5b Solady Ownable accessor
name() / symbol() 0x06fdde03 / 0x95d89b41 Constant "Token Listing" / "TKLIST" — both pure
balanceOf / ownerOf / getApproved / isApprovedForAll 0x70a08231 / 0x6352211e / 0x081812fc / 0xe985e9c5 Standard Solady ERC-721 accessors
ownershipHandoverExpiresAt(address) 0xfee81cf4 Expiry of a pending handover request

Note that themeOf returns the value as stored. The renderer applies a Rec. 601 luma check and
lightens colours below a threshold before painting, so what a card displays and what themeOf
reports can legitimately differ.