Circuit Breaker v2 — UI, Backend & Infrastructure Guide
Overview
Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry v2 (CBv2) is an on-chain rate limiterrate limiterA mechanism that caps the rate of withdrawals or actions over time to reduce exploit impact.View glossary entryrate limiterA mechanism that caps the rate of withdrawals or actions over time to reduce exploit impact.View glossary entry with deferred settlement for protocol outflows. It protects against exploits by limiting how much value can leave the protocol within a time window. When an outflow exceeds available capacity, it is queued for delayed execution instead of being rejected.
Users normally do not need to interact with the Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entryCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry contract directly for most withdrawals. They call the same redeem() / withdraw() functions they always would.
The Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry only becomes visible when a withdrawal larger than the available buffer is requested and gets queued for later settlement. Most withdrawals should not be queued as long as they remain within the available buffer.
Key Concepts
| Term | Description |
|---|---|
| Protected contract | A contract registered with the Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entryCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry that routes outflows through it (e.g., MintAndRedeem, ftYieldWrapperV2). Each inherits ProtectedContract and calls into the Circuit Breaker internally. |
| Limiter asset | The token used as the rate-limit bucket key. Buffer capacity is sized relative to TVL of this asset. |
| Transfer token | The token actually transferred. Usually the same as the limiter asset, but can differ (e.g., wrapper withdrawals may use the wrapper token as limiter but transfer strategystrategyA yield venue tracked by the wrapper; strategies can be added/removed/reordered and capital moved between them by authorized roles.View glossary entry position tokens). |
| Main buffer | Steady-state withdrawal capacity. Replenishes linearly over mainWindow toward a cap of maxDrawRateWad * TVL. |
| Elastic buffer | Temporary capacity from deposits. Decays proportionally over elasticWindow. Prevents deposit-then-withdraw flash loanflash loanAn uncollateralized loan that must be borrowed and repaid within a single transaction; often used for arbitrage or attacks.View glossary entry attacks from consuming the main buffer. |
| Settlement delay | Time a queued outflow must wait before it can be executed. Configurable between 5 minutes and 7 days. Default is 6 hours. |
Architecture
Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry logic lives directly in each engine via the ProtectedContract mixin. There is no separate router contract — each protected contract calls the Circuit Breaker internally when processing outflows.
How a Withdrawal Works
Key points for UIUIThe visual interface through which users interact with an app or product.View glossary entry:
- If the withdrawal is immediate, nothing special — the user gets their tokens in the same transaction.
- If the withdrawal is queued, the UIUIThe visual interface through which users interact with an app or product.View glossary entryUIThe visual interface through which users interact with an app or product.View glossary entry should show a pending state with a countdown to
settlesAt. Once the delay passes, a "Claim" button (or backend keeper) callsexecuteQueued(queueId)to release the funds. executeQueuedis permissionless — anyone can call it once the delay has elapsed. The UIUIThe visual interface through which users interact with an app or product.View glossary entry can offer a "Claim" button to the user, or a backend keeper can auto-execute on their behalf.
Step-by-Step
-
Pre-check capacity before the user submits:
const [wouldBeImmediate, availableCapacity] = await cb.checkOutflow(
contract,
asset,
amount,
tvl,
); -
If queued, track the queue entry via events:
- Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry emits:
OutflowQueued(queueId, asset, recipient, amount, settlesAt)
- Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry emits:
-
Wait for settlement:
const remaining = await cb.timeUntilSettled(queueId); // seconds until executable
const ready = await cb.isSettled(queueId); // true if executable now -
Execute (anyone can call):
await cb.executeQueued(queueId);
// or batch:
await cb.executeQueuedBatch([queueId1, queueId2, queueId3]);
Edge Case: Pre-Check Staleness
Note:
checkOutflowandwithdrawalCapacityread the buffer state at call time. Between the moment the UIUIThe visual interface through which users interact with an app or product.View glossary entryUIThe visual interface through which users interact with an app or product.View glossary entry shows "This withdrawal will be instant" and the moment the user's transaction is mined, other transactions may consume buffer capacity. If that happens, the withdrawal that appeared instant will be queued instead.
The UIUIThe visual interface through which users interact with an app or product.View glossary entry should handle this gracefully:
- Always listen for both
OutflowImmediateandOutflowQueuedevents after a withdrawal transaction, regardless of what the pre-check indicated. - If a pre-check said "instant" but the transaction emits
OutflowQueued, transition the UIUIThe visual interface through which users interact with an app or product.View glossary entry to the queued/countdown state.
Recommended: show likelihood based on buffer headroom. Rather than a binary "instant / queued" label, compare the user's amount against current capacity and convey confidence:
const capacity = await cb.withdrawalCapacity(contract, asset, tvl);
const ratio = amount / capacity; // how much of the buffer this withdrawal would consume
if (ratio <= 0.5) {
// Plenty of headroom — very unlikely to be front-run
showMessage("This withdrawal will be processed instantly.");
} else if (ratio <= 0.9) {
// Moderate headroom — possible but unlikely
showMessage(
"This withdrawal is expected to be instant, but may be queued if demand increases before confirmation.",
);
} else if (ratio <= 1.0) {
// Tight — high chance another tx consumes remaining capacity first
showWarning(
"This withdrawal is close to the instant limit and may be queued if other withdrawals are confirmed first.",
);
} else {
// Exceeds capacity — will definitely be queued
showMessage(
`This withdrawal exceeds the instant limit (${formatAmount(capacity)} available). It will be queued and claimable in ~6 hours.`,
);
}
This gives users a realistic expectation without false precision. The exact thresholds are up to the UIUIThe visual interface through which users interact with an app or product.View glossary entryUIThe visual interface through which users interact with an app or product.View glossary entry team — the key insight is that amount / capacity near 1.0 means the outcome is sensitive to other pending transactions.
Dual Buffer System
The rate limit is determined by the minimum of two independent buffers:
For the UIUIThe visual interface through which users interact with an app or product.View glossary entry health gauge, use getAssetHealth() which returns:
mainBuffer/mainBufferCap— show as a percentage barelasticBuffer— additional transient capacity from recent depositsutilizationBps— 0–10000, how much of the main buffer is consumed (0 = full capacity, 10000 = depleted)pendingOutflows— total value sitting in the queue for this assetisPaused— if true, all operations for this asset are halted
Queue Entry States
A queued withdrawal moves through these states. The UIUIThe visual interface through which users interact with an app or product.View glossary entry should reflect each:
UI State Mapping
| Queue Status | status field | settlesAt vs now | What to Show |
|---|---|---|---|
| Pending (waiting) | Pending | settlesAt > now | Countdown timer: "Claimable in X hours Y min" |
| Pending (sped up) | Pending | settlesAt > now (but reduced) | Updated countdown, maybe "Expedited" badge |
| Claimable | Pending | settlesAt ≤ now | "Claim" button → calls executeQueued(queueId) |
| Paused | Paused | N/A | "On hold — contact support". No claim button |
| Executed | Entry deleted | N/A | "Completed" — entry no longer exists in storage |
| Invalidated | Pending | Any | executeQueued will revert with QueueInvalidated. Show "Cancelled — emergency recovery" |
How to detect invalidated entries: Compare queue.recoveryEpoch with cb.assetRecoveryEpoch(queue.token). If they differ, the entry is invalidated.
Scoped Limiters
By default, all protected contracts share the same limiter state per asset. When scoped limiter is enabled for a contract, that contract gets its own isolated buffer state. This prevents one engine's activity from affecting another's capacity.
Why Scoping Matters
Each protected contract (MintAndRedeem, ftYieldWrapperV2) has its own isolated rate-limit buffer. This means:
- A large redemption on MintAndRedeem does not reduce capacity for ftYieldWrapperV2 withdrawals (and vice versa).
- When calling view functions, you must pass the correct
contractaddress as the first argument to get that contract's buffer state. - Always use the 3-arg view overloads that accept
(contract, asset, tvl). The 2-arg versions read shared state that scoped contracts don't use.
// Correct — reads MintAndRedeem's own buffer
cb.withdrawalCapacity(mintAndRedeemAddr, ftUSDAddr, mintAndRedeemTvl);
// Correct — reads wrapper's own buffer
cb.withdrawalCapacity(wrapperAddr, ftUSDAddr, wrapperTvl);
// Wrong for scoped contracts — reads shared state (empty)
cb.withdrawalCapacity(ftUSDAddr, someTvl);
Check if a contract is scoped:
const isScoped = await cb.isScopedLimiter(contractAddress);
Whitelisted Recipients
The Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entryCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry owner can whitelist specific recipient addresses. Withdrawals to a whitelisted address always execute immediately — they bypass the rate limiter entirely and do not consume any buffer capacity.
This is used for trusted protocol-owned addresses (e.g., treasury, other protocol contracts) that should never be rate-limited.
How It Affects the UI
- Whitelisted recipients will never see a queued withdrawal, regardless of amount or buffer state.
- The flowchart above shows this path: if the recipient is whitelisted, the transfer is immediate with no buffer consumed.
checkOutflowdoes not account for whitelisting — it only checks buffer capacity. A pre-check may reportwouldBeImmediate = falsefor a large amount, but if the recipient is whitelisted the on-chain transaction will still execute immediately.
Checking Whitelist Status
// Check if a specific address is whitelisted
const isWhitelisted = await cb.isWhitelistedRecipient(recipientAddress);
// List all whitelisted addresses
const whitelist = await cb.getWhitelistedRecipients();
If your UIUIThe visual interface through which users interact with an app or product.View glossary entry pre-checks withdrawal outcome, check whitelist status first to avoid showing a misleading "will be queued" warning:
const isWhitelisted = await cb.isWhitelistedRecipient(userAddress);
if (isWhitelisted) {
// Always instant — skip buffer capacity check
showInstantWithdrawalUI();
} else {
const [wouldBeImmediate, capacity] = await cb.checkOutflow(
contract,
asset,
amount,
tvl,
);
// Show instant or queued UI based on capacity
}
Admin Management
Whitelist changes are owner-only and emit RecipientWhitelistUpdated(recipient, enabled). Index this event to keep whitelist state current.
Contract Calls Reference
All read calls below are view functions (no gas cost when called via eth_call).
Before a Withdrawal (Pre-Check)
Use these to show the user what will happen before they submit:
| What You Need | Call | Returns |
|---|---|---|
| Will this withdrawal be instant or queued? | cb.checkOutflow(contract, asset, amount, tvl) | (bool wouldBeImmediate, uint256 availableCapacity) |
| Max amount user can withdraw instantly | cb.withdrawalCapacity(contract, asset, tvl) | uint256 |
| Full buffer health for a gauge | cb.getAssetHealth(contract, asset, tvl) | AssetHealth struct (see below) |
Note:
contractis the address of the protected contract (e.g., MintAndRedeem or ftYieldWrapperV2), not the Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entryCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry itself.tvlis the current TVL of that contract for the given asset — see below for how to obtain it.
How to Obtain TVL
The Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entryCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry view functions require a tvl parameter because the Circuit BreakerCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entryCircuit BreakerA rate‑limiting safety mechanism that throttles withdrawals or outflows to reduce exploit blast radius.View glossary entry itself does not store TVL — it is supplied by the caller. Each protected contract computes TVL differently, and the on-chain _getTvl() override is internal, so the backend must call the appropriate public getter and apply the same floor logic.
| Protected Contract | Public TVL Getter | Units | Notes |
|---|---|---|---|
| MintAndRedeem | accountedCollateralTvl() | 6 decimals (ftUSDftUSDA delta‑neutral, yield‑bearing stable asset designed to target $1 while minimizing liquidation risk by balancing long/short exposures (e.g., supply/stake/borrow loops).View glossary entry) | Sum of (totalIn - totalOut) across enabled collaterals, normalized to 6 decimals. Does not use |