Methodology
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 |
Overview
The contract is verified on Etherscan with an exact source match, so this analysis worked from Solidity rather than from decompiled bytecode. That removes the usual guesswork about function names and control flow, but it introduces a different discipline: the source carries extensive NatSpec, including narrative explanations of past bugs and design reversals. Per the analysis standard, those comments were treated as leads to verify, never as evidence in themselves. Every behavioural claim here traces to executable code or an on-chain read.
Work proceeded in five phases. Phase 0 fetched the verified source bundle and split its eleven files to disk, then established deployment facts — creator, factory, block, compiler settings and runtime size. Phase 1 identified the contract as a standalone, non-upgradeable ERC-721 registry rather than a proxy, mapped its two-contract architecture, and enumerated the full 62-function Application Binary Interface (ABI) with computed selectors. Phase 2 read every function against its storage effects and verified the declared layout against live storage, including decoding the packed Token struct word byte by byte. Phase 3 traced the governance chain upward from the owner address — through a minimal proxy, a Multisig implementation, and a TimelockExecutor module — because the owner turned out not to be an Externally Owned Account (EOA). Phase 4 produced this documentation set.
Reading past the immediate contract surfaced two findings. The owner is a 2-of-3 multisig with a one-hour delay that a unanimous signature set can bypass, which required reading two additional verified contracts to establish. And the live renderer address differs from the one passed to the constructor, meaning setRenderer had already been exercised; both renderer sources were fetched and diffed to determine what that swap changed.
Governance attribution needed a third source. The registry's event log shows only the transactions that reached it, which is the second half of a two-step timelock, so the multisig's own transaction history was read separately to establish who proposed each action rather than who broadcast it. Those answers differ here: a second signer queued two of the eight actions, including the renderer swap.
Thought Process
mindmap
root((TokenList Analysis))
Phase 0 Obtain
Etherscan getsourcecode
Verified exact match
11 files split to disk
Standard-JSON settings
Deployment facts
Block 25675344
CREATE2 vanity factory
Runtime 24243 bytes
333 B under EIP-170
Phase 1 Discovery
Contract type
Not a proxy
No delegatecall
No implementation slot
Standards
ERC-721 soulbound
ERC-5192 locked
ERC-4906 metadata
ERC-7572 contractURI
Surface
62 ABI entries
Selectors via cast sig
19 admin 9 user 34 view
Phase 2 Deep Dive
Provenance mechanism
list takes no name param
pull reads via staticcall
setForeignText blocked when synced
sync is permissionless
Id derivation
Local id equals address
Foreign id keccak plus bit 255
Reservation domain tag
Storage verification
Slots 0 to 7 read live
Token struct 8 slots
Packed word decoded
Solady manual slots
Phase 3 Risk and Trust
Governance chain
Owner is minimal proxy
Multisig 2 of 3
Delay 3600 seconds
TimelockExecutor bypass
Unanimous required
Renderer mutability
Live differs from ctor arg
Diffed both sources
Swap was presentational
lockRenderer never called
Fund surface
No receive no withdraw
Payable inherited functions
Balance zero
Phase 4 Documentation
Six output files
Verification commands
Cross-links to zFi and Tacit
Phase 5 Verification
Facts re-derived from chain
Standards and links
Cross-document consistency
Voice and filler
Verification Guide
All on-chain values quoted in this analysis were read from Ethereum mainnet using Foundry's cast against an archive-capable RPC endpoint, and all historical events were pulled from the Etherscan v2 API. Every command below is reproducible.
External Resources
| RESOURCE | WHAT IT PROVIDED |
|---|---|
| Etherscan — TokenList | Verified source bundle, ABI, compiler settings, creation transaction, complete event log |
| Etherscan — TokenListRenderer (active) | Verified source of the renderer authoring every card since 2026-08-05 |
| Etherscan — TokenListRenderer (original) | Verified source of the constructor-supplied renderer, used as the diff baseline |
| Etherscan — Multisig implementation | Execution model behind the owner's minimal proxy: threshold, delay, executor bypass semantics |
| Etherscan — TimelockExecutor | Established that the delay bypass requires unanimous rather than threshold signatures |
| Etherscan API v2 | getsourcecode, getcontractcreation, getLogs, txlist endpoints |
| Solady | Reference for ERC721, Ownable, Multicallable, MetadataReaderLib, LibSort, LibString, Base64, Base58, LibBytes behaviour and manual storage slot placement |
| Foundry Cast reference | Command syntax for storage reads, slot derivation, selector and topic computation |
| EIP-5192 / EIP-4906 / ERC-7572 | Interface semantics the contract advertises |
| EIP-170 | The 24,576-byte code size ceiling that shaped the two-contract split |
Commandline Tools
Tip
Commands below use cast from the Foundry Toolkit. To run the commands below, you must set the RPC URL environment variable:
Establish Contract Identity and Size
Confirms the contract is deployed, measures it against the EIP-170 ceiling, and shows it holds no funds. The nonce of 1 reflects the contract having performed no CREATE of its own.
# DEPLOYED RUNTIME SIZE IN BYTES - COMPARE AGAINST THE 24,576 EIP-170 LIMIT
cast codesize 0x0000006013dF75A31678B786061C2B54bf531524
# ETH HELD BY THE REGISTRY - EXPECTED TO BE ZERO
cast balance 0x0000006013dF75A31678B786061C2B54bf531524
Read Registry Configuration
The renderer returned here is the value to compare against the constructor argument. A mismatch means setRenderer has been called.
# CURATOR ADDRESS - RESOLVES TO A MULTISIG, NOT AN EOA
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "owner()(address)"
# ACTIVE RENDERER AND WHETHER THAT CHOICE HAS BEEN SEALED
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "renderer()(address)"
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "rendererLocked()(bool)"
# LISTING COUNT AND COLLECTION IDENTITY
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "total()(uint256)"
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "name()(string)"
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "symbol()(string)"
Enumerate Listings
summariesPaged is the read designed for this — it returns every scalar field plus name and symbol while omitting the logo, so the response stays a predictable size.
# EVERY LISTING ID, HIGHEST SORT WEIGHT FIRST
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "rankedIds()(uint256[])"
# ALL LISTINGS WITHOUT THE UNBOUNDED FIELDS
cast call 0x0000006013dF75A31678B786061C2B54bf531524 \
"summariesPaged(uint256,uint256)((uint256,bytes32,uint64,uint8,uint8,uint8,bool,bool,bool,uint24,uint32,bool,string,string)[])" \
0 30
Verify the Provenance Claim
The core assertion of this contract is that a local token's identity fields come from the token, not from the curator. Two checks establish it. First, list has no name parameter — visible in the ABI. Second, the only function that can write those fields directly refuses to run on a synced listing.
# THE WETH LISTING - NOTE synced = true AND THAT NAME MATCHES WETH's OWN name()
cast call 0x0000006013dF75A31678B786061C2B54bf531524 \
"get(address)((bytes32,uint64,uint8,uint8,uint8,bool,bool,bool,uint24,uint32,bool,string,string,string,string,string,string))" \
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
# THE SOURCE OF TRUTH - READ DIRECTLY FROM THE TOKEN CONTRACT
cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 "name()(string)"
cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 "symbol()(string)"
cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 "decimals()(uint8)"
Verify Soulbound Behaviour
The listing NFT for a local token is held by the token contract itself, and cannot move.
# THE WETH LISTING IS HELD BY WETH - ID IS LITERALLY THE ADDRESS AS A uint256
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "ownerOf(uint256)(address)" \
1097077688018008265106216665536940668749033598146
# THE REGISTRY HOLDS ONLY THE CARDS WITH NO LOCAL SUBJECT - NATIVE ETH AND THE TACIT ASSET
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "balanceOf(address)(uint256)" \
0x0000006013dF75A31678B786061C2B54bf531524
# ERC-5192 LOCK STATUS AND INTERFACE ADVERTISEMENT
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "locked(uint256)(bool)" 0
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "supportsInterface(bytes4)(bool)" 0xb45a3c0e
Verify Storage Layout
Slot 4 holds the listing count directly. Slot 7 packs the renderer address with the lock flag. The Token struct occupies eight consecutive slots at a derived base, and the second of those carries ten packed scalars.
# SEQUENTIAL SLOTS 0-7 AS DECLARED IN TokenList
for i in 0 1 2 3 4 5 6 7; do
cast storage 0x0000006013dF75A31678B786061C2B54bf531524 $i
done
# SOLADY OWNABLE USES A MANUALLY PLACED HIGH SLOT, NOT SLOT 0
cast storage 0x0000006013dF75A31678B786061C2B54bf531524 \
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927
# BASE SLOT OF THE NATIVE ETH LISTING - keccak256(abi.encode(0, 3))
cast index uint256 0 3
# MEMBERSHIP TEST AND ADDRESS BINDING
cast storage 0x0000006013dF75A31678B786061C2B54bf531524 $(cast index uint256 0 5)
cast storage 0x0000006013dF75A31678B786061C2B54bf531524 \
$(cast index address 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 6)
Trace the Governance Chain
The owner address has 45 bytes of code, which is a minimal proxy rather than a wallet. Following it to its implementation reveals the actual execution model.
# THE OWNER IS A CONTRACT - 45 BYTES IS A MINIMAL PROXY
cast codesize 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2
cast code 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2
# MULTISIG CONFIGURATION - SIGNERS, THRESHOLD, DELAY, AND EXECUTOR
cast call 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2 "getOwners()(address[])"
cast call 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2 "threshold()(uint16)"
cast call 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2 "delay()(uint32)"
cast call 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2 "executor()(address)"
# WHETHER THE TIMELOCK BYPASS IS ENABLED FOR THIS MULTISIG
cast call 0x00000000a72A30AdBf38e14d36BCE2610ec3973F "forwardEnabled(address)(bool)" \
0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2
Reconstruct the Curation History
The registry has no direct transactions — every owner call arrives as an internal call from the multisig — so the history has to be read from event logs rather than from txlist.
# EVENT TOPIC HASHES FOR DECODING THE LOG
cast keccak "Listed(uint256,bytes32,uint64,string,string,uint8,bool)"
cast keccak "Updated(uint256,bytes32)"
cast keccak "RendererSet(address)"
cast keccak "Delisted(uint256,bytes32)"
cast keccak "Froze(uint256)"
# FULL EVENT HISTORY VIA THE ETHERSCAN V2 API
curl -s "https://api.etherscan.io/v2/api?chainid=1&module=logs&action=getLogs\
&address=0x0000006013dF75A31678B786061C2B54bf531524\
&fromBlock=0&toBlock=latest&page=1&offset=1000&apikey=$ETHERSCAN_API_KEY"
Render a Card End to End
Confirms the renderer path works and lets the produced SVG and attribute set be inspected directly.
# FULL ERC-721 METADATA DOCUMENT - BASE64 JSON WITH AN EMBEDDED BASE64 SVG
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "tokenURI(uint256)(string)" \
3718177199436581777268575498026466751093263122
# THE COMPACT MACHINE-READABLE FORM CONSUMERS ARE MEANT TO BATCH
cast call 0x0000006013dF75A31678B786061C2B54bf531524 "json(uint256)(string)" \
104165018710067097353655755692819801489527232022561016148205125677286991358696
Document Verification
The drafted documents were re-checked before publication in five independent passes, each with a narrow scope and each reading from primary sources rather than from the draft. Running them separately matters: a claim can be correctly formatted, non-redundant, neutral in tone, free of filler, and still be factually wrong, and a single reviewer holding all five concerns at once reliably drops the last one.
| DIMENSION | WHAT IT CHECKS | CHECKED AGAINST |
|---|---|---|
| Facts | Addresses, block heights, listing counts, dates, compiler version, selector values, every quantitative claim | Live chain state via cast, Etherscan API v2, the verified source bundle for the registry and both renderers |
| Standards | Address display format, explorer links on every on-chain reference, table casing, section separators, icon semantics, glossary term wrapping, nav registration | CLAUDE.md, docs/glossary.md, mkdocs.yml |
| Redundancy | Duplicated tables and paragraphs across the six documents, and the same claim stated with different values in two places | The document set itself |
| Voice | Superlatives asserted without a verification clause, opinion adjectives, speculation about intent or users | CLAUDE.md writing standards |
| Filler | Rhetorical padding — meta-narration, telegraphed structure, formulaic emphasis | CLAUDE.md anti-patterns |
Every disagreement the fact pass raised was re-derived from chain or source before any edit was made, on the assumption that the checker is as capable of being wrong as the draft. A claim was only changed where the contradicting value could be reproduced from a command in the Verification Guide above.
The governance section is where that discipline mattered most. Attribution read cleanly from the registry's own event log and was still incomplete, because that log records only the executing half of a two-step timelock — the multisig's txlist had to be read separately to establish who proposed each action.
Token Cost Breakdown
| PHASE | DESCRIPTION | TOKENS |
|---|---|---|
| Phase 0 | Obtain the Contract | 15 tok |
| Phase 1 | Discovery & Understanding | 20 tok |
| Phase 2 | Deep Dive Analysis | 30 tok |
| Phase 3 | Risk & Trust Analysis | 20 tok |
| Phase 4 | Documentation Generation | 35 tok |
| Phase 5 | Document Verification | 1.4 mtok |
| TOTAL | Complete Contract Analysis | 1.5 mtok |
Note: Token costs are estimates based on typical conversation lengths and complexity. Actual consumption may vary by ±10-15% depending on API responses, iterative refinement, and verification steps.
Phase 5 dominates the total by roughly forty to one, and the fact-check pass accounts for nearly all of it. Every quantitative claim was re-derived from chain state or source rather than carried forward from the drafting phase, so most on-chain reads in this analysis were performed at least twice, independently. That ratio is the intended shape of the work — producing a plausible document is cheap, and establishing that it is correct is not.