Two readers look at every tracked contract. The scorer feeds the score. The independent reader shares no code with it and answers one question: who can change this contract, and how many of them. Everything it does is a plain JSON-RPC call, listed here, so you can repeat it without trusting our code.
Set RPC to a public endpoint of the contract's chain, replace TARGET with the contract address and HOLDER with the address found in step 2 or 3 (left-padded words: the address is the last 20 bytes). Each contract page has these calls prefilled under "Reproduce this read yourself".
# 1. Is there code at the target? 0x means no: a native identifier, not a contract
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["TARGET","latest"]}'
# 2. The EIP-1967 admin slot of a proxy: a non-zero word is the admin (a key, a ProxyAdmin or a Safe)
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_getStorageAt","params":["TARGET","0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103","latest"]}'
# 3. Otherwise owner(): the last 20 bytes of the answer are the holder
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"TARGET","data":"0x8da5cb5b"},"latest"]}'
# 3b. Or admin()
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"TARGET","data":"0xf851a440"},"latest"]}'
# 4. Classify the holder. eth_getCode on it: 0x is a plain key
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["HOLDER","latest"]}'
# 5a. A Safe: how many signatures are needed
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"HOLDER","data":"0xe75235b8"},"latest"]}'
# 5b. A Safe: who can sign (the answer lists the owner addresses)
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"HOLDER","data":"0xa0e67e2b"},"latest"]}'
# 5c. A timelock: the minimum delay in seconds (OpenZeppelin)
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"HOLDER","data":"0xf27a0c92"},"latest"]}'
# 5d. A timelock: the delay in seconds (Compound style)
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"HOLDER","data":"0x6a42b8f8"},"latest"]}'
# 6. The code behind a proxy: a change in this word is an upgrade
curl -s -X POST $RPC -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_getStorageAt","params":["TARGET","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","latest"]}'
0x): one plain key. One signature moves the contract.getModulesPaginated, selector 0xcc2f8452), and a guard in the slot 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8.198 reads in the published file, by what the holder turned out to be (second_opinion.json):
| safe | 46 |
|---|---|
| unreadable | 38 |
| contract | 35 |
| timelock | 23 |
| squads | 17 |
| eoa | 16 |
| native | 16 |
| pda | 7 |
38 of the 198 reads are "unreadable": none of the steps above gave a holder. Known blind spots, found by checking twelve pages against the chain on 12 October 2026: contracts whose control sits behind governance(), gov(), feeToSetter() or upgradeAdmin() are not read by these steps, and an owner chain longer than two hops stops at the second. The scorer and the reader can both be wrong; a contract page says when they disagree.
If your own read differs from what a page shows, open an issue with your call and the answer: a disagreement is published, not smoothed.
Slots and selectors on this page are read from the reader's source at build. Not an audit, not investment advice. Back to the dashboard.