
Verified contracts, proxies, and the upgrade key nobody mentions
There's a green checkmark on the block explorer that says Contract Source Code Verified, and an enormous number of people read it as "this contract is safe."
It does not mean that. It means something much narrower and much more useful, and confusing the two is how careful people end up approving things they'd never have agreed to.
What "verified" actually certifies
Contracts are deployed as bytecode — machine instructions, unreadable in practice. Verification is the act of uploading the original source code and having the explorer confirm that compiling it, with the stated compiler version and settings, produces exactly the bytecode already living at that address.
That's the entire claim: the source you're reading is genuinely the source that runs.
It's a real and valuable guarantee. Without it, you'd have no way to know whether the nice-looking code on a project's GitHub bears any relationship to the thing your transaction is about to touch.
But notice everything it doesn't say. Verification is not an audit. Nobody assessed whether the code is correct, whether it has bugs, or whether it does anything like what its function names suggest. A contract with a function called claimRewards that transfers your entire balance to the deployer can be verified, and the verification would be entirely honest — it would be truthfully certifying that the theft is exactly what the code says it does.
Verified means legible, not trustworthy. It moves the question from "what is this thing?" to "do I understand what I just read?", which is progress, but it doesn't answer the second question for you.
The converse matters too. An unverified contract isn't proof of malice — plenty of legitimate deployments never get around to verification. But it does mean nobody, including any tool you use and any researcher who might have warned you, can read what you're about to interact with. That's a reasonable thing to be cautious about.
Then proxies make it worse
Here's where the ground shifts, and it's the part that mostly goes unmentioned.
Most non-trivial contracts you'll interact with are not single contracts. They're proxies. The proxy is the address you see, and it holds all the data — balances, allowances, state. But it contains almost no logic. Instead it forwards every call to a separate implementation contract, and simply executes whatever that contract says.
The reason is upgradeability. Immutable contracts cannot be patched, so a bug is permanent and a missing feature is forever. Pointing a proxy at a new implementation lets a team fix bugs and ship improvements without asking every user to migrate. Given how much value has been lost to unfixable bugs, this is a defensible engineering choice.
It has a consequence that's easy to miss. When you look up a proxy on an explorer and see it verified, you've verified the forwarding logic, not the behaviour. The code that actually decides what happens to your money lives at the implementation address, which the explorer may or may not show you prominently, and which can be replaced.
So the real question was never "is this contract verified?" It's:
Who can change this contract's behaviour, and how quickly?
The admin key is the actual security model
Every upgradeable contract has someone who can perform the upgrade. That authority is the security model, regardless of how good the current code is.
The range runs from alarming to reasonable:
A single externally-owned account. One private key, on somebody's machine, can swap the logic of the contract holding your funds. If it's phished, stolen, or its owner is coerced, the contract becomes whatever the attacker wants. This arrangement is more common than it should be.
A multisig. Several signers must agree. Substantially better — it removes the single point of failure — though it's worth knowing how many signers, and whether they're genuinely independent people or four laptops belonging to the same team.
A multisig behind a timelock. An upgrade is announced on-chain and can only execute after a fixed delay, often 24 to 72 hours. This is the meaningful one, because it changes your position entirely: you get advance warning of a change, and a window to withdraw before it takes effect. A timelock doesn't stop a malicious upgrade. It makes one survivable.
No upgrade path at all. The contract is immutable. Maximum predictability, zero recourse if a bug is found. A legitimate choice, usually made by protocols that have been battle-tested long enough to bet on it.
None of these is automatically right. But the difference between "one key" and "multisig plus a two-day delay" is the difference between hoping nobody makes a mistake and having time to react when they do.
Why this bites approvals specifically
This is the part that connects to something you actually do regularly.
When you grant a token approval, you're authorising an address to move your tokens. Not a piece of code — an address. The approval sits in the token contract's storage and persists indefinitely.
If that address is an upgradeable proxy, then the logic entitled to move your tokens can be replaced after you approved it, without your involvement and without any new signature from you. You audited one thing and remain exposed to a different thing.
This is why the standing advice to revoke approvals you're no longer using carries more weight than it first appears. It isn't only about a contract turning out to have a bug. It's that an unlimited allowance to an upgradeable contract is a permission granted to whoever controls the upgrade key, for as long as you leave it open.
It's also the sharpest limit on transaction simulation. Simulating before you sign tells you what a transaction does against the current state and the current implementation. It is genuinely useful and it cannot tell you what the same approval will permit next month.
What SSP shows you, and where it stops
When SSP Key presents an approval, it decodes the calldata rather than showing you raw hex. It recognises the standard ERC-20 selectors — approve, transfer, transferFrom, increaseAllowance, setApprovalForAll — and renders them in plain language, including flagging unlimited allowances, defined in the code as any amount at or above 2^255.
The decoder is deliberately conservative. Its own header describes it as presentation-layer only: it never alters what gets signed, and it fails closed. An unknown selector, a wrong length, malformed hex, or non-standard address padding all cause it to return nothing and fall back to a generic action with raw hex behind an "Advanced" view. It would rather admit it doesn't know than guess confidently and be wrong. Token symbols and decimals are attached only when known from the on-device registry, never inferred.
Now the honest boundary. The decoder has no concept of a proxy. There is no notion of an implementation contract anywhere in it. It can tell you, correctly and clearly, "you are granting an unlimited USDC allowance to 0xABC…" — and it cannot tell you that 0xABC is a proxy, who holds its upgrade key, or that its behaviour may be entirely different next week.
That isn't an oversight so much as a boundary no wallet decoder can cross. Decoding describes the call in front of you. Whether the counterparty is upgradeable, and who governs it, is a property of the wider world that has to be checked separately. We'd rather state the boundary than let a confident-looking approval screen imply a completeness it doesn't have.
A practical check before you approve something significant
Is it verified? If not, understand that you're trusting reputation alone.
Is it a proxy? Explorers label this — look for a proxy notice, or a "Read as Proxy" option. If it's there, the implementation is the code that matters.
Who can upgrade it, and is there a delay? This is the highest-value question on the list and the one almost nobody asks. A project that has thought about this will document it. One that hasn't, or won't say, has told you something.
Does the approval need to be unlimited? Usually it doesn't. Approving the amount you actually intend to spend bounds your exposure to exactly that, no matter what the contract becomes later.
Revoke when you're done. An open allowance is a standing permission, and it outlives your attention.
The mental adjustment worth making is small but changes a lot. Verification tells you what code is running right now. Upgradeability tells you who decides what runs tomorrow. Almost all of the risk lives in the second question, and almost all of the reassurance on offer addresses only the first.


