Verifying a Safe’s configuration in one read
The Gnosis Safe Inspector reads any Safe{Wallet} (formerly Gnosis Safe) multisig and returns its owners, signature threshold, nonce, and contract version from one expression. It is built for developers, integrators, and anyone who wants to verify a Safe’s configuration before sending funds to it or building around it, without ABIs or a block explorer tab.
Key facts
- Reads a Safe{Wallet} (Gnosis Safe) multisig’s owners, signature threshold, nonce, and contract version from
getOwners(),getThreshold(),nonce(), andVERSION(), all in one call. - Automatically detects that a Safe address is a minimal proxy and dispatches reads to the shared singleton implementation, no manual ABI lookup needed.
- The owner list has no pagination cap; Safe owner sets are small in practice, so every owner returns in one response.
- Covers Ethereum, Base, BNB Smart Chain, and Polygon today.
How to use it
- Paste the Safe address into the form above.
- Pick the chain the Safe is deployed on.
- Run the query and read the signature threshold, nonce, version, and full owner list in the result panel.
The result panel shows the threshold as “N of M” (N signatures required out of M owners), the current nonce, the Safe contract version, and every owner address stacked below.
The proxy factory, the singleton, and why every Safe is thin
Every Safe you interact with, the one holding a DAO treasury, the one behind a team’s multisig, the one your own wallet created, is a minimal proxy deployed by Safe’s SafeProxyFactory (or its predecessor, GnosisSafeProxyFactory). The proxy itself carries almost no logic: a constructor, a fallback that delegatecalls to a shared singleton implementation contract, and the storage slots that make each Safe’s owners, threshold, and nonce its own even though the code executing against that storage is shared across every Safe on the chain. The factory deploys these proxies with CREATE2, so a Safe’s address is deterministic from its owners, threshold, and a salt nonce chosen at creation, which is how tools can predict a Safe’s address before it is deployed and why the same owner set can produce the same Safe address on multiple chains. This inspector detects that proxy pattern automatically and dispatches every read to the correct singleton, the same detection the Proxy Contract Detector runs when it classifies an address as gnosis-safe rather than an EIP-1967 or UUPS pattern.
Owners, threshold, and nonce, in practice
The owner list and threshold are not independent settings someone can silently change; changing either requires a Safe transaction signed by enough of the current owners to meet the current threshold, so a Safe cannot be taken over by adding an owner without the existing owners’ consent. The nonce is the piece that actually stops a signed transaction from running twice: every Safe transaction is signed over a hash that includes the Safe’s nonce at signing time, so replaying an old signed payload after the nonce has advanced produces a hash mismatch and the Safe rejects it. Reading the current nonce before you sign is a real check, not a formality: it tells you whether the transaction you are about to approve is still next in line.
Version matters more than it looks. VERSION() on a 1.1.1 Safe and a 1.4.1 Safe are answering the same question, but the two versions differ on real behavior: 1.3.0 introduced the fallbackHandler slot that lets a Safe support token-receiver callbacks (onERC721Received, onERC1155Received) it could not handle before, and 1.4.1 changed how the Safe guards against a specific signature-replay edge case across chains. Reading the version before you build tooling that assumes a specific method exists is cheaper than finding out it doesn’t at call time.
The dyn() escape hatch for mixed-type maps
{ "owners": dyn(safe.getOwners()), "ownerCount": dyn(safe.getOwners().size()), "threshold": dyn(string(formatUnits(safe.getThreshold(), 0))), "nonce": dyn(string(formatUnits(safe.nonce(), 0))), "version": dyn(safe.VERSION())}CEL map literals normally require every value in the map to share exactly one type. This expression needs to return an address array (owners), scalar integers (ownerCount, threshold, nonce), and a plain string (version) in a single response, which a strictly-typed map can’t hold at once. Wrapping every field in dyn(...) escapes that restriction and lets the map mix types.
That escape comes with a shape to know about: dyn() applied to a sol_int or a sol_address / list<sol_address> wraps each value as {"value": "<string>"}, while dyn() applied to a plain CEL string (like VERSION(), which the Safe contract already returns as a string) passes through unwrapped. A real response looks like:
{ "owners": [{ "value": "0xe4df...ffed9" }, { "value": "0xa1cf..." }], "ownerCount": { "value": "5" }, "threshold": { "value": "3" }, "nonce": { "value": "8" }, "version": "1.3.0"}So callers should read .value off owners, ownerCount, threshold, and nonce, but read version directly as a string.
Read a Safe’s configuration yourself
Each of the snippets below makes the same POST call to the evmquery REST API. The example targets GnosisDAO’s Safe on Ethereum.
REST (curl)
curl -X POST https://api.evmquery.com/api/v1/query \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "chain": "evm_ethereum", "schema": { "contracts": { "safe": { "address": "0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe" } } }, "expression": "{ \"owners\": dyn(safe.getOwners()), \"ownerCount\": dyn(safe.getOwners().size()), \"threshold\": dyn(string(formatUnits(safe.getThreshold(), 0))), \"nonce\": dyn(string(formatUnits(safe.nonce(), 0))), \"version\": dyn(safe.VERSION()) }" }'Python
import requests
resp = requests.post( "https://api.evmquery.com/api/v1/query", headers={"x-api-key": "YOUR_API_KEY"}, json={ "chain": "evm_ethereum", "schema": { "contracts": {"safe": {"address": "0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe"}}, }, "expression": ( '{ "owners": dyn(safe.getOwners()),' ' "ownerCount": dyn(safe.getOwners().size()),' ' "threshold": dyn(string(formatUnits(safe.getThreshold(), 0))),' ' "nonce": dyn(string(formatUnits(safe.nonce(), 0))),' ' "version": dyn(safe.VERSION()) }' ), }, timeout=10,)print(resp.json()["result"])TypeScript
const resp = await fetch("https://api.evmquery.com/api/v1/query", { method: "POST", headers: { "x-api-key": process.env.EVMQUERY_API_KEY ?? "", "Content-Type": "application/json", }, body: JSON.stringify({ chain: "evm_ethereum", schema: { contracts: { safe: { address: "0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe" } }, }, expression: '{ "owners": dyn(safe.getOwners()), "ownerCount": dyn(safe.getOwners().size()), "threshold": dyn(string(formatUnits(safe.getThreshold(), 0))), "nonce": dyn(string(formatUnits(safe.nonce(), 0))), "version": dyn(safe.VERSION()) }', }),});const { result } = await resp.json();The free tier has no monthly cap. Get a free API key to drop these snippets into your project.
Where owner and threshold checks actually matter
- Verifying a Safe before sending funds. Confirm the owner set and threshold match what you expect before treating an address as a trusted multisig.
- Monitoring treasury or DAO Safes. Track nonce changes to detect new transactions, or watch for owner set changes that signal a governance action. To see what a pending transaction actually does before that nonce advances, decode its calldata with the Calldata Decoder, which resolves the Safe singleton’s ABI the same way this inspector does.
- Auditing integrations. Check the Safe contract version before building tooling that depends on version-specific behavior.
- Writing developer docs. Show readers a working Safe read example without making them set up a node, an ABI file, or the Safe SDK.
FAQ
What is a Gnosis Safe / Safe{Wallet}?
Safe{Wallet} (formerly Gnosis Safe) is the most widely used multisig smart contract wallet on EVM chains. Instead of a single private key controlling funds, a Safe is owned by a set of addresses and requires a minimum number of them to approve any transaction before it executes.
What does the signature threshold mean?
The threshold is the minimum number of owners that must sign a transaction before the Safe will execute it. A Safe with 5 owners and a threshold of 3 is written as “3 of 5”: any 3 of the 5 owners can approve and move funds, but 2 signatures alone are not enough.
Does this work with any Safe version?
Yes. The tool reads whatever version the deployed Safe proxy is currently running and returns it alongside the other fields, so you can confirm the exact contract version without looking it up separately.
Which chains are supported?
Ethereum, Base, BNB Smart Chain, and Polygon today. More EVM chains are being added on the evmquery backend.
Can I use this from my own application?
Yes. The same expression and Safe address work against the public REST API. The free tier has no monthly cap, and a single inspection counts as one read because Multicall3 batches the underlying calls into one round trip. Bring your own API key and the rate limit applied to this page no longer applies.
Limits and accuracy
- The result reflects the latest block at the time of the read. There is no historical replay.
- The owner list has no pagination. Safe owner sets are small in practice, so every owner is returned in one response.
- The demo is rate limited per browser. If you hit the limit, grab a free API key and the limit goes away.
Pending or queued transactions are out of scope. evmquery returns the Safe’s current on-chain configuration, not its transaction queue.
Related
- Token Allowance Checker: check what a spender is approved to move from a wallet
- ERC-20 Token Inspector: read any ERC-20 token’s name, symbol, decimals, and supply
- Calldata Decoder: decode a pending Safe transaction’s calldata before its nonce advances
- Proxy Contract Detector: the same Safe proxy detection this inspector relies on, on its own
- evmquery for developers: the full integration story