Skip to content

Contract Analysis

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

Analysis Date: 2026-08-08


Metadata

Primary Contract

PROPERTY VALUE
Contract Address 0x00000060...531524 (etherscan)
Network Ethereum Mainnet
Contract Type Standalone (non-upgradeable) ERC-721 registry
Contract Name TokenList
Deployment Date 2026-08-03 15:43:59 UTC
Deployment Block 25675344
Contract Creator 0xAcFBA7Ce...DE4082 (etherscan)
Creation TX 0xe5e00f96...21dd38 (tx)
Compiler Version Solidity v0.8.36+commit.8a079791, EVM prague, optimizer enabled (20 runs)
Runtime Size 24,243 bytes (333 bytes below the EIP-170 limit)
Total Functions 62 (public/external ABI entries)
External Contract Dependencies 2 categories — 1 renderer contract, plus a staticcall read of every listed token
Upgrade Mechanism ☒ None — no proxy, no delegatecall, no implementation slot. The renderer is replaceable; the registry is not
Verification Status ☑ Verified on Etherscan (exact match, standard-JSON input, 11 source files)
Audit Status △ No published audit report located. Source comments reference internal "audit fixes" and per-listing audit links, but no third-party report was found
TYPE ADDRESS NOTES
Owner (curator) 0x006CD14F...8549F2 (etherscan) 2-of-3 Multisig with a 1-hour execution delay. Holds every onlyOwner power
Multisig implementation 0xD54cb652...9f4FB0 (etherscan) The owner is a minimal proxy delegating here. Verified as Multisig
Multisig signer 1 0x1C0Aa8cC...855A20 (etherscan) z0r0z's canonical deployer Externally Owned Account (EOA). Broadcast 7 of the 8 transactions that reached the registry
Multisig signer 2 0x999657A4...B44E1C (etherscan) Plain EOA, no code. Queued two of the eight actions through the multisig, including the renderer swap
Multisig signer 3 0xCB059258...006aa2 (etherscan) Plain EOA, no code. No observed activity against this registry
Timelock executor 0x00000000...c3973F (etherscan) TimelockExecutor singleton. Bypasses the 1-hour delay on unanimous (3-of-3) signatures. Enabled for this multisig
Renderer (active) 0x00000096...1eDcF4 (etherscan) TokenListRenderer, set 2026-08-05. Authors tokenURI, json and contractURI
Renderer (original) 0x000000d5...DbbE87 (etherscan) Constructor-supplied renderer, superseded. Deployed directly by z0r0z's EOA
Deployment factory 0x00000000...4e6f2a (etherscan) CREATE2 vanity factory used for the registry and both renderers

Executive Summary

TokenList is an on-chain token registry. A listing here is not a row in a mapping that happens
to be readable. It is an ERC-721 token, and that NFT is minted to the address of the token being
described. The listing for WETH is held by the WETH contract
itself; anyone inspecting WETH on a block explorer or in an NFT-aware wallet sees a card carrying
its logo, symbol, decimals and links. Minting is listing, burning is delisting.

The intended audience is tooling rather than end users: wallets, swap frontends, indexers and
other contracts that need a canonical answer to "what is this token, and what should it look
like". The registry ships read paths shaped for that: a compact JSON form, a ranked id list, a
summary page that omits the multi-kilobyte logo field, and Solady's Multicallable so a
consumer can batch reads for exactly the rows on screen.

The contract splits facts from presentation. For a token
deployed on this chain, name, symbol and decimals are never accepted as arguments. They are
read out of the token contract by gas-capped staticcall through Solady's MetadataReaderLib,
and the sync(uint256) function that re-reads them is permissionless. The curator cannot type a
name for a local token even if they want to. What the curator does author is everything with no on-chain
source: logo, theme colour, sort weight, project and audit links, description, and an open-ended
bytes32 => string extension mapping. Foreign listings (other chains, Bitcoin-rooted assets, and
the native asset at address(0)) have no readable source here, so their text is curator-supplied
and stored with synced = false so consumers can render the distinction.

Trust concentrates in two places. The first is the owner: a 2-of-3 multisig
(0x006CD14F...8549F2) whose signers include z0r0z's canonical EOA, carrying a 1-hour execution
delay that a unanimous 3-of-3 signature set can bypass through an enabled TimelockExecutor. It
alone can list, delist, re-rank and re-skin. The second is the renderer. tokenURI, json and contractURI all forward to a
replaceable contract, and a renderer is free to print any text it likes regardless of what
storage holds. Storage-level provenance is real; display-level provenance is only as good as the
current renderer. That power has already been exercised once: the renderer was swapped on
2026-08-05, two days after deployment.

The risks here concern curation, not funds. The contract holds no balances, charges no fee and
has no withdrawal path. The realistic failure modes are a compromised or
careless curator attaching a legitimate-looking card to a malicious token, a renderer swap that
misrepresents every listing at once, and the inherited renounceOwnership path, which would
freeze the list permanently, including the ability to remove a listing whose project later turns
hostile.

As of this analysis the registry is five days old, holds 17 listings, and has never had a listing
delisted, frozen, reserved, or activated.


Architecture

graph TB
    subgraph Governance["Governance"]
        Z["z0r0z EOA<br/>0x1C0Aa8cC...855A20<br/>(EIP-7702 delegated)"]
        S2["Signer 2<br/>0x999657A4...B44E1C"]
        S3["Signer 3<br/>0xCB059258...006aa2"]
        MS["Multisig (owner)<br/>0x006CD14F...8549F2<br/>2-of-3, delay 3600s"]
        TLE["TimelockExecutor<br/>0x00000000...c3973F<br/>3-of-3 = instant"]
    end

    subgraph Registry["Registry (immutable)"]
        TL["TokenList<br/>0x00000060...531524<br/>ERC-721 + Ownable + Multicallable"]
        ST["_tokens / _ids / _position<br/>_boundLocalId / _extra*"]
    end

    subgraph Presentation["Presentation (replaceable)"]
        R["TokenListRenderer<br/>0x00000096...1eDcF4<br/>pure, stateless, ownerless"]
        RO["Renderer v1 (superseded)<br/>0x000000d5...dbbe87"]
    end

    subgraph Subjects["Listed subjects"]
        T1["WETH, USDC, USDT, DAI<br/>wstETH, stETH, rETH, WBTC<br/>BOLD, LUSD, ZORG, ZAMM, FWA"]
        T2["zOrgz, WNS<br/>(ERC-721 collections)"]
        T3["Native ETH (id 0)<br/>Tacit Coin (Bitcoin-rooted)"]
    end

    Z --> MS
    S2 --> MS
    S3 --> MS
    Z --> TLE
    TLE -->|executor bypass| MS
    MS -->|onlyOwner calls| TL
    TL --- ST
    TL -->|tokenURI / json / contractURI| R
    RO -.->|replaced 2026-08-05| R
    TL -->|"staticcall name/symbol/decimals<br/>60k gas cap"| T1
    TL -->|staticcall + ERC-165 probe| T2
    TL -->|listing NFT minted to token| T1
    TL -->|listing NFT minted to token| T2
    TL -->|listing NFT held by registry| T3
    Anyone(["Anyone"]) -->|"sync(id) — permissionless"| TL
    Consumers(["Wallets / dapps / indexers"]) -->|read| TL

    style TL fill:#e1f0ff
    style MS fill:#ffe1e1
    style R fill:#fff4e1

System Overview

The registry keys every listing by a deterministic id. For a token on this chain the id is
literally its address cast to uint256, so a consumer that knows the address knows the id without
a lookup. Everything else — other chains, Solana mints, Bitcoin-rooted assets, and reservations
for tokens not yet deployed — is keyed by keccak256 over its namespace tuple with bit 255 set,
which keeps the two id spaces disjoint by construction.

  • Stores a curated set of token identities and renders each one as a self-contained SVG card and
    compact JSON, entirely on-chain, with no IPFS or HTTP dependency for the card itself
  • Reads name, symbol, decimals and the ERC-165 standard directly from local token contracts,
    and refuses to let the owner author those fields for a local listing
  • Marks provenance explicitly: synced = true only on listings whose text came from an on-chain
    read, and consumers get that flag in get, summariesPaged and the JSON v field
  • Makes every listing soulbound (ERC-5192) — the card cannot drift away from its subject
  • Does not validate that a listed token is safe, solvent, or non-malicious. A listing is a
    curatorial statement about identity and appearance, nothing more
  • Does not protect the displayed text from the owner. setRenderer remains open, and a
    renderer can print anything regardless of storage
  • Does not hold funds, and has no receive() or withdrawal path — ETH sent to any of its
    inherited payable functions is unrecoverable

Design Patterns Used

  • Soulbound NFT (ERC-5192): _beforeTokenTransfer reverts Soulbound() whenever both from and
    to are non-zero, so mint and burn are the only transitions. locked(id) returns true for
    every existing listing, and Locked is emitted at every mint site
  • Subject-as-holder: the listing NFT is minted to the token contract it describes via _mint
    rather than _safeMint, deliberately — the subject has no receiver hook and must not be able
    to refuse the card. Listings with no local subject (native ETH, foreign assets, reservations)
    are held by the registry itself
  • Derived-fact / authored-presentation split: _pull writes name/symbol/decimals/standard
    from the token; _setArt and the set* family write only fields with no on-chain source
  • Renderer indirection: all three metadata reads forward to an external pure contract, keeping
    the immutable registry under EIP-170 while leaving the card improvable. contractURI forwards
    in hand-written assembly to avoid the decode/re-encode round trip
  • Sparse sort weights: rank is a weight, not a position, and the seeded values step by 1,000 so
    a listing can be inserted between two others without renumbering. Note that rank is the
    registry's ordering, not the only one in circulation — see
    ZorgConviction, which scores the same listing ids by
    bonded stake and reads this contract only through isListed
  • Escape-hatch extension mapping: a bounded (32-key) bytes32 => string store per listing, so a
    new metadata field costs a transaction rather than a redeployment and a re-listing
  • Defence-in-depth sanitisation: _clean strips the six characters that break out of a JSON
    string or an SVG attribute on the way into storage, and the renderer's _safe applies the same
    filter on the way out, so a caller other than the registry cannot induce malformed output

Access Control

Roles & Permissions

ROLE ASSIGNED BY REVOKABLE CALL COUNT
Owner (curator) Constructor (_initializeOwner) Yes — transferOwnership, renounceOwnership, or the two-step Solady handover Unlimited
Anyone Implicit N/A Unlimited — sync(id) and all view functions
Listing holder (token contract) _mint at list time No Zero — every transfer path reverts Soulbound()

The owner is not an EOA. It is a minimal proxy to a Multisig implementation configured with
threshold = 2, ownerCount = 3 and delay = 3600. A normal 2-of-3 execution queues the call
and becomes executable one hour later. The TimelockExecutor at 0x00000000...c3973F is
registered as the multisig's executor, and forwardEnabled is true for this multisig, so a
unanimous 3-of-3 signature set executes immediately with no delay. Threshold-signed cancellation
of a queued call is always available through the same executor.

Permission Matrix

FUNCTION OWNER LISTING HOLDER ANYONE
list / listForeign / reserve ☑ ☒ ☒
activateReserved ☑ ☒ ☒
delist ☑ ☒ ☒
setArt / setLogoSVG / setRank ☑ ☒ ☒
setAudit / setExtra ☑ ☒ ☒
setStandard / setOnchainSvg / setForeignText ☑ ☒ ☒
freeze ☑ ☒ ☒
setRenderer / lockRenderer ☑ ☒ ☒
transferOwnership / renounceOwnership ☑ ☒ ☒
sync ☑ ☑ ☑
get / json / tokenURI / search / all reads ☑ ☑ ☑
transferFrom / safeTransferFrom ☒ ☒ ☒

sync is permissionless by design — refreshing a fact from its own
source is not treated as a privilege, which means the owner can never be the reason a stale symbol
persists. And no role, including the owner, can transfer a listing; the soulbound check is applied
in _beforeTokenTransfer and has no bypass.

Time Locks & Delays

ACTION TIME LOCK CAN CANCEL PURPOSE
Any onlyOwner call, 2-of-3 path Yes — 3,600 s queued in the multisig Yes — threshold-signed cancelQueued via the executor ☑ One-hour public warning before curation changes take effect
Any onlyOwner call, 3-of-3 path ☒ None — TimelockExecutor.forward executes immediately No △ Unanimous signers can act with no delay; forwardEnabled is currently true
freeze(id) None at the registry level No — irreversible ☑ Permanently seals one listing's owner-authored fields
lockRenderer() None at the registry level No — irreversible ☑ Permanently removes the ability to change what cards display. Not yet called
renounceOwnership() None at the registry level No — irreversible ☒ Would permanently end all curation, including the ability to delist a hostile project

Economic Model

This contract does not handle funds or implement economic mechanics. It holds no token balances,
charges no fee, and has no withdrawal, sweep, or receive() function. Its ETH balance at the time
of analysis is 0.

Ten functions are declared payable through inheritance: approve, transferFrom, both
safeTransferFrom overloads, transferOwnership, renounceOwnership,
requestOwnershipHandover, cancelOwnershipHandover, completeOwnershipHandover, and
multicall. Nine of those ten will accept ETH that can never be retrieved, since no code path
moves ETH out of the contract. multicall is the exception — Solady's implementation opens with
if (msg.value != 0) revert(), so it rejects value outright. Triggering the trap requires caller
error, no such transaction has occurred, and the balance is 0, but there is no recovery path.


Summary of Observations

TokenList is a curated, on-chain token registry that models each listing as a soulbound ERC-721
minted to the token it describes. What the code enforces is narrow and specific: the identity
fields of a Token List cannot be authored by the curator, while everything aesthetic must be
authored by someone.

That goal is met at the storage layer, and the mechanism is straightforward to verify. list
takes no name, symbol or decimals parameter. _pull obtains them by gas-capped staticcall
through MetadataReaderLib, sets synced = true, and setForeignText — the only function that
can write those fields directly — reverts NotSyncable() on any listing where synced is true.
sync is callable by anyone. For the 15 local listings currently present — 13 ERC-20 tokens
and 2 ERC-721 collections — the displayed name and symbol came from the token contract
rather than from the curator.

Three qualifications follow. _pull sets synced = true unconditionally at
the end, even when every read returned empty; an empty read is treated as "no answer" and leaves
the stored value untouched, so a listing can legitimately read synced = true while carrying text
from an earlier successful read, or no text at all. Second, the provenance guarantee is about
identity fields only — the logo, description, links, colour and rank on every card are curator
statements with no on-chain backing. Third, and most consequentially, the guarantee covers storage. Display is a
separate matter. tokenURI, json and contractURI all forward to a replaceable renderer,
and a renderer may print whatever it likes. lockRenderer exists to close that gap permanently
and has not been called. The renderer has already been replaced once, on 2026-08-05; comparing the
two verified sources shows that change to be purely presentational (a Bitcoin chain label, an
initials fallback when a listing has no logo, restyled provenance chips, numeric trait types), but
nothing in the contract constrains a future swap to be equally benign.

On governance, the owner is a 2-of-3 Multisig rather than a bare EOA, and it carries
a one-hour delay on the ordinary path. The delay is bypassable by unanimous signature through an
executor module that is currently enabled, so the effective worst case is that all three signers
acting together can change any listing instantly.

Reconstructing who actually drives that governance requires reading the multisig's own transaction
history, not the registry's event log. The registry only ever sees the second half of a two-step
process: an execute call queues an action, and a later executeQueued applies it. z0r0z's
canonical EOA 0x1C0Aa8cC...855A20 (etherscan)
broadcast 7 of the 8 transactions that reached the registry — the eighth is the deployment itself,
sent by the funded deployer EOA. But signer 2 0x999657A4...B44E1C (etherscan)
queued two of those eight actions, the wstETH listing and the renderer swap, each force-executed
by z0r0z minutes later through the unanimous bypass. Signer 2 is an active proposer rather than a
passive co-signer, and the renderer swap in particular did not originate with z0r0z.

The attribution to z0r0z and the zFi stack still rests on direct on-chain evidence rather than
inference from naming: the same EOA funded the deployer four minutes before deployment and
deployed the original renderer itself.

On funds, there is nothing to observe. The contract never holds or moves value. The only economic
note is the set of inherited payable functions with no withdrawal path, which makes accidentally
attached ETH unrecoverable.

Patterns that appear deliberate and self-consistent:

  • ☑ No proxy, no delegatecall, and no upgrade path for the registry itself. The identity rule
    consumers are expected to cache cannot be changed out from under them.
  • ☑ A separation between derived facts and authored presentation that can be checked directly:
    list accepts no identity parameters, _pull reads them from the subject, and
    setForeignText refuses any listing already flagged synced.
  • ☑ Refresh is permissionless. sync is callable by anyone, so the curator is never the reason a
    stale symbol persists, and it is exempt from the freeze gate because it copies a fact rather
    than authoring one.
  • ☑ Soulbound Token listings, applied in _beforeTokenTransfer with no bypass, so a card
    cannot drift away from the subject it describes.
  • ☑ The same character filter applied on both the write side (_clean) and the render side
    (_safe), so a renderer handed hostile input by some other caller still emits well-formed
    output.
  • ☑ Bounded loops in the paths that matter, including a 32-key cap on extensions specifically so
    that delist always fits in a block.
  • ☑ No whole-struct paginated reads, which would exceed eth_call limits once 24 KB logos are
    counted. summariesPaged omits the unbounded fields by design.

Trade-offs and trust assumptions:

  • △ The renderer remains swappable, so the strongest claim the contract makes about identity is
    defeasible by the owner at the display layer. lockRenderer would close this permanently and
    has not been called.
  • △ renounceOwnership is inherited and open. In a registry, ownerless means uncorrectable —
    including the inability to remove a listing whose project later turns hostile.
  • △ The contract sits 333 bytes below the EIP-170 limit with no upgrade path, so a fix
    requiring more space would mean redeploying under fresh ids and migrating every consumer.
  • △ No third-party audit report was located, despite source comments referring to audit fixes.
  • ◇ At five days old with 17 listings, the registry has no operating history against adversarial
    conditions. Reservation, activation, delisting and freezing have never been exercised on
    mainnet.

Three questions remain open for the team: whether lockRenderer will be called once the card
design settles, whether forwardEnabled is meant to stay true given it makes the one-hour delay
optional, and what the curation policy is for adding a listing.

This analysis was performed for educational purposes. It is not an official security audit and it
is not financial advice. Verify the claims here yourself using the commands in
Methodology, and form your own view.


References

RESOURCE NOTES
Etherscan — TokenList Verified source, ABI, bytecode, event logs
Etherscan — TokenListRenderer (active) Verified source of the renderer currently authoring every card
Etherscan — TokenListRenderer (original) Constructor-supplied renderer, used to diff what the swap actually changed
Etherscan — Multisig implementation Verified source behind the owner's minimal proxy; threshold, delay, executor semantics
Etherscan — TimelockExecutor Verified source establishing that the delay bypass requires unanimous signatures
Solady ERC721, Ownable, Multicallable, MetadataReaderLib, LibSort, LibString, Base64, Base58 — the nine library files bundled in the verified source
EIP-5192: Minimal Soulbound NFTs The locked(uint256) interface and 0xb45a3c0e interface id advertised by the contract
EIP-4906: Metadata Update Extension MetadataUpdate / BatchMetadataUpdate semantics used to invalidate cached cards
ERC-7572: Contract-level metadata contractURI() collection metadata forwarded to the renderer
EIP-170: Contract code size limit The 24,576-byte ceiling that motivated splitting the renderer out of the registry
EIP-1153: Transient storage Context for the prague EVM target
Foundry Cast reference Command syntax for all on-chain verification in this analysis
z0r0z entity profile DNZN's prior research on the developer and deployment patterns matched here
zFi Project Overview Project context for the zFi stack this registry serves
Tacit Project Overview Background on the Bitcoin-rooted asset listed as Standard.TACIT

Change Log

DATE AUTHOR NOTES
2026-08-08 Artificial. Generated by robots. Gas: 1.5 mtok
2026-08-08 Denizen. Reviewed, edited, and curated by humans.