Immutability and the Closer
Why nothing can be changed after deployment, what the single human key can and cannot do, and the date on which it stops existing.
After the deployment transaction there is no setter, no timelock, no proxy, no administration role, on any of the nineteen modules. Every parameter is an immutable validated by a constructor assertion, a formula read on-chain, or a dated tier. The only human power that survives deployment is the power to close, and it expires 365 days later (principle P7, Rule R-12.2.1).
This is not a slogan. It is a set of properties verifiable by reading the deployed bytecode, and asserted contract by contract at deployment (Rule R-16.5.1).
Why
Three arguments, in decreasing order of how much they matter.
A key that can change a parameter is a key that decides how much stablecoin exists. Raising a debt ceiling, listing a collateral, adjusting a minimum ratio — each one changes the quantity that can be issued. Under MiCA, "control via admin keys" is one of the six criteria under consultation for characterising incomplete decentralisation, and a company holding such a key is difficult to distinguish from a person who issues. Removing every such key removes the argument.
A key that can be used can be stolen or compelled. Lybra's deployer kept its governance roles for life. Every parameter reachable by a key is a parameter reachable by whoever obtains the key, or by whoever can compel its holder.
A component that becomes wrong is replaced by a version, not repaired in place. A repair in place is an upgrade path, and an upgrade path is the thing that has to be trusted. Fyber's answer is: a failing component degrades its branch, then kills it, and a corrected branch is a new deployment. Liquity did exactly this on 19 May 2025, and Fyber follows the same shape (Rule R-12.4).
The cost is real and it is stated: a wrongly calibrated parameter cannot be fixed; a false-positive shutdown kills a branch; redeploying a branch costs a differential audit and a migration, four to eight weeks, and nobody can shorten that.
What replaced the levers
Everything that used to need a decision became a formula, a dated table, or a pre-loaded dataset:
| Was a decision | Is now |
|---|---|
| Extending the market calendar | Algorithmic NYSE rules plus a frozen exception table to 2036-12-31; degradation after that is bounded, not breaking |
| Raising the debt ceiling | Dated C_abs tiers, locked permanently by any bad debt |
| Setting exit liquidity | On-chain Uniswap depth with a one-way ratchet, sampled by whoever borrows |
| Posting a reference rate | A 30-day average of an on-chain Morpho market, clamped, with fixed floors on failure |
| Listing a collateral | Five branches pre-deployed, three activating on eight on-chain criteria |
| Suspending interest during an outage | An interest clock that stops after 24 hours of a degraded state |
| Delisting a branch | Permissionless shutdown on TCR, on a dead feed, or on an unresolved token upgrade |
| Winding the protocol down | Permissionless Sunset on two shut branches or an aggregate TCR below 130% |
| Sweeping PSM surplus | Fees minted in fyUSD and routed by the same key as interest |
The Closer
A Safe requiring two of three signatures, held by officers of the development company acting for it. The Safe address and the company's identity are published in the repository and in the terms. No natural person is named. It is not a governance body: it does not deliberate, does not vote, does not modify anything. It is an emergency mechanism with a programmed expiry (Rule R-12.1.1).
There is no execution delay. A delay would make the key useless against an exploit in progress and harmless against nothing.
The closed list of what it can do
| Function | Effect | Bound |
|---|---|---|
freeze(branch or ALL, mask, duration) | Sets freezeUntil[op] on the branch or all branches; mask is a subset of MINT, WITHDRAW, REDEEM; MINT on ALL also covers PSM intake | duration ≤ 72 h; cumulative ≤ 30 days per branch over the key's life |
freezeLiquidation(branch, duration) | Blocks liquidate and liquidateOpen alike on the branch, and refuses new Stability Pool deposits while it runs | ≤ 24 hours; at most 3 per branch over the key's life; at least 7 days between two |
unfreeze(branch or ALL) | Returns to the default state, never beyond it | — |
shutdown(branch) | Same path as an automatic shutdown, with urgentFrom = shutdownAt + 7 days | Irreversible |
shutdownAll() | Immediate Sunset | Irreversible |
renounce() | Sets the expiry to now | Irreversible |
Freezes expire on their own with no action. Two successive 72-hour freezes consume 144 hours of the cumulative budget and each sets the expiry to 72 hours from the call; a freeze never extends another beyond its own bound (Rule R-12.1.3, R-12.1.4).
The cumulative bound is what separates a Closer from a Guardian. A permanent freeze would be control over issuance.
The two extra bounds on a liquidation freeze
Version 0.4 added both, as constructor assertions rather than as policy (decision D87, Rule R-12.1.2, Rule R-9.1.1):
At least 7 days between two liquidation freezes on the same branch. The three a branch may ever receive were each capped at 24 hours, and nothing stopped them being chained. Three back to back covered Friday 19:00 to Monday 19:00: a whole weekend during which nothing could be liquidated while the Stability Pool was free to withdraw. The spacing makes that arrangement impossible to build.
New Stability Pool deposits are refused while the freeze runs. SP.provide and sfyUSD.deposit revert on that branch. A freeze is the protocol declaring that it does not currently know whether liquidating is correct, and capital should not be attracted into that position, whether by an insider who understands the freeze or by a depositor acting in good faith on a rate that is about to change meaning. SP.withdraw, claimCollateral and sfyUSD.redeem are untouched, as they are under every other state.
Guarantee
No freeze, of any kind, ever blocks an exit. Repayment, collateral addition, closure, surplus
claims, pool withdrawal, swapOut, sfyUSD.redeem and urgent redemption remain callable
throughout every freeze the key can set (Rule R-4.4.1).
What it can never do
The functions are absent from the bytecode, and their absence is asserted at deployment (Rule R-12.1.7):
Guarantee
The Closer cannot touch repay, addCollateral, close, claimSurplus, Stability Pool
withdrawal, claimCollateral, urgentRedeem, settleAfterShutdown, swapOut or
sfyUSD.redeem. It cannot mint or designate a minter. It cannot modify an address, a constant, a
tier or an activation criterion. It cannot activate a branch. It cannot transfer any fund. It
cannot reopen what has been closed. It cannot extend its own life. It cannot name a successor.
Expiry
How it is calculated
EXPIRES_AT = DEPLOY_TS + 365 days, immutable, verified by the deployment script. Every Closer
function reverts CloserExpired afterwards.
No extension is possible by anybody. renounce() brings the date forward to the moment of the call (Rule R-12.1.5).
Twelve months rather than three, for a mechanical reason: the dormant branches activate between day 90 and day 150, and their final capacity tiers open 180 days after their own activation. Code that has not yet been exercised in production therefore enters service until roughly month eleven. Beyond that, the key is a legal liability with no security benefit. An early renunciation at month three remains an option if legal advice concludes the key is incompatible with the position the protocol takes.
An immutable expiry rather than a promise, because a promise of renunciation is precisely what Lybra made, and its deployer kept its roles for life. An immutable is verified by anybody in one read.
Published doctrine
Freeze first — reversible, at most 72 hours. Shut down afterwards, and only if the flaw is confirmed. A shutdown on a false positive kills a branch, and the key holder is expected to know that. A shutdownAll() outside an emergency is preceded by at least seven days of public notice, which is a commitment outside the protocol rather than a mechanism (Rule R-12.1.6).
Every use emits an event: Frozen, LiquidationFrozen, Unfrozen, Shutdown, Sunset. A dedicated page shows the Safe address, the expiry with a countdown, any freeze in force and its end, the cumulative budget consumed per branch, the number of liquidation freezes remaining, and the earliest date another one could be set (Rule R-15.1.13).
Risk
The value of the Closer assumes 24/7 monitoring during its life — an on-call rotation plus the open-source watcher. Without that monitoring it is worth nothing, and that is said publicly rather than implied.
What it covers, and what it does not
| Failure class | Without any key | With the Closer |
|---|---|---|
| Flaw found before exploitation | Public notice and voluntary exit; a race against anyone who reads the notice | shutdown(branch) in minutes closes the race |
| Exploit in progress on mint, liquidation or redemption | Drain until users withdraw. Ajna: roughly 775 000 USD, no pause button | A freeze in minutes bounds the drain to human latency rather than to the attacker's speed |
| Atomic exploit, one transaction | Immediate loss | Immediate loss. Only design helps: the same-block rule and the absence of flash minting |
Bug in repay, close or pool withdrawal | Not protectable | Not protectable, deliberately: freezing those is worse than the bug |
| A risk parameter that is plainly wrong | Nothing | Nothing, beyond freezing minting for 72 hours while a new version is published |
| Dead oracle, delisted token, hostile upgrade | Automatic at 7 days | Automatic at 7 days, or immediate by the key |
The verdict is stated plainly in the design record: sufficient at the debt level of the first phase, strictly better than no key for the most common failure class in practice, and insufficient against an atomic exploit — like everything else (Rule R-12.1.8).
The claim the protocol makes
During the key's life, Fyber does not claim to be fully decentralised. It claims that fyUSD has no identifiable issuer: issuance is exclusively the act of contracts whose minters were frozen at construction, and closing is not issuing. The key is documented as an emergency mechanism with a programmed extinction, and the amount at stake during that window is capped by the dated tiers.
Every use of the word "decentralised" in marketing is prohibited while the key lives (Rule R-15.4.2).
The team-disappears test
The protocol must pass, by construction and by simulation, the scenario in which the company takes no action from day one onward (Rule R-12.6.1, Rule R-16.5.2):
- Borrowing, repayment, liquidation, redemption, PSM, wrapper and router: unchanged, permissionless, incentivised.
- Caps:
LiquidityOracle.poke()is called by borrowers themselves, dated tiers open on their own, dormant branches activate on their own if anyone fills their depth buffer. - Calendar: valid without intervention to 2036, then bounded degradation.
- Dead oracle, delisted token, hostile upgrade: automatic shutdown, urgent redemptions, permissionless settlement.
- Rate floor: unavailable reference gives the fixed floors.
- Closer: nobody uses it, it expires at month twelve. The only consequence of the disappearance is the loss of the emergency response during the first phase.
- Monitoring, front end, paymaster: they disappear unless a third party takes them over; the protocol stays usable by direct contract call.
The same test at month thirteen is identical minus the Closer line, and that is the state Liquity V2 is in today.
Verification
The deployment script asserts, contract by contract (Rule R-16.5.1): no owner() and no role anywhere in the address graph; no ABI entry whose name begins with set, add, remove, update (other than updateMintFreeze), grant, revoke, upgrade, transferOwnership or pause; no storage variable holding a risk constant; the minter set exactly right; five distinct branches on five distinct tokens with slots six through ten zero; the Closer pointing at the published Safe with the correct expiry; every cross-address consistent; every constructor relation verified by reading; and the deployed bytecode of each module equal to the compiled bytecode of the frozen specification. The report is published.
Two fuzzed invariants back this up (Rule R-16.3 items 17, 18, 19): the hash of the complete set of immutables and tier tables is identical before and after any call sequence, including sequences run from the Closer's own address; every write function called from a random address either succeeds or reverts with one of six whitelisted authorisation errors; and after warping past the expiry, every Closer function reverts and no onlyCloser function anywhere is reachable.
Last reviewed: 2026-09-07 · Spec v0.4