Skip to content
Protocol

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 decisionIs now
Extending the market calendarAlgorithmic NYSE rules plus a frozen exception table to 2036-12-31; degradation after that is bounded, not breaking
Raising the debt ceilingDated C_abs tiers, locked permanently by any bad debt
Setting exit liquidityOn-chain Uniswap depth with a one-way ratchet, sampled by whoever borrows
Posting a reference rateA 30-day average of an on-chain Morpho market, clamped, with fixed floors on failure
Listing a collateralFive branches pre-deployed, three activating on eight on-chain criteria
Suspending interest during an outageAn interest clock that stops after 24 hours of a degraded state
Delisting a branchPermissionless shutdown on TCR, on a dead feed, or on an unresolved token upgrade
Winding the protocol downPermissionless Sunset on two shut branches or an aggregate TCR below 130%
Sweeping PSM surplusFees 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

FunctionEffectBound
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 intakeduration ≤ 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 daysIrreversible
shutdownAll()Immediate SunsetIrreversible
renounce()Sets the expiry to nowIrreversible

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 classWithout any keyWith the Closer
Flaw found before exploitationPublic notice and voluntary exit; a race against anyone who reads the noticeshutdown(branch) in minutes closes the race
Exploit in progress on mint, liquidation or redemptionDrain until users withdraw. Ajna: roughly 775 000 USD, no pause buttonA freeze in minutes bounds the drain to human latency rather than to the attacker's speed
Atomic exploit, one transactionImmediate lossImmediate loss. Only design helps: the same-block rule and the absence of flash minting
Bug in repay, close or pool withdrawalNot protectableNot protectable, deliberately: freezing those is worse than the bug
A risk parameter that is plainly wrongNothingNothing, beyond freezing minting for 72 hours while a new version is published
Dead oracle, delisted token, hostile upgradeAutomatic at 7 daysAutomatic 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