Events
What each module emits, and what an indexer should do with it. Every state transition emits, which is what makes a protocol with no administrator auditable.
Spec v0.9.1, reviewed 2026-09-08
Every state transition emits an event. That is a design rule rather than a convention: with no administrator to ask and no upgrade to inspect, the log is the only account of what happened, and an indexer built on it can reconstruct the whole history of a branch.
Positions
Emitted by each Branch.
| Event | What it carries | What an indexer does |
|---|---|---|
PositionOpened | Owner, collateral, debt, rate | Opens a position record |
PositionAdjusted(user, collDelta, debtDelta, newDebt) | Signed deltas and the resulting debt | Updates it. A zero resulting debt closes it |
RateChanged(user, rate, fee) | The new rate and any fee charged | Updates the rate and the position in the queue |
InterestMinted(amount) | fyUSD minted at this accrual | Adds to the branch's cumulative interest |
InterestClockPaused(since) / InterestClockResumed | When accrual stopped and restarted | Marks the interval as unbilled |
SurplusClaimed | Owner | Closes a residual collateral claim |
Interest is not emitted per position. A position's debt is a function of its recorded debt, its rate, the branch interest clock and the redistribution index, so an indexer that wants a per-position balance recomputes it rather than summing events.
Liquidation and loss
| Event | Emitted by | What an indexer does |
|---|---|---|
Liquidated(user, debtRepaid, collSeized, bonusWad, priceUsed, regime, mode) | Branch | Records the execution with the regime and the price it used, which is what makes a liquidation checkable after the fact |
Redistributed(debt, coll, L_debt, L_coll) | Branch | Marks every open position on that branch as carrying a share. Notify each owner with the new ratio |
BadDebtRecorded(amount, cumul) | Branch | Updates the branch's cumulative bad debt, which is what locks its ladder |
BadDebtCovered(amount) | Branch | Reduces the outstanding shortfall |
MintFreeze(bool, tcr) | Branch | Toggles the branch's borrowing state |
Risk
Redistributed is the one event an interface must act on immediately. Positions that were healthy
a block earlier now carry more debt and more collateral, at a lower ratio, without their owners
having done anything.
Caps, activation and closure
| Event | Meaning |
|---|---|
Activated(at) | A dormant branch passed all eight criteria and is now lending |
CapTierReached(index, cAbs) | The branch climbed a step of its debt ladder |
CapTierLocked(index, badDebtCumul) | It will never climb another. Permanent |
ShutdownFlagged(at, tcr) | The one-hour clock to closure has started |
Shutdown(reason, lastGood, urgentFrom) | The branch is closed. Urgent redemptions open at urgentFrom |
Settled(user, debt, coll) | A position settled after closure |
UpgradeDetected(newImpl) | The collateral token's implementation changed |
UpgradeCleared / UpgradeExtended(until) | The automatic transferability test passed, or did not |
Frozen(mask, until, cumul) / LiquidationFrozen(until, count) / Unfrozen | The Closer key acted, with its consumed budget |
Price
Emitted by each OracleAdapter. This is the densest group and the one an interface reads most.
| Event | What an indexer does |
|---|---|
RegimeChanged(from, to, cause) | Records the transition with its cause. Drives every rule an interface displays |
AnchorAccepted(idx, roundId, price, quarantinedBefore) | Records a new reference print |
AnchorQuarantined(idx, roundId, price, pCompRaw) | The exchange print disagreed with the live markets. Publish it |
AnchorSwitched(from, to) | The branch moved to its second reference |
Circuit(cause) / CircuitCleared() | A validity failure opened or closed |
JumpEdge(until) | The pool executes nothing until that timestamp |
SourceExcluded(id, reason) | A source went absent. Reasons: stale, unqualified, invalid ratio, saturated, reverting, or a broken peg on a vault pool |
RatioSnapshot(id, ratio) / RatioOutOfBounds(id, ratio) | A derived source was recalibrated, or refused |
MultiplierAnomaly(m, impliedShare) | The feed and the token's multiplier disagree |
LiveEpoch(epoch) | Every outstanding liquidation flag on the branch is void |
InterestUnbilled(cumul) | Cumulative seconds during which interest did not accrue |
An interface that watches only one of these should watch RegimeChanged. An indexer that wants to reconstruct why a liquidation executed at a given price needs SourceExcluded and AnchorQuarantined as well.
Alongside it, V4Observer emits ObserveSkipped when a pool cannot be read or holds no liquidity at the trading price, and the pull oracle emits ReporterRenounced when one of the fixed keys withdraws for good.
Pools, the wrapper and the peg module
The Stability Pool and its wrapper follow the standard token and vault event shapes: transfers, deposits, withdrawals. Beyond those, the wrapper emits when it starts selling seized collateral, and the collateral sale emits each purchase with the discount applied at that moment.
The peg module emits its swaps in both directions, and the depeg guard opening and closing. An indexer should treat the guard as the explanation for a refused entry rather than as an error.
FBR
| Event | Emitted by | What an indexer does |
|---|---|---|
FbrShare(f, spRatio, fmax) | InterestRouter | Records the revenue share in force and the pool health that set it |
| Season settled | FBRDistributor | Freezes the totals of that season. Allocations become computable |
| Buy-back executed | FBRDistributor | Records the amount bought, the amount credited to stakers, and the amount burned |
Burned | FBRToken | Reduces total supply. The only way it ever moves |
| Stake, unstake request, withdrawal, cancellation | sFBR | Tracks lots, their dates and their weights |
| Seizure, auction opened, bid, auction closed | Backstop | Records what was taken from stakers and what was recovered |
A season's allocation is not emitted per user. It is computed from the counters and the frozen totals, which is why the counters are readable rather than emitted.
The vaults
Each LPVault emits on deposit, withdrawal, recentring, harvest and checkpoint. Recentring is the one to watch: it carries the old and new centres, and it briefly changes the pool's liquidity, which can invalidate that pool's price window for half an hour.
The gate hook emits nothing. It only reverts when a party other than a vault tries to add liquidity.
What is not emitted
Two absences are deliberate and worth knowing before building an indexer.
No parameter-change event, on any contract, because no parameter can change. An indexer that watches for one is watching for something that cannot happen, and its absence is a property you can rely on.
No per-second interest event. Interest is minted at each accrual and routed in one movement. Reconstructing what a specific position owes at a specific block means reading the branch interest clock and the position's own snapshot, not summing a log.
Addresses
Chain details for Robinhood Chain and its testnet, the external contracts the protocol reads, and the address tables that will be filled in at deployment.
Invariants
The seventy properties that must hold under every call sequence, in words and in short pseudo-code, as they are fuzzed nightly against bounded handlers.