# What is AEGIS?

AEGIS is programmable infrastructure for Uniswap v4 pools. It is not a new AMM, it is not a new chain, and it is not a new pegged asset. It is a set of on-chain contracts that sit directly on top of Uniswap v4's PoolManager and add two capabilities the base AMM does not have:<br>

1. Dynamic, volatility-aware fees that adapt to real-time pool activity and compound a share of earnings into protocol-owned liquidity.
2. Pool-native credit — the ability to borrow liquidity itself, collateralized by the same pool, with no external price oracle in the solvency path.

Both capabilities are delivered as opt-in Uniswap v4 hooks and periphery contracts. Any pool that attaches an AEGIS hook inherits the behavior; pools that don't are unaffected.

### The two products

AEGIS ships as two composable modules that share a common hook, oracle, and policy surface:

**AEGIS DFM — Dynamic Fee Mechanism.** A Uniswap v4 hook that replaces the static fee tier with a two-component dynamic fee (a slow-moving base and a fast-moving surge) and recycles a configurable share of fees into full-range protocol-owned liquidity. DFM is the live, production-hardened layer of AEGIS.

**AEGIS Engine — Pool-native liquidity and credit engine.** Builds on DFM's hook and oracle foundation to offer pooled full-range liquidity deposits (sL shares), borrowable liquidity units (L), and an oracle-free solvency model based on a √K collateral floor. Engine is in active development and has not yet been audited.

You can use DFM without Engine. You cannot use Engine without DFM: Engine assumes an AEGIS-approved hook is installed on the underlying pool, and that hook is always DFM or a DFM-compatible variant.

### Built on Uniswap v4

AEGIS is not a fork of Uniswap. Every token balance, every swap, every liquidity change lives in Uniswap v4's singleton PoolManager. AEGIS contracts never custody ERC-20 tokens at rest — all token movement happens inside the PoolManager unlock frame using its ERC-6909 accounting. At the end of every transaction, AEGIS contracts hold no user ERC-20 balances.

Concretely, this means:

* You are trading and providing liquidity on Uniswap v4 pools. Routing, slippage, and composability work exactly as they do for any Uniswap v4 pool.
* AEGIS attaches to pools; it does not replace them.
* If AEGIS contracts were to be disabled, the underlying Uniswap v4 pools would continue to operate. LP value does not depend on AEGIS being "up."

### What AEGIS is not

AEGIS is often mistaken for things it is not. To be precise:<br>

* **Not an issuer.** AEGIS does not mint a pegged stablecoin, a wrapped asset, or a receipt token that sits on a balance sheet somewhere. sL shares represent a pro-rata claim on a specific pool's full-range liquidity, not a deposit with a third party.
* **Not custodial.** AEGIS does not take possession of user funds. Tokens move directly between users and Uniswap's PoolManager.
* **Not an oracle-dependent lender.** Engine's solvency model uses a √K floor computed from the positions themselves, not an external price feed. There is no oracle to manipulate into a liquidation cascade.
* **Not a governance token farm.** Participation is based on pool activity and position performance, not on buying a token.

### The shape of the rest of this documentation

This site is organized so that readers can stop reading at any point and still have a coherent mental model:<br>

* Start Here — what AEGIS is, why it exists, and which path to follow.
* AEGIS Engine — the core credit story, end to end.
* Using the Engine — role-specific guides for liquidity providers, borrowers, and integrators.
* Engine System Structure — the contracts and accounting for readers who need to verify the story.
* AEGIS DFM — the dynamic-fee layer, its mechanism, and live pool context.
* Trust, Risk & Status — risks, governance, audit status, and deployed addresses.
* Reference — ABIs, contract map, state views, and glossary.

\
If you only have five minutes, read [Why AEGIS Exists](/part-1-start-here/why-aegis-exists) next, then [The Big Idea: Turning Liquidity Into Credit.](/part-2-core-engine-concepts/the-big-idea-turning-liquidity-into-credit)


# Why AEGIS Exists

Two problems in DeFi keep recurring. AEGIS is built to address both inside a single, pool-native design.

### Problem 1: Static fees under-compensate liquidity providers

Uniswap pools charge a fixed fee tier — typically 5, 30, or 100 basis points — regardless of what the market is doing. In practice, market conditions are not fixed. Volatility, volume, and adverse selection all fluctuate constantly, and a static fee is essentially a bet that the average pair behavior is a good match for the fee tier.

When markets are calm, a 30 bp fee is often too high to attract the best routing flow. When markets are volatile, the same 30 bp is often too low to compensate the LP for the loss-versus-rebalancing (LVR) they are absorbing. The result is well-documented: professional LPs have migrated from passive positions into concentrated liquidity, active management, and off-chain hedging — solutions that are either capital-intensive, operationally complex, or only available to sophisticated participants.

AEGIS DFM addresses this by making the fee itself a function of the pool's observable state. A slow-moving base component tracks realized volatility; a fast-moving surge component spikes during rapid price moves and decays over time. Both components are computed from pool ticks and block timestamps — no external price feeds — and a configurable share of collected fees is compounded into a protocol-owned full-range position, deepening the book over time.

The core mechanism claim is simple: a fee that adapts to volatility captures more of the economic surplus that a static fee leaves on the table.

### Problem 2: Credit systems depend on oracles they shouldn't

Most on-chain lending and margin systems price their collateral using an external oracle — a Chainlink feed, a TWAP from another venue, a ratio between two vaults. This design has a structural weakness that has produced most of the catastrophic failures in DeFi credit:

* If the oracle lags real-time price, arbitrageurs extract value at the cost of healthy positions.
* If the oracle leads, benign price action triggers liquidations that wouldn't have been necessary.
* If the oracle can be manipulated — even briefly, even on a thin venue — the entire book can be liquidated at a price nobody actually trades at.

The cascading failure mode looks like this: an oracle ticks in one direction; liquidations fire at the oracle price; liquidation sell-pressure pushes the real market in the same direction; the oracle ticks again; more liquidations fire; the cascade accelerates until liquidity is exhausted. Users who were nowhere near the "true" liquidation boundary lose their collateral. The design rationale for this structure — "we need a price" — has always been in tension with the observation that the thing being quoted is itself a market.

AEGIS Engine takes a different approach. The collateral check is not "how much is this collateral worth in USD?" It is "if we pulled every lender's liquidity out at the pool's current √K, would the borrower still have enough tokens to cover their debt?" This is a purely structural question that can be answered from on-chain state with no external input. It is a conservative bound, not a spot price, and it does not move faster than the pool itself moves.

Because the solvency check is derived from the same pool the borrowing occurs against, the cascade failure mode that has caused so much damage to oracle-dependent lenders does not exist in AEGIS Engine. This is the single most important property of the design, and it is what makes pool-native credit possible at all.

### Why both in one protocol

DFM and Engine are not two unrelated products stitched together. They share:

* **The same Uniswap v4 hook stack.** The hook that quotes a dynamic fee is the same hook that orchestrates fee reinvestment into protocol-owned liquidity — which, for Engine pools, is the same pooled full-range position that backs sL shares and L credit.
* **The same oracle.** TruncGeoOracleMulti / OracleManager provides the TWAP observations that DFM uses to compute fees and that Engine uses as a soft-enforcement check on keeper liquidations.
* **The same policy manager.** Risk parameters, fee splits, and cap budgets are all read from PoolPolicyManager, keeping the economic configuration for a pool in one place.

\
Conceptually: *DFM makes a pool worth participating in for LPs.* *Engine makes the same pool's liquidity productive as credit.* The two together turn a Uniswap pool from a passive trading venue into an active, self-tuning liquidity and credit market.

### What AEGIS does not try to do

A few things are explicitly out of scope:

* **Cross-pool margin.** Every position in Engine is collateralized by the single pool it lives in. There is no portfolio netting across pools, and no cross-pool contagion path.
* **Leveraged long/short trading UX.** Engine exposes borrowable liquidity, not synthetic exposure to an asset. Applications that want a trading product can build it on top; Engine itself is infrastructure.
* **A new stablecoin.** There is no pegged unit of account. All accounting is in pool-native liquidity units (L) and the underlying pool's two tokens.
* **Prescriptive governance.** Parameter changes go through a Compound-style Timelock. There is no rapid-reaction governance and no runtime pause. See Governance and Roles.


# What is AEGIS Engine?

AEGIS Engine is a pool-native liquidity and credit engine built on Uniswap v4. In one sentence: it turns an AMM pool's liquidity into a borrowable resource, and it does so without ever taking custody of user tokens and without depending on an external price oracle for solvency.

This page explains what Engine is as a system. Later pages explain why the design choices matter (The Big Idea), how borrowing against a pool actually works (How Borrowing Works), and what keeps it safe (The Safety Model).

### The product, concretely

Engine consists of four contract surfaces a user or integrator will interact with:

1. **AegisEngine** — the singleton core contract. Holds market state, vault state, the sL share ledger, and the L-unit debt ledger. Every mutation goes through a session that AegisEngine orchestrates.
2. **AegisRouterV1** — the periphery / canonical router. Handles Uniswap v4 PoolManager.unlock orchestration, pre-funds the engine during a session, and exposes a batch of action opcodes that let integrators compose multiple operations in a single transaction.
3. **AegisHook** — the Uniswap v4 hook that an AEGIS-enabled pool installs. Quotes dynamic fees via the DynamicFeeManager, maintains oracle observations, and reinvests a share of fees into the pooled full-range position that backs sL shares.
4. **VaultRegistr**y — an ERC-721 that issues a borrower's vault NFT. Transferring the NFT transfers ownership of the vault (subject to session-unlocked restrictions).

Engine depends on two supporting managers that are part of the same deployment:

* **OracleManager** — a Uniswap-truncated-oracle ring buffer per pool. Used by the hook during swaps and by keepers to soft-enforce TWAP-bounded liquidation behavior.
* **LimitOrderManager** — a bucket/epoch-based limit order book integrated as NFT-wrapped positions that vaults can attach to their collateral.

And it reuses the DFM stack for fees:

* **DynamicFeeManager + PoolPolicyManager** — dynamic fee engine + risk/policy registry.

### What a lender does

A lender ("liquidity provider") deposits a balanced pair of tokens into a specific pool through the router. In exchange, Engine mints sL shares tied to that pool (tokenId = PoolId under ERC-6909). The deposited tokens become part of a single, pooled full-range position that Engine manages on behalf of all lenders in that pool.

Key properties:

* **sL is pool-scoped.** A share of pool A cannot be burned against pool B.
* **Share price is monotonically non-decreasing.** Fees accumulated by the pooled position raise the share price over time; redemption never pays less than proportional to the current pool state.
* **Redemption is permissionless.** You can always burn your sL shares back into your share of the pool's current token holdings, subject to the pool having sufficient idle capacity (see [utilization cap](http://../06-trust-risk-status/solvency-and-liquidation.md#utilization-cap)).

### What a borrower does

A borrower ("vault owner") opens a vault (an ERC-721 from VaultRegistry) bound to a single pool. Into that vault they can deposit:

* Idle token balances in either pool currency.
* Up to MAX\_NFTS\_PER\_VAULT Uniswap v4 concentrated-liquidity NFTs or AEGIS limit-order NFTs. When attached, ownership of these NFTs transfers to the engine; they are returned on detach.

The borrower then calls borrowL with a desired amount of L — a unit of Uniswap liquidity. Engine burns L worth of the pooled full-range position (reducing sL backing proportionally) and credits the resulting token amounts to the vault's idle balance. The vault now owes L back to the lenders.

Repayment works in reverse: the borrower provides the two tokens, Engine mints L worth of full-range liquidity back into the pooled position, and the vault's debt is reduced.

Critically, the borrow and repay are equity-neutral in L: the number of L units owed equals the number of L units destroyed from the shared position. Nothing about the mechanic creates or destroys value in the lender pool.

### What a keeper does

If a vault becomes unsafe — collateral below the required floor — a keeper can call stabilize(v) on the router. This triggers one of two operations:

* **Peel.** If the vault has attached CL or LO NFTs, Engine reduces liquidity on one of those NFTs and routes the resulting tokens back into the vault's idle, improving its C\_min (collateral floor).
* **Micro-liquidation.** If there are no attachable NFTs to peel from, Engine performs a small, TWAP-bounded, AE-owned swap between the vault's two idle balances, then uses the rebalanced tokens to mint L and repay a slice of the debt.

Both operations must improve the vault's health. Both operate under a TWAP guard and a ±N-tick limit. Keepers are paid a small bounty in the vault's currencies.

There is no traditional "auction" liquidation. See Liquidations and Keepers for the full picture.

### What makes it "pool-native"

Three structural choices put Engine in a different category than most lending/margin protocols:

1. **No ERC-20 custody at rest.** At the end of every transaction, AegisEngine holds zero ERC-20 balances. Token movement happens only inside Uniswap v4's PoolManager.unlock frame, using the PoolManager's ERC-6909 claim accounting. A user's deposit is never "in the Engine contract" — it is always either in the PoolManager as part of the shared position, or in the user's own wallet.
2. **Oracle-free solvency.** Collateral adequacy is not a USD valuation. It is a structural check: if the vault's borrowed L were fully withdrawable at the pool's current √K, would the vault's idle balances plus attached NFTs cover the resulting debt? This is the √K floor, and it is computed entirely from on-chain state.
3. **One-pool scope.** A vault is bound to exactly one pool at creation. There is no portfolio netting, no cross-pool collateral, and no cross-pool contagion path. This narrows what Engine can do (no portfolio margin), but it also narrows the failure modes.


# The Big Idea: Turning Liquidity Into Credit

Almost every credit system in DeFi works by stacking two separate markets on top of each other: a trading market that produces a price, and a lending market that uses that price to value collateral. The lender sits across the table from the borrower; a price oracle sits between them; and the whole arrangement only works as long as the oracle honestly reports the trading market.

AEGIS Engine collapses that stack into one layer. Lenders and borrowers interact with the same pool that produces the price, and the price never has to be quoted to anyone — the collateral check reads it off of the pool directly.

This page explains the reframe and the three consequences that fall out of it.

### The reframe: "liquidity" is already a unit

A Uniswap v4 pool's state is captured by two primary quantities:

* √P — the square root of the current price, stored with Q64.96 fixed-point precision.
* L — the liquidity of every position in the pool, stored as uint128.

L has a precise meaning: a position with L = x contributes x \* some-function-of-price tokens to the pool at any given price. When the price moves, the token split of that position changes, but the L itself does not.

Most protocols treat L as a detail of the AMM's internal plumbing. AEGIS Engine treats it as a unit of denomination — the thing you borrow, the thing you owe, and the thing the accounting is measured in.

* A lender who deposits into Engine receives sL shares proportional to the L they added to the pooled position.
* A borrower borrows L directly. Engine burns L worth of the pooled position and hands the borrower the two tokens that come out.
* Debt is tracked in L-units on a per-vault ledger, with interest accrued via a standard compounding borrowIndexWad.
* Repayment mints L back from the two tokens the borrower provides and reduces the debt.

Because every mint and every burn of L goes through the same pooled full-range position, the lender side is always precisely whole: the L owed by the borrow ledger is exactly the L missing from the position. This is the equity-neutrality invariant that sits at the bottom of the design.

### Consequence 1: collateral can be checked without a price

If debt is denominated in L and collateral is denominated in the same pool's two tokens, the question "is this vault safe?" has a structural answer that doesn't require an oracle:

If we minted back all the L this vault owes, using the vault's idle and attached positions at the pool's current √K, would we have enough tokens? And would anything be left over?

This is the √K floor. It is a conservative lower bound on the vault's collateral value, computed directly from:

* The vault's idle token balances.
* The token amounts that would be recovered if every attached CL / LO NFT were burned at the current √P.
* The pool's current √K (the geometric mean of the two pool reserves, in L-units).

No external price enters the computation. No oracle feed can be manipulated to trigger a liquidation at a price the pool isn't actually at. If the pool's √P moves, the floor moves with it — because they are computed from the same state.

This is the single most important property of Engine, and [The Safety Model](http://./the-safety-model.md) covers it in detail.

### Consequence 2: the cascade failure mode doesn't exist

Oracle-based liquidations fail catastrophically when the liquidation process and the oracle are tied together. The pattern that has caused most DeFi credit blowups looks like:

1. Oracle ticks down.
2. Liquidation bots sell borrower collateral at the oracle price.
3. The sell pressure pushes the true market down further.
4. Oracle ticks down again.
5. Go to step 2.

The cascade only terminates when liquidity is exhausted.

In Engine, there is no oracle to tick. The collateral check is structural. When a vault's floor drops below its debt, the keeper operation is a bounded, pool-aware operation — either peeling a slice of one attached NFT, or swapping the vault's own idle balances within a ±N-tick window under TWAP protection. Neither operation is allowed to fail a vault faster than the pool's own price moves, and neither one dumps external sell pressure on the market.

This does not mean Engine is "can't be liquidated." It means the mechanism that connects oracle lag to contagious liquidations is not present. Vaults can still become under-collateralized in sharp adverse price moves; the system's job is to handle that case safely, not to pretend it can't happen.

### Consequence 3: LP shares and debt share the same pool, the same fees, the same interest

Traditional lending and AMM LP'ing are separate economies. In Engine, they are the same economy viewed from two sides:

* Lenders provide tokens → Engine adds full-range L to the pool → trading fees accrue to the pooled position → sL share price rises.
* Borrowers pull L out → Engine credits them tokens → borrowers pay variable-rate interest back into the pool's index → sL share price rises again.

The interest rate is governed by a Frax-inspired VariableInterestRate oracle with a kinked utilization curve. Utilization — the fraction of L in the pool currently borrowed — drives the rate. A configurable protocol fee rakes a share of interest into protocol-owned shares; the rest increases sL share price.

For lenders this means the return source is transparent: trading fees and interest from borrowers of the same pool. For borrowers it means the cost of borrowing is legible: it's the supply-and-demand price of L in their specific pool.

### Why this matters beyond Engine itself

The pool-native approach has implications beyond one protocol:

* It suggests a general pattern for building credit in AMM-based systems: if a debt's unit is the AMM's native liquidity unit, you get solvency-by-construction.
* It suggests a different composition surface: routers, vault strategies, and keepers can now compose borrowing and LP'ing as primitives that share state, rather than as two separate balance sheets that need reconciliation.
* It suggests a cleaner trust boundary: the protocol's honest operation depends on Uniswap v4's correctness and AEGIS's own contract correctness, but not on any external price-reporting party.

The next two pages walk through the concrete mechanics: [How Borrowing Works](/part-2-core-engine-concepts/how-borrowing-works) shows the step-by-step borrow/repay cycle, and [The Safety Model ](/part-2-core-engine-concepts/the-safety-model)shows the √K floor, the LTV/utilization caps, and the keeper guards that together keep the design safe.


# How Borrowing Works

This page walks through the borrowing lifecycle end to end: creating a vault, depositing collateral, borrowing L, and repaying. If you haven't read [The Big Idea](http://./the-big-idea.md) yet, read it first — most of the vocabulary here (L, sL, √K, idle, vault) is introduced there.

The flow is intentionally narrow. A vault is always bound to one pool. Its collateral is always in that pool's two tokens. Its debt is always in L for that pool. There is no cross-pool netting.

### The lifecycle at a glance

&#x20; create vault ─► deposit idle ─► attach NFTs (optional) ─► borrow L

&#x20;                                                             │

&#x20;                                                             ▼

&#x20;                                                      do something

&#x20;                                                      with the tokens

&#x20;                                                             │

&#x20;                                                             ▼

&#x20;                                 repay L ─► detach NFTs ─► withdraw idle ─► (optional) close vault

All of these steps happen through AegisRouterV1. Most integrators will batch several steps into a single transaction using the router's action encoding — see Router and Actions Reference.

### Step 0: the session model

Every state-changing interaction with Engine happens inside a session. A session has three nested layers:

1. Outer transaction. The user calls AegisRouterV1.execute(unlockData, deadline).
2. PoolManager unlock frame. The router calls PoolManager.unlock(...), which gives it access to ERC-6909 currency accounting and delta settlement. At the end of this frame, Uniswap enforces that all deltas net to zero.
3. AE session. Inside the unlock frame, the router calls AegisEngine.aeUnlock(...), which opens an AE session. The session tracks which vaults the caller has unlocked for mutation and guarantees that they are all re-locked before the session ends.

Inside the session, the borrower must call unlockVault(vaultId) before mutating their vault, and lockVault(vaultId) at the end (or leave it locked implicitly — the session epilogue enforces all vaults relocked). lockVault is where the solvency check happens.

Everything that touches the PoolManager — deposit, withdraw, borrow, repay, peel, micro-liq — has to execute inside the unlock frame (Phase-1). NFT attach and detach have to execute outside the unlock frame (Phase-0), because Uniswap's PositionManager requires the PoolManager to be locked for ERC-721 transfers of its NFTs. See Architecture and Contract Roles for the full two-phase story.

### Step 1: create a vault

A borrower creates a vault with createVault(poolKey). This mints an ERC-721 from VaultRegistry; the NFT owner becomes the vault owner. The vault is permanently bound to that pool.

Ownership can be transferred like any ERC-721, subject to one restriction: the vault NFT cannot be transferred while the vault is "unlocked" in an active AE session. This prevents a mid-session transfer from bypassing session-level authorization checks.

### Step 2: deposit idle collateral

Before borrowing, the vault needs collateral. Idle collateral is either token of the pool pair, deposited directly:

* The router takes the tokens from the user via PoolManager.sync + transferFrom + settleFor(AE), which credits Engine with a positive delta in the PoolManager's ERC-6909 accounting.
* Engine records the delta as idle\[vaultId]\[currency] += amount.

At the end of the session, the PoolManager's delta-zero invariant ensures the tokens were actually moved from the user; Engine's session epilogue ensures no stray funding buffer remains.

Optionally, a borrower can attach up to MAX\_NFTS\_PER\_VAULT (currently 4) CL or LO NFTs as collateral. When attached, the NFT's ownership transfers from the user to Engine. Attach happens in Phase-0 and completes a handshake via onERC721Received. Attached NFTs count toward the vault's collateral floor in addition to idle balances.

### Step 3: borrow L

The borrower calls borrowL(vaultId, dL, ...) on the router. Inside the session, Engine does four things:

1. Accrue. First touch of this pool in the session triggers a single feeSync(); accrue(); pass. Fees earned by the pooled full-range position are realized into L; the market's borrowIndexWad advances to "now."
2. Burn L from the pool. Engine removes dL \* M units of liquidity from the pooled full-range position, where M is the scale factor between the market's real and nominal L ledgers (see Shares, Debt, and Core Accounting). This produces two token amounts (x\_out, y\_out) determined by the pool's current √P at the moment of the burn.
3. Credit idle. Engine adds x\_out and y\_out to the vault's idle balances in PoolManager ERC-6909.
4. Book debt. Engine increases the vault's rL principal and the market's totalRlBorrowed by the conservative bridge defined in the L-ledger research (R-0009).

At the end of the session, lockVault(vaultId) recomputes the vault's √K collateral floor and enforces:

* Post-borrow LTV ≤ MAX\_LTV\_PIPS (currently 980,000 / 98%).
* Post-borrow utilization ≤ UTILIZATION\_CAP\_PIPS (currently 950,000 / 95%).

If either is violated, the session reverts and nothing persists.

The two cap values are compile-time constants — not governance-tunable parameters — so the envelope in which borrowing is allowed is fixed and transparent.

### Step 4: use the tokens

At this point the vault holds the (x\_out, y\_out) tokens as idle. The borrower can withdraw them to their own address with withdrawIdle(v, c, amount, to=self). Once withdrawn, the tokens are theirs to use however they want — trade, deposit elsewhere, bridge, whatever. Engine no longer tracks what happens to them.

The vault's debt is the L owed back to the pool, not any specific use of the borrowed tokens. This is why the protocol is infrastructure for applications, not a trading product itself.

### Step 5: repay L

Repayment is the reverse of borrow. The borrower provides tokens (either from idle still in the vault or freshly deposited) and calls repayL(vaultId, dL, ...).

1. Accrue is called on first touch per session (same as borrow).
2. Mint L back into the pool. Engine uses the provided tokens to add liquidity back to the pooled full-range position. The actual L minted depends on the current √P and the token amounts available. Engine reads the actual minted liquidity back from Uniswap v4's return value — it does not guess.
3. Reduce debt conservatively. The vault's rL is reduced by rLEff = floor(liquidityMinted / M). "Conservative" means if there's any rounding ambiguity, the borrower's debt is reduced by at most the amount actually minted, never more.
4. Adjust idle. The tokens used to mint are subtracted from the vault's idle balances.

If the borrower passes dL = 0 as a sentinel, Engine repays the entire outstanding rL. This is the standard way to close out a vault.

A partial repayment is always allowed as long as post-repay LTV passes the cap. Repayment has no utilization check — utilization can only go down on repay.

### Step 6: detach NFTs and close out

If the vault had NFTs attached, the borrower detaches them with detachUserNft(vaultId, nftRef, to). Engine verifies that:

* The caller is the vault owner or approved operator.
* The post-detach LTV still passes the cap.

On success, Engine transfers the NFT back from itself to to. Like attach, detach happens in Phase-0.

Finally, remaining idle can be withdrawn, and the vault NFT can be kept (for future use) or transferred.

### What the borrower pays

Borrowing is not free. Two costs accrue:

* Interest. borrowIndexWad advances whenever the market accrues. The rate is set by a Frax-style variable interest rate oracle that follows a kinked utilization curve: low rate below a target utilization, steep increase above. A configurable marketProtocolFee (bps) directs a share of interest to protocol-owned sL shares; the rest accretes to sL share price (i.e., lenders).
* Swap fees, if the borrower also trades. Tokens borrowed and then used to swap pay the normal dynamic fee on whichever pool they swap through. Engine doesn't charge a separate origination fee.

There is no liquidation penalty as such — see [Liquidations and Keepers](/part-2-core-engine-concepts/liquidations-and-keepers) for how the keeper bounty is structured and why it isn't punitive.

### Edge cases worth knowing

* Borrow then immediately repay, same transaction. Supported. This is a common pattern for keepers and for atomic composed strategies. The session epilogue enforces solvency only at lockVault time, so intermediate states can be temporarily unsafe as long as the final state is not.
* Borrow from one vault and deposit into another, same transaction. Supported via the router's withdrawIdle(..., to=address(0)) sentinel, which keeps the AE positive delta across actions in the same frame. See Router and Actions Reference.
* Borrowing when utilization is already at the cap. Rejected at lockVault time with a utilization-cap error. Lenders can still redeem normally.
* Borrowing with zero idle and only attached NFTs. Allowed, subject to the post-borrow LTV check. The NFTs' liquidation-adjusted token amounts count toward the floor.


# The Safety Model

A credit system is as trustworthy as the worst-case behavior of its solvency model. This page spells out AEGIS Engine's solvency model: the √K collateral floor, the LTV and utilization caps that govern it, and the session-level guards that make the whole thing enforceable.

If you only read one thing on this page, read the next section. Everything else follows from it.

### The √K floor

AEGIS Engine does not value collateral in USD. It values collateral in units of pool liquidity it could be converted into at the current pool state.

Concretely, for a vault v on pool p, the collateral floor C\_min(v) is:

The minimum number of L units that the vault's idle balances plus the burnable output of every attached CL/LO NFT would produce, if minted back into a full-range position at the pool's current √P.

This is what the CollateralFloorMath library computes. It is conservative: it never over-states collateral, and it handles the multi-NFT case with a SAFE bound that remains conservative for every value of N up to MAX\_NFTS\_PER\_VAULT.

A vault is considered solvent if its debt in L is at or below its floor times the LTV cap:

**rL(v) ≤ C\_min(v) × MAX\_LTV\_PIPS / PIPS**

Two observations:

* C\_min is a function of current pool state, specifically √P. When the pool moves, C\_min moves. This is the intended behavior — collateral valued in pool units should track the pool.
* C\_min is deterministic and bounded by on-chain state only. No external oracle, no off-chain input, no trusted reporter enters the formula.

### Why no oracle is needed

The question a lending protocol typically asks is "is this collateral worth more than this debt, denominated in a common unit of account (like USD)?" That question requires a price.

AEGIS Engine asks a different question: "if we minted back the L this vault owes, would the vault's own tokens cover it at the pool's current state?" That question does not require a price — the answer comes directly from the pool's √P and the vault's own token balances.

This is why the protocol can issue credit without an oracle in the solvency path. The trade-off is that debt is denominated in L, not in USD — so a borrower who wants a particular USD exposure has to manage that exposure separately. (That is what borrowers typically do by swapping the borrowed tokens into whatever asset they actually want.)

### The three caps

Three parameters bound how far any single vault or market can lean:

* MAX\_LTV\_PIPS = 980\_000 (98%) — compile-time. A vault can borrow up to 98% of its C\_min. This is a structural ceiling: at 100% LTV, a sharp pool move would leave lenders short, so the cap keeps a small structural buffer.
* UTILIZATION\_CAP\_PIPS = 950\_000 (95%) — compile-time. A market cannot have more than 95% of its L borrowed at any one time. This protects lenders' ability to redeem and the pool's ability to absorb inflows/outflows without hitting edge cases.
* HARD\_LTV\_BPS = 9\_900 (99%) — a second, stricter ceiling used in the liquidation path. A vault cannot be left above 99% LTV even transiently inside the session.

These are enforced at lockVault(v) time — the moment a session ends for a vault. Inside a session, intermediate states can briefly be above the caps, as long as the final state passes. This flexibility lets borrowers run atomic rebalances, repay-then-reborrow, and other multi-step operations without artificial rejection.

A few things deliberately are not parameters:

* The caps are not tunable by governance. They are compile-time constants. Changing them requires a contract upgrade — which, given AEGIS's governance model, is a Timelock operation with visible delay.
* There is no per-pool "risk parameter" that lets governance raise an individual pool's LTV. Risk configuration is in the hook/DFM/policy layer, not in the core solvency check.

### The session guard

The solvency check only works if nothing can bypass it. AEGIS Engine enforces this through the session pattern:

1. Every mutation is gated by unlockVault(v). Only the vault owner or an approved operator, operating inside an active aeStart frame, can unlock a vault.
2. Every session ends with lockVault(v), which must pass the LTV and utilization checks or the entire transaction reverts.
3. The session epilogue enforces that no vaults remain unlocked at session end and no stray pending NFT attachments are left hanging. If either is true, the transaction reverts.
4. The VaultRegistry refuses to transfer a vault NFT while the vault is unlocked in a session. You cannot transfer a half-mutated vault to someone else.

The effect is that there is no way to put a vault into an under-collateralized state at the end of a transaction. You can do whatever you want mid-session, but you cannot commit anything that fails the cap.

### The keeper envelope

If a vault's floor drops below its debt between transactions — because the pool's √P moves and the vault's collateral no longer covers rL × MAX\_LTV\_PIPS / PIPS — a keeper can call stabilize(v). The keeper's toolkit is bounded:

* The operation must improve the vault's state (C\_min\_post > C\_min\_pre for peel, or LTV-must-improve for micro-liq).
* The operation must respect a TWAP guard: any swap happens inside a ±N\_TICKS = ±5 tick window around the current TWAP (TWAP\_WINDOW = 30 minutes).
* The operation auto-selects its input side from the vault's own idle imbalance. Keepers do not get to choose the direction.
* A small bounty (in the vault's currencies) is paid to the keeper. Bounty sizes are capped by compile-time bps constants; see Liquidations and Keepers.

What keepers cannot do:

* They cannot liquidate an already-solvent vault. The entry conditions require the vault to be unsafe.
* They cannot move the pool price outside the TWAP band. The swap reverts if it would.
* They cannot sell the vault's collateral at an arbitrary price on an external venue. All swaps happen on the same pool using the same AMM curve.
* They cannot pile on. A failed keeper call returns zeros cleanly; there is no "retry and try harder" path that compounds into a cascade.

### What the safety model does not protect against

Being honest about limits is part of the model:

* Sharp, sudden price moves that outpace keeper reaction. If the pool moves faster than any keeper can react, a vault can become under-collateralized below the point where a single keeper action can heal it. In that case, repeated micro-liquidations will eventually restore health, but in the meantime rL > C\_min and lenders are exposed to first-loss.
* Uniswap v4 correctness. AEGIS inherits its swap accounting, liquidity math, and ERC-6909 settlement from Uniswap v4. If there is a bug in the v4 PoolManager, AEGIS is affected.
* DFM hook correctness. A pool that uses a custom DFM-compatible hook inherits that hook's behavior. The AE hook allow-list is one-way; approved hooks cannot be removed.
* User key management. Vault NFTs are bearer tokens. If a borrower's key is compromised, the attacker can close out the vault. AEGIS does not add a custodial layer here.

### How the pieces fit together

A concise restatement of the safety model:

Debt is denominated in L. Collateral is measured as the pool-native L that the vault's idle and attached NFTs would produce at the current √P. Solvency is a structural inequality between these two quantities. The session pattern ensures every transaction that commits has to satisfy the inequality. Keepers restore solvency through bounded, pool-aware operations with TWAP protection. No external oracle enters the path.


# Why AEGIS Engine Is Different

A few design choices separate AEGIS Engine from the existing space of lending, margin, and liquidity protocols. This page is deliberately written as a comparison: what the standard approach is, what AEGIS does, and why the difference matters. It is not a marketing page — the claims are mechanism claims, and each one is traceable to specific contract behavior.

### Versus oracle-based lending (Aave, Compound, Morpho)

Standard lending systems take deposits in one asset, borrow against another, and price collateral using an external oracle. An over-collateralized loan becomes liquidatable when the oracle price crosses a threshold; liquidators sell collateral, repay debt, and pocket a discount.

Engine differs in three structural ways:

1. Collateral is priced by the pool itself, not an oracle. The √K floor is computed from √P and the vault's token balances; there is no external feed in the solvency path.
2. Debt is denominated in the pool's liquidity unit L, not an external asset. Borrowing doesn't mean "I owe you $X worth of USDC." It means "I owe you dL units of Uniswap liquidity on this specific pool."
3. Liquidation is pool-aware, bounded, and same-pool. Keepers restore solvency by either peeling an attached NFT or doing a small TWAP-bounded swap on the same pool. There is no external venue to cascade into.

The consequence that matters: the most common DeFi credit failure mode — "oracle lags, liquidators cascade, healthy positions get wiped" — is not present in Engine. This does not make Engine immune to all failures (sharp pool moves can still leave vaults under-collateralized), but the specific feedback loop between oracle latency and liquidation pressure is absent.

### Versus perp DEXes (dYdX, GMX, Hyperliquid)

Perpetual futures give traders leveraged exposure to an asset price. They rely on an oracle or an internal mark price to compute PnL and funding; liquidations are triggered when margin falls below a maintenance threshold.

Engine differs at the product level:

* **Engine is infrastructure, not a trading product.** The output of borrowL is two tokens, not a position with PnL. If a borrower wants leveraged exposure, they have to swap the borrowed tokens into the asset they want — that's a separate action.
* **There is no mark price and no funding rate.** Interest on borrowed L is a variable utilization-curve interest rate, not a basis spread.
* **There is no perpetual carry.** Every position is an open loan that can be closed whenever the borrower chooses (subject to LTV).

The consequence that matters: Engine can be composed into a perp-style product, but the protocol itself doesn't take on the failure modes perp DEXes have (funding manipulation, mark-price attacks, oracle-driven liquidation cascades). An application built on Engine can add whatever mark-price and funding model it wants on top.

### Versus concentrated-liquidity manager vaults (Gamma, Arrakis, Steer)

CL manager vaults actively rebalance Uniswap v3/v4 concentrated positions on behalf of LPs. They add operational value (auto-compounding, band management, tax-efficient rebalances) but they don't change the underlying AMM's fee behavior or add a credit market.

Engine relates to this category differently than it competes with it:

* **Engine's sL is a passive full-range position.** It is not concentrated, it doesn't rebalance, and it does not take a view on price.
* **A CL manager can, in principle, use Engine as a borrow source.** A vault strategy could borrow L, deploy it into a concentrated range, and hedge the delta. Engine is neutral about what borrowers do with the tokens.
* **Engine's fee capture story is via the DFM hook:** dynamic fees + POL compounding happens at the pool level, not at the vault-strategy level. LPs get volatility-aware fees without having to hand their tokens to an active manager.

The consequence that matters: LPs who want passive, full-range exposure with volatility-aware fees and the optional upside of lending-to-borrowers can get all three in one place with sL, while still retaining the option to use a CL manager for a different slice of capital.

### Versus isolated money markets (Morpho Blue, Euler v2)

Modern isolated money markets also limit contagion by scoping collateral to individual markets. This is the closest conceptual neighbor to Engine's "one-pool-scope" property.

Engine differs in that:

* **The isolated market in Engine is always an AMM pool, not an arbitrary asset pair.** Collateral and debt are denominated in the pool's native units.
* **There is no oracle in the solvency path, even inside the isolated market.** Isolated money markets still use an oracle; they just limit the damage if that oracle misbehaves.
* **The collateral itself is productive:** it earns swap fees and lending interest simultaneously because the same pool's L is both the lender's position and the borrower's debt unit.

The consequence that matters: Engine's isolation isn't just a blast-radius limit — it is a structural feature of the solvency math. Contagion across pools is not prevented by a policy knob; it is absent by construction, because a vault on pool A has no account on pool B.

### Versus DFM-only (the AEGIS v1 story)

Readers familiar with AEGIS's earlier work may know DFM — the dynamic fee hook that improves fee capture for LPs — but not the Engine. What does Engine add?

* **DFM is a passive improvement.** It makes LP positions more productive by adjusting swap fees to volatility. Good for LPs, does not change what a pool can do.
* **Engine is an active extension.** It turns the pool's pooled liquidity into a borrowable resource, creating a second source of revenue (interest) for lenders and a new primitive (pool-native credit) for borrowers.

The two are designed to compose. An AEGIS-enabled pool with both DFM and Engine installed offers:

* A volatility-aware fee curve that scales with pool activity.
* Protocol-owned liquidity that deepens the book over time.
* Lender positions (sL) that earn both fees and interest.
* Borrower positions (vaults) that can unlock L from the pool without an oracle.

The consequence that matters: AEGIS is not just "a better fee hook" anymore. It is the base layer for turning any Uniswap v4 pool into a self-tuning, self-lending, self-stabilizing venue.

### Where the differences end

A short list of properties AEGIS Engine shares with the broader DeFi credit space, because it's honest:

* **AEGIS is still exposed to smart-contract risk.** Bugs in Engine, in DFM, in the hook stack, or in Uniswap v4 itself can affect user funds.
* **AEGIS is still exposed to governance risk**. The hook allow-list, the protocol fee bps, and the rate oracle configuration are all under governance control (though behind a Timelock). See Governance and Roles.

These are real risks. They are enumerated in Risk Overview and should not be dismissed. The claim of this page is that Engine removes specific, well-understood failure modes from the design space — not that it removes all risk.

<br>


# Liquidations and Keepers

Most lending protocols answer "what happens when a loan goes bad?" with an auction: sell the collateral on an external venue, use the proceeds to repay the debt, pay the liquidator a discount. AEGIS Engine answers the question differently. There is no auction. There is no external venue. The restoration operation is local to the pool, bounded in size, and gated by a TWAP.

This page explains what actually happens when a vault becomes unhealthy, what tools keepers have, and what their economic incentive looks like.

### When is a vault "unhealthy"?

A vault is unhealthy when its debt in L exceeds its allowed fraction of its collateral floor:

rL(v) > C\_min(v) × MAX\_LTV\_PIPS / PIPS

This can happen between transactions even though every individual transaction had to pass the cap at lockVault time, because C\_min(v) is a function of the pool's current √P. If the pool moves adversely, the floor drops, and a vault that was fine yesterday can be under water today.

A vault is critically unhealthy if it exceeds HARD\_LTV\_BPS = 9\_900 (99%). At this point keepers are allowed to take more aggressive actions.

### The stabilize path

The entry point is AegisRouterV1.stabilize(vaultId, params) (which routes into AegisEngine.peelOrMicroLiquidate(...)). The call has to happen inside an AE session, like all engine mutations, but the keeper does not own the vault — they are operating while the vault is locked, in the keeper lane.

The flow selects between two operations based on vault state:

#### Peel (if attached NFTs exist)

If the vault has at least one CL or LO NFT attached:

1. Engine picks one NFT (rules in CollateralFloorMath + LiquidationMath; conservatively, the one whose liquidity reduction most improves C\_min).
2. Engine calls Uniswap v4 periphery's modifyLiquiditiesWithoutUnlock to decrease liquidity on that NFT by some dL.
3. The two tokens produced by the liquidity reduction are taken into AE's ERC-6909 balance and credited to the vault's idle.
4. The vault's C\_min recomputes; the operation reverts if C\_min\_post ≤ C\_min\_pre.

The effect is that some of the vault's concentrated collateral is converted into idle token collateral at the pool's current √P. Idle balances contribute more efficiently to C\_min than concentrated positions out of range, so the net effect is almost always to improve health.

Utilization is unchanged by peel — no L is minted or burned on the pool.

A small bounty is paid to the keeper in the pool's currencies. Bounty size is controlled by Constants.PEEL\_BOUNTY\_BPS (compile-time).

#### Micro-liquidation (if no NFTs to peel, or peel is insufficient)

If the vault has no attachable collateral to peel, or after peeling the vault is still unhealthy, Engine performs a micro-liquidation:

1. Engine inspects the vault's idle balances. One side will typically be too heavy relative to the other for the pool's current √P.
2. Engine performs an AE-owned, exact-input swap on the pool using the over-weighted side as input. The swap is bounded by:
3. A ±N\_TICKS = ±5 tick limit.
4. The pool's TWAP\_WINDOW = 30 minutes oracle check, which must remain within the allowed band.
5. If the swap partially fills (common under ±5 tick bounds on volatile pools), Engine proceeds with the tokens it did get. If it zero-fills, the call returns without reverting — this is a deliberate design choice so keepers don't spam transactions that revert and burn gas.
6. With the rebalanced idle, Engine mints full-range liquidity into the pooled position and applies the minted L as a debt repayment on the vault. rL decreases; utilization decreases.
7. The operation reverts if the post-state does not improve LTV, or if utilization would exceed the cap.

The keeper receives a bounty in the vault's currencies, capped by Constants.MICRO\_LIQ\_FEE\_MAX\_BPS.

Key invariant: a micro-liquidation always improves LTV when it fills. It never makes a vault worse to pay a bigger bounty.

#### Stabilize as a composite

The router exposes stabilize(v, params) as a single action that internally orchestrates both: peel one NFT if available and worth doing, then micro-liquidate if the vault is still unhealthy. The two operations share a single TWAP read within the session to save gas and to keep the price context consistent.

### What keepers can't do

The bounded design is what makes liquidation safe. Explicitly:

* Keepers cannot choose the swap direction. The micro-liq direction is forced by the vault's own idle imbalance. A keeper cannot direct the pool to move in a favored direction.
* Keepers cannot exceed the TWAP band. The ±N\_TICKS bound and the TWAP\_WINDOW check are enforced by OracleManager and cannot be bypassed by the caller.
* Keepers cannot liquidate solvent vaults. peelOrMicroLiquidate reverts if the vault's LTV is below the cap; there is no "profitable rebalance" path that touches a healthy vault.
* Keepers cannot extract more than the bounty. All tokens flow through PoolManager ERC-6909 accounting with per-currency deltas; keepers receive their bounty via an explicit take(currency, keeper, amount) whose amount is computed from the vault's repayment and the compile-time bps cap.
* Keepers cannot force an external venue to act. All operations happen on the same Uniswap v4 pool the vault is bound to. There is no external DEX, no off-chain auction, no exogenous settlement.

### Keeper economics

Keeper bounties are intentionally small. The reasoning:

* A big bounty is a big haircut for the borrower and a tax on lenders (since it reduces the residual collateral that eventually comes back to the vault).
* A small bounty is sufficient when the operation itself is cheap (a bounded swap on the same pool) and when the liquidation frequency is low (because sharp moves that push vaults under water are rare).
* Keeper gas costs are bounded because the operation is a single PM.unlock call with a small, predictable action set.

Specific bps values are compile-time constants and may be adjusted between deploys:

* PEEL\_BOUNTY\_BPS — bounty for a successful peel, paid in the vault's currencies proportional to the peeled notional. TODO: verify exact value in Constants.sol — current working assumption is 25 bps.
* MICRO\_LIQ\_FEE\_MAX\_BPS — maximum bounty for a successful micro-liq, paid in the vault's currencies proportional to the L repaid. TODO: verify exact value — current working assumption is 50 bps.

The protocol keeps zero of the keeper bounty. All bounty proceeds go directly to the keeper address that called stabilize.

### What borrowers experience

From a borrower's perspective, a successful keeper call looks like:

* Some of your collateral has been converted (peel: NFT liquidity → idle tokens; micro-liq: one-sided idle → L applied to debt).
* Your rL decreased by the amount of debt repaid (micro-liq only).
* A small amount of tokens left the vault as the keeper bounty.
* Your LTV is lower and your vault is healthy again (or, in severe cases, closer to healthy with further stabilize calls pending).

There is no "bad debt socialization" path built into Engine: if a cascade of micro-liquidations cannot close the gap because idle has been exhausted and utilization would exceed the cap, the vault stays under-collateralized until further market moves or deposits change the picture. In that interval, lenders bear first-loss on the uncovered amount.

See How AEGIS Handles Solvency and Liquidation for a deeper treatment of the edge cases and what lenders should expect in worst-case scenarios.

### Running a keeper

Keepers are permissionless. Anyone can watch pool state, compute C\_min and rL for every vault in a market (both exposed via views in AegisStateLibrary), and call stabilize when the inequality flips.

Practical guidance:

* Index via the subgraph. aegis-engine-subgraph exposes per-vault and per-market state for efficient off-chain selection. Polling on-chain state every block for every vault is wasteful.
* Simulate before sending. Both peel and micro-liq can zero-fill (peel if the chosen dL is too small to improve C\_min meaningfully; micro-liq if the TWAP bound is tight). Simulating via eth\_call avoids paying gas for zero-fill calls.
* Respect the ±N\_TICKS window. On volatile pools, micro-liq can revert with InvalidSecondsAgo or similar TWAP errors. If a pool is outside its TWAP band, keepers should wait and retry.
* Don't fight the protocol. There is no benefit to calling stabilize on a solvent vault. The operation reverts, you burn gas, and nothing happens.

For more detail and example opcode batches, see Router and Actions Reference.


# AEGIS for Liquidity Providers

If you are providing liquidity into an AEGIS-enabled pool, there are two related questions worth answering:

1. What exactly am I holding?
2. Where does the return come from?

This page answers both, then walks through the concrete actions (deposit, redeem, monitor) and the specific risks that apply to lenders.

### What you hold: sL shares

When you provide liquidity into an AEGIS Engine pool, you receive sL shares. Specifically:

* sL is an ERC-6909 token issued by AegisEngine itself (the singleton). It is not a separate ERC-20.
* Each pool has its own sL. The ERC-6909 tokenId equals the pool's PoolId.
* Your balance is your claim on that pool's shared full-range position. If the pool's position holds L\_total liquidity and sL\_total shares are outstanding, one of your shares represents L\_total / sL\_total liquidity.

This means sL is pool-scoped, not protocol-wide. You cannot burn pool A's sL against pool B. A user can hold sL in many pools simultaneously; each is independent.

Unlike an ERC-20 pool-share token, ERC-6909 doesn't have a built-in "transferable receipt" UX. The AEGIS app handles this; integrators who want a wrapping layer can build one on top.

### Where the return comes from

Two sources. Both flow through the same sL share price.

#### 1. Trading fees

The pooled full-range position earns swap fees on every trade through the pool. The dynamic fee (computed by DFM) means the fee per swap varies with pool volatility — higher during volatile periods, lower during calm ones. Unlike a vanilla Uniswap v4 position, the fee schedule is not a fixed tier.

Fees realize into L through a deliberate mechanism:

* During a swap, the hook credits accrued fees to the pool's \_pendingFees.
* On the next modifyLiquidity or liquidity-touching operation, the hook reinvests pending fees into the full-range position (subject to MIN\_REINVEST\_LIQUIDITY and a cooldown).
* On reinvestment, Engine counts the reinvested L as new equity, which raises sL share price.

Only reinvested (mintable) fees raise the share price. One-sided residues that cannot be minted into liquidity sit off-equity until the pool's imbalance allows them to mint.

#### 2. Borrower interest

Borrowers pay interest on their rL principal, accruing via borrowIndexWad. This accrues as additional L in the market's equityLWad, which means new share price.

In other words: when a borrower pays interest, part of it goes to the treasury (via protocol-minted sL) and part of it goes to lenders (via raising the existing sL share price).

#### Share price is monotonically non-decreasing

A structural guarantee: under honest operation, sL share price never goes down. Fees and interest only add to L. Redemptions burn sL at the current share price, which means redeemers always get their proportional claim.

The exception — and the one thing a responsible LP should understand — is first-loss on bad debt. If a vault's debt exceeds its collateral floor and keepers cannot fully restore it, the shortfall is borne by sL holders in that pool: equityLWad is reduced by the uncovered L, and share price drops. This is not an oracle-cascade event; it is the normal first-loss property of being the lender.

### Depositing

LP flows go through AegisRouterV1 like all Engine operations. The conceptual flow is:

1. The router calls PoolManager.unlock.
2. Inside the unlock frame, the router calls AegisEngine.aeUnlock.
3. The router takes your two tokens via PoolManager.sync + transferFrom + settleFor(AE).
4. Engine calls modifyLiquidity with a positive delta, minting sL shares to the caller.
5. The session ends, the unlock frame closes, and the PoolManager enforces delta-zero.

In practice, the AEGIS app handles all of this for you. As an LP you:

* Connect a wallet.
* Pick a pool.
* Enter the amounts of token0 and token1 you want to deposit.
* Approve and submit.

There is no minimum lock-up. You can redeem anytime. The protocol charges no deposit fee. You pay network gas and the small cost of the router's atomic flow.

#### Balance requirements

Because the pool is full-range, deposits should be balanced to the pool's current reserves ratio. If you deposit an imbalanced pair, some portion will remain unused as dust — Engine enforces a minimum liquidity threshold (MIN\_LIQUIDITY = 1\_000) and may revert a deposit that produces zero shares.

#### Min-share guard

modifyLiquidity enforces a minimum share mint to prevent dust attacks. If the shares minted would round to zero, the call reverts with ERR\_ZERO\_SHARES.

### Redeeming

Redemption is the reverse: burn sL, withdraw your proportional tokens. Conceptually:

1. Open a session via the router.
2. Call modifyLiquidity with a negative share amount.
3. Engine burns your shares and credits you with the proportional share of the pool's token holdings at the current √P.
4. Router calls take(currency, to, amount) to move the tokens from PM to your address.
5. Session ends.

What can delay a redemption? The utilization cap. If the pool is at 95% utilization — meaning 95% of the pool's L is currently borrowed — redemption is limited to the idle portion until borrowers repay, interest accrues, or additional deposits arrive. This is intentional: it protects the remaining lenders from a run.

In practice this is rare because utilization has to be very high before it starts limiting redemptions, and the rate curve pushes utilization down as borrowers pay more to borrow marginal L.

### Monitoring

Lender-relevant values exposed via AegisStateView / AegisStateLibrary:

* Per-pool: equityLWad (total equity in L, including residues), sL\_totalSupply, totalRlBorrowed, borrowIndexWad, fullUtilizationRate, current utilization.
* Per-LP: sL balance (ERC-6909 balanceOf), implied L share (balance / totalSupply × equityLWad).
* Per-pool fee state: current baseFeePPM, surgeFeePPM, inCap flag.

The AEGIS subgraph (aegis-engine-subgraph) indexes these for easier querying. TODO: add subgraph URL when deployed — see the Live Pools page for the current index location.

### Risks specific to lenders

Beyond the general AEGIS risks (smart contract, governance, Uniswap v4 correctness — see Risk Overview), lenders should understand:

* First-loss on under-collateralized vaults. If a vault's debt exceeds its floor and keepers can't close the gap, sL holders in that pool eat the uncovered L. This is rare, but it is how the math works.
* Impermanent loss, full-range. A full-range LP is exposed to the standard v3/v4 IL curve. In sustained trending markets, token holdings shift. This is not an AEGIS-specific&#x20;
* risk but it applies.
* Utilization-bounded redemption. If the pool is at the cap, redemption is temporarily bounded to idle. You can always redeem something; sometimes the full amount has to wait for borrower repayments or new deposits.
* Pool-scope. A bug or parameter change on one pool's hook does not affect other pools. But a bug in the core Engine affects every pool that uses it.

### What AEGIS does not promise

Here are things AEGIS explicitly does not promise:

* **No guaranteed yield.** Fees and interest depend on pool activity and borrower demand. Some pools may produce little of either; lenders in those pools may earn very little.
* **No insurance fund.** There is no protocol-run backstop that covers bad debt. The risk is borne by lenders in the affected pool.
* **No price-correlated returns.** sL share price moves with pool state, not with any external benchmark. If the pool underperforms, sL does too.

If those trade-offs are acceptable, sL gives you a passive full-range exposure with volatility-aware fees and additional interest from a captive borrower market — a combination that does not exist in vanilla Uniswap.


# AEGIS for Borrowers

If you're going to use AEGIS Engine to borrow, it helps to have a precise picture of what you're actually borrowing and what you owe. This page gives the pragmatic borrower's view, complementing the mechanistic How Borrowing Works.

### What you're borrowing

You are borrowing L — Uniswap v4 liquidity units — from a specific pool. When the borrow is executed, Engine:

1. Burns L worth of the pool's pooled full-range position.
2. Hands you the two tokens that came out of the burn, at whatever √P the pool was at the instant of the burn.
3. Books you for rL debt, which starts accruing interest.

The important thing: you do not borrow "USDC" or "ETH" or any specific asset. You borrow liquidity, and what comes out depends on the pool's current state. In an ETH/USDC pool with √P near 2000, a borrow of dL gives you roughly equal value in each token. As the price moves, the split at the moment of borrow moves with it.

#### Why that's actually useful

This may sound strange at first — "I just wanted to borrow USDC" — but the structure has real advantages for common strategies:

* **Hedged positions.** If you want USD exposure, borrow from an ETH/USDC pool, keep the USDC, and swap the ETH on the open market to lock in your position. The resulting synthetic is indistinguishable from a standard ETH-collateralized USDC loan except it has no oracle risk and no liquidation cascade.
* **Liquidity provisioning.** If you want to LP a different (possibly concentrated) range, borrow and deploy without selling what you have.
* **Atomic rebalancing.** Inside one transaction, borrow, swap, LP, repay — a common pattern for sophisticated strategies.

#### Why that might not be useful

If your actual need is a loan against your ETH to hold a stablecoin, and the relevant pool's volatility means your collateral floor moves with √P, you may find AEGIS noisier than a stable-oracle lender. The trade-off: oracle lenders give you a smoother LTV number, but the oracle itself is the source of the cascade risk that AEGIS doesn't have. Which you prefer depends on what you care about.

### What you owe

Your debt is measured in L, not USD. Every time the pool accrues interest, your rL principal rises at the borrowIndexWad rate. Every time you repay, the amount of L you mint back reduces your debt.

Practical implications:

* Your debt does not track USD. If the pool's √P moves, the USD-equivalent of your debt moves with it. Your collateral also moves with it — they move together because they're both pool-native.
* Interest is variable. The rate is a function of pool utilization. Higher utilization → higher rate. You can read the current rate from the pool's fullUtilizationRate view; the actual rate paid is a standard kinked curve below/above UTIL\_KINK (see the rate oracle docs).
* No fixed-rate option in v1. If you need a fixed rate, you'll need to hedge the variable rate externally. Some integrators build fixed-rate products on top of Engine via rate swaps.

### How much you can borrow

Your maximum borrow is bounded by two caps:

* LTV cap. Your rL cannot exceed 98% of your C\_min. You can borrow up to that ratio, but you probably don't want to — a small adverse pool move can push you over.
* Market utilization cap. Your borrow, combined with all other borrows in the pool, cannot exceed 95% of the pool's total L. If the pool is already near the cap, your borrow is bounded by the headroom.

### The concrete flow

Depending on how you're interacting with Engine:

#### Through the AEGIS app

1. Connect a wallet.
2. Pick a pool. You'll see the pool's current √P, utilization, and rate curve.
3. Create a vault (if you don't have one in that pool).
4. Deposit collateral — either token of the pair, and/or attach Uniswap v4 CL positions you already hold.
5. Enter a desired borrow amount. The app shows you projected LTV, projected utilization, and projected rate.
6. Submit. The router handles session, unlock, borrow, and credit in one transaction.

The borrowed tokens land in your vault's idle. You can withdraw them to your wallet in the same transaction or later.

#### Via the router directly

An integrator batch looks like:

PM.unlock:

&#x20; AE.aeUnlock:

&#x20;   \[Phase-0]

&#x20;   CREATE\_VAULT(poolKey)

&#x20;   ATTACH\_NFT(vaultId, nftRef)        // optional

&#x20;   \[Phase-1]

&#x20;   UNLOCK\_VAULT(vaultId)

&#x20;   DEPOSIT\_IDLE(vaultId, token0, amount0)

&#x20;   DEPOSIT\_IDLE(vaultId, token1, amount1)

&#x20;   BORROW\_L(vaultId, dL)

&#x20;   WITHDRAW\_IDLE(vaultId, token0, amount, to=borrower)  // or keep idle

&#x20;   WITHDRAW\_IDLE(vaultId, token1, amount, to=borrower)

&#x20;   LOCK\_VAULT(vaultId)

See Router and Actions Reference for the full action set and opcodes.

### Managing your vault

#### Day-to-day

* Monitor LTV. Your vault's health depends on C\_min which depends on the pool's √P. Watch the pool.
* Top up if needed. Depositing more idle or attaching more NFTs raises C\_min and drops your LTV.
* Repay partially. Any amount of rL can be repaid. You do not need to close the vault to reduce your debt.

#### Approaching the LTV cap

If your LTV rises toward 98%, you have three options:

1. Deposit more collateral. Idle top-up immediately raises C\_min.
2. Repay some debt. Providing tokens and calling repayL reduces rL and your LTV.
3. Let it ride. If you're confident the pool will revert, you can sit above your target. But if it breaches 98%, keepers can stabilize you at a small bounty cost.

The keeper operation (see Liquidations and Keepers) is not punitive. You pay a small bounty (bps) and your vault ends up healthier. It is explicitly not an auction — you do not lose a large discount to a liquidator.

#### Closing out

To close your vault completely:

1. Ensure enough idle or fresh deposit to repay your full rL.
2. Call repayL(vaultId, dL=0) — the sentinel repays the full outstanding principal.
3. Detach any NFTs back to yourself.
4. Withdraw remaining idle.

The vault NFT persists (it's yours, in VaultRegistry). You can reuse it for future positions on the same pool, or transfer it, or simply leave it.

### Borrower-specific risks

Beyond the universal AEGIS risks (see Risk Overview):

* Your LTV moves with the pool. C\_min is a function of √P. A sharp adverse move increases your effective LTV without you doing anything. This is the expected behavior — your collateral is priced in pool units that track the pool — but it means you have to monitor or use automation.
* Keeper bounty on your vault if you breach. If you cross the cap, a keeper will call stabilize. You pay a small bounty cost and your position is partially restored. Not punitive, but not free.
* Utilization-affected rate. If utilization in your pool spikes (e.g., during a volatile period), your interest rate spikes too. Rates above the kink point are steep.
* Swap-side execution risk. If you use borrowed tokens to open a broader position (swap into something else, LP elsewhere), that execution is separate from Engine — it's subject to its own slippage, routing, and counterparty risks.

### What borrowers typically do

Without editorializing: the common patterns integrators have built on Engine include:

* Looped LP — borrow L, deploy into a concentrated range in the same pool or a different pool, earn fees, hedge externally.
* Synthetic token exposure — borrow, swap one side away to concentrate into the asset you want, repay when the trade is complete.
* Cross-pool arbitrage — borrow liquidity from one pool, use it to execute arbitrage that doesn't require your own balance sheet.
* MEV and sandwich avoidance — borrow at the moment of a planned trade instead of signaling demand by pulling liquidity, then repay.

Engine is intentionally a primitive. What you do with it is your choice; the protocol doesn't have opinions.


# Integrating AEGIS Engine

This page is for builders: developers writing routers, vault strategies, keepers, aggregators, front-ends, or any other code that calls AEGIS Engine programmatically.

The target audience is someone who has read What is AEGIS Engine? and How Borrowing Works and now wants to write code.

### The mental model for integrators

AEGIS Engine is a two-layer stack:

* Core: AegisEngine (singleton) + supporting managers (OracleManager, LimitOrderManager, VaultRegistry, DynamicFeeManager, PoolPolicyManager).
* Periphery: AegisRouterV1.

In v1, almost all integrations should go through AegisRouterV1. The core is designed to be composed by the router, not called directly from EOAs or arbitrary contracts. Direct engine calls are possible but require you to re-implement the session and PM.unlock orchestration yourself.

Inside the router, the unit of work is an ExecutionBatch: an ordered list of action opcodes with a requiresV4Unlock flag. The router can execute multiple batches in sequence, and can split work between Phase-0 (PM locked) and Phase-1 (PM unlocked).

### Key patterns

#### 1. Phase-0 and Phase-1

The Uniswap v4 PositionManager restricts ERC-721 transfers of its NFTs to when the PoolManager is locked. Engine's NFT attach and detach therefore have to run in Phase-0 — outside the PM.unlock frame.

Conversely, anything that touches the PoolManager (deposit, withdraw, borrow, repay, peel, micro-liq) has to run in Phase-1 — inside PM.unlock.

The router exposes execute2Phase(pre, actions, deadline) where pre is the Phase-0 batch and actions is the Phase-1 batch. Inside the router they are processed in order, with a single PM.unlock transition between them.

If you mix up the phases, the call reverts with ManagerLocked or with the NFT transfer failing. Check requiresV4Unlock on your batch carefully.

#### 2. Sentinel batching

Often you'll want to pass value between actions without settling through ERC-20. The router supports this via the to = address(0) sentinel on withdrawIdle:

\[Phase-1]

WITHDRAW\_IDLE(vaultA, token0, amount, to=address(0))  // AE retains positive delta

DEPOSIT\_IDLE(vaultB, token0, amount, ...)              // consumes the delta

The PoolManager enforces delta-zero at frame end, but within the frame AE can hold a positive delta across actions. This is how cross-vault rebalances, borrow-and-redeploy, and atomic repay-from-another-position all work.

Similarly, TransientNftTracker lets the router address the "last attached" / "last minted" NFT via sentinels (USE\_LAST\_NFT) so that composed calls don't need to know IDs statically.

#### 3. Session discipline

Every vault mutation requires the vault to be unlocked in the current AE session by a caller authorized per VaultRegistry. Explicit pattern:

UNLOCK\_VAULT(vaultId)

&#x20; // all mutations on vaultId

LOCK\_VAULT(vaultId)  // enforces caps

A session can have multiple vaults unlocked simultaneously (typically rare, but supported for cross-vault flows). All unlocked vaults must be re-locked before the session ends or the transaction reverts with ERR\_VAULTS\_NOT\_LOCKED.

lockVault is where the LTV and utilization caps are enforced. If you skip LOCK\_VAULT, the engine session epilogue calls it implicitly, but you should include it explicitly to make the call flow legible.

#### 4. First-touch accrual

The first PM-touching action on a given pool in a session triggers a feeSync(); accrue(); pass. Subsequent same-pool actions in the same session reuse the cached accrue result via EIP-1153 transient storage. This means a batch of three deposits into three different vaults of the same pool only pays accrue gas once.

You don't have to do anything to opt into this — it happens automatically. But if you're gas-profiling, it explains why "first action in batch" is more expensive than later same-pool actions.

#### 5. Pre-fund, don't operator-pull (v1)

AegisRouterV1 uses a pre-fund model: the router calls PoolManager.sync → transferFrom(user → PM) → settleFor(AE). AE then consumes the resulting positive delta on its ERC-6909 balance.

This means:

* Users approve the router (or Permit2), not the engine.
* The engine never needs any approval from the user.
* Operator-pull (where AE pulls from the router's PM ERC-6909 via PM.burn) is reserved for future router versions (operatorPull = true flag). Pass false in v1.

If you build a custom router, stick with the pre-fund pattern unless you deeply understand the operator-approval surface.

### The action set

A condensed list of the most useful router actions. See [Router and Actions Reference](http://../07-reference/router-and-actions-reference.md) for opcodes and exact calldata encoding.

Vault lifecycle (AE actions):

* AE\_CREATE\_VAULT(poolKey) — mints a vault NFT, returns vaultId.
* AE\_UNLOCK\_VAULT(vaultId) / AE\_LOCK\_VAULT(vaultId) — session begin/end per vault.
* AE\_MODIFY\_IDLE(vaultId, currency, deltaInt) — deposit (+) or withdraw (−) idle.
* AE\_MODIFY\_DEBT(vaultId, dL\_signed) — borrow (+) or repay (−). Sentinel: dL=0 on repay repays full rL.
* AE\_ATTACH\_NFT(vaultId, nftRef) / AE\_DETACH\_NFT(vaultId, nftRef, to) — custody.
* AE\_PEEL\_OR\_MICRO\_LIQUIDATE(vaultId, params) — keeper lane.
* AE\_MODIFY\_LIQUIDITY(vaultId, sharesDelta) — LP deposit/redeem on sL (minting or burning shares).
* AE\_FLUSH\_POSITIVE\_DELTA(currency, to) — governance-only; drains a stuck positive delta.

Limit order actions (LO actions): LO\_PLACE\_ORDER, LO\_CANCEL\_ORDER, LO\_WITHDRAW\_ORDER, LO\_MODIFY\_LIQUIDITIES.

Uniswap v4 actions (CL actions): mint, increase/decrease liquidity, collect, burn — these are standard v4 PositionManager operations encoded inline.

Settlement helpers (from BaseActionsRouter): SETTLE, TAKE, SETTLE\_ERC6909, TAKE\_ERC6909 — move value between AE, PM, and the user.

### Reading state

Off-chain integrations should use:

* AegisStateView — a read-only wrapper exposing per-vault and per-market state (rL, idleBalance, cMin, borrowIndexWad, equityLWad, totalRlBorrowed, sL\_totalSupply, etc.).
* AegisStateLibrary — library functions that compute slots for extsload lookups. Useful for building custom indexers or fast on-chain reads.
* aegis-engine-subgraph — the reference subgraph implementation indexing markets, vaults, and events.

For dynamic fee state, read DynamicFeeManager.getLatestFeeQuote(poolId) and getFeeState(poolId). For oracle observations, OracleManager.observe(poolKey, secondsAgos) or consult(poolKey, secondsAgo).

### SDK

aegis-engine-sdk provides TypeScript bindings, common action builders, and helpers for encoding ExecutionBatch arrays. It is the easiest way to call the router from a front-end or from Node-based automation.

import { AegisClient } from '@labs-solo/aegis-engine-sdk'

const client = new AegisClient({ rpcUrl, chainId })

const batch = client.batch()

&#x20; .createVault(poolKey)

&#x20; .depositIdle(vaultId, token0, amount0)

&#x20; .depositIdle(vaultId, token1, amount1)

&#x20; .borrowL(vaultId, dL)

&#x20; .withdrawIdle(vaultId, token0, amount0Out, userAddress)

&#x20; .lockVault(vaultId)

&#x20; .build()

await client.execute(batch, { deadline })

TODO: Confirm SDK public API before v1.0 — the above is representative but the method names are not yet locked.

### Keeper patterns

For keepers, the essential loop is:

1. Index all markets with at least some totalRlBorrowed > 0.
2. For each market, index all vaults with rL > 0.
3. For each vault, compute current C\_min using the pool's current √P and the vault's idle + attached NFT positions.
4. If rL > C\_min × MAX\_LTV\_PIPS / PIPS, submit stabilize(vaultId) as a keeper transaction.
5. Handle zero-fill gracefully (the operation returns cleanly rather than reverting).

TODO: Link to an example keeper implementation once one is published in a public repo.

### Gas profile

Approximate gas figures (cold, mainnet-style gas prices) for the main actions, TODO: confirm against deployed gas report before mainnet launch:

* Create vault: \~150k
* Deposit idle: \~90k (first touch per pool per session, adds \~40k accrue)
* Borrow: \~250k (first touch adds accrue)
* Repay: \~220k
* Stabilize (peel): \~180k
* Stabilize (micro-liq): \~200k

Batching helps significantly: a create-deposit-borrow-withdraw sequence is typically \~50-80k cheaper than the individual calls because accrue runs only once and the PM.unlock cost is amortized.

### What to be careful about

A short checklist from integrators who have done this before:

* Don't forget LOCK\_VAULT. Failure to relock at session end causes the entire transaction to revert. The session epilogue will catch this, but making it explicit is clearer.
* Approvals go to the router, not the engine. Users who approved AegisEngine directly find their transactions fail.
* Phase-0 is for attach/detach only. Putting a DEPOSIT\_IDLE in the Phase-0 batch reverts with ManagerLocked.
* Vault NFT transfers are session-gated. Don't try to transfer a vault mid-mutation; \_update on VaultRegistry reverts if the vault is unlocked.
* Borrow against current pool state. The token amounts you receive on BORROW\_L depend on the pool's √P at the moment of the burn. If you're swapping-and-redepositing atomically, this is fine; if you're building a UX that pre-quotes amounts, refresh your quote close to submission.
* Dynamic fees apply to swaps. If you swap in an AEGIS-enabled pool as part of your flow, the fee paid reflects DFM's current quote — not a static tier. This matters for strategy PnL modeling.

### Reference deployments

Deployed contract addresses for the current supported networks are listed in Deployed Contracts.

If you are integrating ahead of mainnet, the testnet deployment uses the same addresses across Ethereum Sepolia, Base Sepolia, Optimism Sepolia, and Unichain Sepolia (by deterministic CREATE2). Refer to the AegisDependencies.sol contract and the script/Create2Deploy.s.sol script in the aegis-engine repo for the 4-phase deterministic deployment flow.


# System Architecture and Contract Roles

This page describes the AEGIS Engine contract system from a structural standpoint — what the contracts are, how they're related, and what each one is responsible for. It is deliberately terse where the Statement of Intended Behavior (in the aegis-engine repo) is authoritative, and refers there for details.

### The three-layer model

┌─────────────────────┐

│     AegisRouterV1 (Periphery)                              │

│  • Batch orchestration                                         │

│  • Action routing                                                  │

│  • Pre-funding management                               │

└──────────┬──────────┘

&#x20;                                        │

┌──────────▼──────────┐

│      AegisEngine (Core Singleton)                        │

│  • Vault lifecycle (ERC-721)                                 │

│  • sL shares (ERC-6909)                                     │

│  • L-unit ledger                                                     │

│  • √K solvency checks                                         │

│  • Session management                                       │

└──────────┬──────────┘

&#x20;                                       │

┌──────────▼──────────┐

│    PoolManager (Uniswap V4)                             │

│  • AMM state                                                        │

│  • ERC-6909 token accounting                          │

│  • Delta settlement                                              │

└──────────┬──────────┘

Three invariants bridge the layers:

* Router orchestrates PM.unlock; engine orchestrates its own AE session nested inside.
* Engine never touches PM outside an unlock frame. Any such attempt reverts with ManagerLocked.
* Engine never holds ERC-20 balances at rest; all value sits either in the user's wallet or in the PoolManager's ERC-6909 accounting.

### Core contracts

#### AegisEngine (singleton)

The ledger. Owns:

* Markets (\_markets\[PoolId → Market.State]): per-pool state including totalRlBorrowed, borrowIndexWad, equityLWad, rateOracle, fullUtilizationRate, and the PoolKey.
* Vaults (\_vaults\[vaultId → Vault.State]): per-vault state including poolId, idleBalances\[currency], rL, and the set of attached positions\[].
* sL shares via inherited ERC6909ClaimsWithSupply — tokenId = PoolId, with per-tokenId totalSupply tracked.
* Session state in EIP-1153 transient storage: active vault count, per-vault session operator, pending NFT attach digest, cached TWAP, cached fee growth, accrual flags.
* Hook allow-map: hookAllowed\[IHooks → bool], one-way.

Key interfaces: IAegisEngine, IEngineStartCallback.

#### AegisRouterV1 (periphery)

The canonical periphery. Inherits BaseActionsRouter, DeltaResolver, ReentrancyLock, Permit2Forwarder, NativeWrapper. Responsibilities:

* execute(bytes unlockData, uint256 deadline) is the user entrypoint.
* Calls PoolManager.unlock and handles the unlockCallback.
* Starts the AE session via AegisEngine.aeStart and handles aeStartCallback.
* Decodes ExecutionBatch\[] and dispatches action opcodes via \_handleAction.
* Resolves sentinel NFT IDs via TransientNftTracker for composability.
* Wraps Permit2 for approval-free flows (when the user has approved Permit2).

Key interfaces: IAegisRouterV1, AegisRouterActions.

#### AegisHook

The Uniswap v4 hook. One per deployment. Installed on pools to enable AEGIS behavior.

Responsibilities:

* beforeSwap: quotes the dynamic fee via DynamicFeeManager.prepareSwap; adjusts oracle cardinality; stores swap context in EIP-1153 transient storage (HookRuntime).
* afterSwap: finalizes the fee via DynamicFeeManager.finalizeSwap; updates pool-pending fees (\_pendingFees\[poolId]); reinvests into the protocol-owned full-range position when thresholds (MIN\_REINVEST\_LIQUIDITY, REINVEST\_COOLDOWN) are met.
* beforeAddLiquidity / beforeRemoveLiquidity: maintain oracle state; passive on fee accounting.
* Implements IAegisHook; inherits from Uniswap v4's BaseHook, Ownable, Pausable.

External calls (to DynamicFeeManager, OracleManager, AegisEngine) are wrapped in try/catch so that a buggy or paused downstream manager emits ExternalCallFailed rather than bricking the hook.

#### VaultRegistry

An ERC-721 contract that issues vault NFTs. Minted only by AegisEngine. Transfers are blocked by a \_update override when the vault is unlocked in an active session.

### Supporting managers

#### DynamicFeeManager

Maintains per-pool dynamic fee state. Implements IDynamicFeeManager. Hook surface:

* initialize(poolKey) — called once per pool from the hook on afterInitialize.
* prepareSwap(poolKey, params) / finalizeSwap(...) — called from before/after swap.
* getFeeState(poolId) → (base, surge) — view for integrators.

Admin surface: authorizeHook, refreshPolicyCache, setAutoTunePaused.

#### PoolPolicyManager

Read-only registry from the hook stack's perspective. Stores per-pool policy:

* Manual fee overrides (if governance wants to pin a fee).
* Dynamic fee parameters (min/max base fee, surge multiplier, decay period).
* Budgets and cap bounds for the CAP event mechanism.
* Fee splits between LP, protocol, and POL vault.
* Tick-scaling and tick-spacing whitelist.

Admin: onlyOwner setters with parameter range validation via PolicyValidator and PolicyManagerErrors.

#### OracleManager

Per-pool truncated TWAP oracle. Stores observations (ring buffer via TruncatedOracle) and metadata in states. Hook surface:

* recordAfterInitialize(poolKey) — seed the oracle.
* beforeModifyLiquidity, afterSwap — record observations.
* increaseCardinalityNext(poolKey, cardinality) — grow the buffer.

Integrator surface: observe, consult. Rejects unauthorized hooks; rejects secondsAgo = 0 and stale data.

#### LimitOrderManager

Bucket/epoch-based limit order book, integrated as ERC-721 NFT-wrapped order positions. One-tick buckets; approved hooks must call on tick-cross to fill/knock out bucket liquidity. AE treats LO NFTs uniformly with CL NFTs in the √K floor computation.

Router-style entrypoints: modifyLiquidities / modifyLiquiditiesWithoutUnlock, \_place, cancel, withdraw, settlement helpers.

#### VariableInterestRate

Frax-inspired rate oracle with a kinked utilization curve. getNewRate(utilizationPips, elapsed) → (newRate, newFullUtilRate). Construction validates parameter ranges; getNewRate clamps and reverts on invalid inputs.

Per-market configuration: each market can have its own rate oracle via setPoolRateOracle(poolId, addr), falling back to \_defaultRateOracle.

### Two-phase execution

Engine splits router programs into two phases within a single transaction:

#### Phase-0 (PM locked)

Permitted actions: CREATE\_VAULT, UNLOCK\_VAULT, LOCK\_VAULT, ATTACH\_NFT, DETACH\_NFT. These do not touch the PoolManager.

The restriction: Uniswap v4's PositionManager requires PM locked for ERC-721 transfers of CL NFTs. Attach and detach rely on this, so they can't be in Phase-1.

First-touch accrue does NOT fire in Phase-0.

#### Phase-1 (PM unlocked)

Permitted actions: everything else (idle modifications, borrow/repay, peel, micro-liq, modifyLiquidity for sL, limit order operations, settlement).

The first PM-touching action on a given pool in Phase-1 triggers feeSync(); accrue(); exactly once. Cached via EIP-1153 for the remainder of the session.

If a Phase-1-only action appears in a Phase-0 batch, the PM call reverts with ManagerLocked.

### Session lifecycle

user → RouterV1.execute(unlockData, deadline)

&#x20;  → PM.unlock(unlockData)

&#x20;     ↳ RouterV1.unlockCallback(data)           // onlyPoolManager

&#x20;         ↳ AE.aeUnlock(userData)               // opens AE session

&#x20;              ↳ RouterV1.aeUnlockCallback(...) // decode & run actions

&#x20;         ↩ AE epilogue: require unlockedCount == 0

&#x20;     ↩ PM enforces frame-wide delta-zero

Invariants:

* aeUnlock requires the caller to be the router (the pre-registered callback address).
* aeUnlockCallback runs the action batch. Actions can unlock/lock multiple vaults; the session tracks an unlockedCount.
* The AE epilogue requires unlockedCount == 0 at session end. Any still-unlocked vault causes revert.
* The AE epilogue also requires no stray pending NFT attach intent remains.
* The PM epilogue (unlock return) requires the PM's frame-wide NonzeroDeltaCount == 0.

Together these three checks ensure every session ends in a clean state: no mid-mutation vaults, no orphaned NFT intents, no lingering deltas.

### The keeper lane

Most actions require the caller to be the vault owner or an approved operator. Keeper actions are a separate lane:

* PEEL, MICRO\_LIQ, STABILIZE do not require caller authorization on the vault.
* They do require the vault to be locked at call start (they operate on a locked vault by design).
* They still run inside Phase-1 of a PM.unlock frame and an AE session.

This split lets keepers operate without needing per-vault permissions, while preserving session discipline.

### What's constant vs. what's tunable

A short summary of the knob inventory (see Governance and Roles for the full picture):

Compile-time constants (in contracts/libraries/ae/Constants.sol):

* TWAP\_WINDOW\_SECONDS = 30 minutes
* UTILIZATION\_CAP\_PIPS = 950\_000 (95%)
* MAX\_LTV\_PIPS = 980\_000 (98%)
* HARD\_LTV\_BPS = 9\_900 (99%)
* MIN\_LIQUIDITY = 1\_000
* MAX\_NFTS\_PER\_VAULT = 4
* N\_TICKS = 5 (keeper swap band)
* L\_MIN\_TO\_MINT = 1\_000
* PEEL\_BOUNTY\_BPS, MICRO\_LIQ\_FEE\_MAX\_BPS — TODO: enumerate exact values from Constants.sol

Changing these requires a contract redeploy.

Governance-tunable (via Timelock):

* Hook allow-list on AE, OracleManager, LOM, DFM — one-way (approvals are permanent).
* Default and per-pool rate oracle (setDefaultRateOracle, setPoolRateOracle).
* Per-market protocol fee bps (setMarketProtocolFee, bounded 0-10,000 bps).
* DFM policy parameters (fee ranges, surge multipliers, CAP budgets, fee splits).
* DFM per-pool hook fee ppm.

Permissionless:

* Market initialization (AegisEngine.initialize(poolKey)) — anyone can initialize a pool market, provided the hook is authorized and the pool has been initialized on Uniswap v4.
* Vault creation.
* LP deposit/redeem, borrow/repay, attach/detach.
* Keeper stabilize.

### CREATE2 deterministic deployment

AEGIS uses a 4-phase CREATE2 deterministic deployment so that the same code at the same addresses can exist on multiple chains:

* Phase 1 (permissionless): deploy the dependencies bundle (AegisDependencies.sol).
* Phase 2 (trusted deployer): deploy the core contracts (AegisEngine, AegisRouterV1, VaultRegistry, etc.) — requires the AEGIS\_INITIALIZER key.
* Phase 3 (permissionless): deploy the hook and managers.
* Phase 4 (trusted deployer): initialize, authorize hooks, set default rate oracle — requires the AEGIS\_INITIALIZER key again.

The practical effect: if you know the chain ID and the deployer's address, you can compute every AEGIS address in advance. See script/Create2Deploy.s.sol in the aegis-engine repo.

***

For the accounting details — what rL, sL, L, M, borrowIndexWad are exactly, and how they evolve — see [Shares, Debt, and Core Accounting](http://./shares-debt-and-core-accounting.md).

***

## Shares, Debt, and Core Accounting

This page is the accounting cheat sheet. It is compact; anyone implementing against Engine should read it alongside the relevant sections of the Statement of Intended Behavior and the research notes on the L-unit ledger and √K floor.

### The four quantities

Everything in Engine boils down to four per-pool / per-vault quantities:

* L — Uniswap v4 liquidity units on the pool's pooled full-range position. Quantified as uint128 on-chain.
* sL — ERC-6909 lender shares, scoped to tokenId = PoolId. Per-tokenId totalSupply is tracked by ERC6909ClaimsWithSupply.
* rL — borrower real-L principal on a vault. Stored in WAD precision (rLWad).
* M — scale factor between the market's real and nominal L ledgers, per R-0009. Used to convert between real on-pool L and ledgered L.

### Market state

Market.State (in libraries/ae/market/Market.sol) carries:

* bool initialized
* uint128 totalRlBorrowed — sum of rL across all vaults in this market, nominal.
* uint128 borrowIndexWad — compounding index starting at 1e18.
* uint128 equityLWad — total equity in the pooled full-range position, in L-WAD.
* IVariableInterestRate rateOracle — per-pool rate oracle, or zero to fall back to default.
* uint64 fullUtilizationRate — cached full utilization rate, updated on accrue.
* PoolKey key — the pool this market is bound to.
* Residues residues — one-sided fee residues held off-equity until mintable.

### Vault state

Vault.State (in libraries/ae/vault/Vault.sol) carries:

* PoolId poolId — immutable after creation.
* mapping(Currency => int256) idleBalances — signed, allowing temporary intermediate negatives inside a session.
* uint128 rLWad — debt principal in L-WAD.
* NftSet positions — the set of attached CL/LO NFTs, capped at MAX\_NFTS\_PER\_VAULT.

### Share price

sL share price is a simple ratio of equity to supply:

sharePrice = equityLWad / sL\_totalSupply

Two important properties:

* Monotonically non-decreasing in normal operation. Fee reinvestment and interest accrual only add to equityLWad. Lender deposits mint new shares at the current price, so price doesn't dilute.
* Can drop only in one case: if a vault's debt exceeds its collateral floor and keepers cannot fully heal it, the uncovered L is debited from equityLWad, reducing the share price. This is first-loss, borne only by sL holders of the affected pool.

### Borrow / repay accounting

When a borrower calls borrowL(v, dL):

1. Accrue (if first-touch this session per pool) — advances borrowIndexWad by the current rate over the elapsed time, and reinvests mintable fees into equityLWad.
2. Burn full-range L — remove dL × M units of liquidity from the pooled position. Receives (idle0Added, idle1Added) tokens from Uniswap v4, which are added to the vault's idle.
3. Increase debt — market.totalRlBorrowed += dL; vault.rLWad += dL × borrowIndexWad / 1e18.
4. Update market — equityLWad -= dL × M (the burned liquidity leaves equity), net of any fee residues realized during the pre-burn accrue.

The repay path is symmetric:

1. Accrue (first-touch) — same as borrow.
2. Mint full-range L from the provided tokens. Engine reads the actual liquidityMinted back from Uniswap v4.
3. Compute rLEff = floor(liquidityMinted / M). This is conservatively the lower bound on L-units created, ensuring debt is never reduced by more than what was actually minted.
4. Reduce debt — market.totalRlBorrowed -= rLEff; vault.rLWad -= rLEff × borrowIndexWad / 1e18 (clamped at zero).
5. Update market — equityLWad += rLEff × M.

#### The conservative bridge

The reason for M and for floor-rounding: borrowing and repaying happen on a real pool that has continuous-value liquidity math, but the engine ledger tracks debt in discrete nominal L units. The bridge is designed so that:

* Borrow rounds down the real L removed from the pool relative to the nominal dL credited as debt.
* Repay rounds up the real L added to the pool relative to the nominal dL removed as debt.

Together these ensure equity-neutrality as a strict inequality: the real L in the pool is always at least the nominal L recorded in the ledger. The rounding discipline is enforced by LUnitMath and Market.sol.

### Interest accrual

Interest is handled via a single compounding borrowIndexWad:

borrowIndexWad\_new = borrowIndexWad\_old × (1 + rate × dt)

where rate is the per-second rate from the market's rate oracle and dt is seconds since last accrue.

A borrower's real debt is:

realDebt\_L = rLWad × borrowIndexWad / 1e18

Both rLWad and borrowIndexWad persist in storage; the product is computed on-demand when needed (for LTV, repay, or view).

The rate oracle returns two values: newRate (per-second, 1e18 scale) and newFullUtilRate (cached and stored as market.fullUtilizationRate). The Frax-style curve has two regions: below UTIL\_KINK (linear slope upward with time adjustment), above UTIL\_KINK (steep slope).

#### Protocol fee

On every accrue that advances the index, Market.accrue splits the interest earned:

* Protocol share: interestL × marketProtocolFeeBps / 10\_000 minted as new sL to the protocol address (via \_mintProtocolShares).
* Lender share: the remainder flows into equityLWad, raising the sL share price for existing holders.

marketProtocolFeeBps is per-market, bounded 0-10,000, tunable by owner via setMarketProtocolFee. Initial value is chosen at deployment time (TODO: confirm default — working assumption is 500 bps).

### Collateral floor (√K)

Given a vault v on pool p at current √P:

C\_min(v) = sum over {idle balances, attached CL NFTs, attached LO NFTs} of L-burn-value

* Idle balances contribute their geometric-mean-based √K value: √(idle0 × idle1) converted to L-units.
* Attached NFTs contribute the L they would produce if burned at the current √P. For CL NFTs, this uses the NFT's liquidity, tickLower, tickUpper relative to the current tick. For LO NFTs (single-tick buckets), the contribution depends on whether the tick has been crossed.

The SAFE multi-NFT bound (from R-0008) ensures C\_min is conservative for any N ≤ MAX\_NFTS\_PER\_VAULT. The implementation is in CollateralFloorMath.evaluateRaw and TickSetCodec.

The solvency inequality:

rLWad × borrowIndexWad / 1e18 ≤ C\_min × MAX\_LTV\_PIPS / 1\_000\_000

Enforced at lockVault(v) time. A vault in breach after session end causes the transaction to revert.

### Share mint and burn

modifyLiquidity(poolId, sharesDelta, ...):

Mint (sharesDelta > 0):

1. Compute tokens required at current share price.
2. Take tokens from the user via PoolManager's ERC-6909 (pre-funded by the router).
3. Add full-range liquidity to the pooled position (\_poolManager.modifyLiquidity with positive delta).
4. Mint sharesDelta sL to the caller (ERC-6909 \_mint).
5. Update equityLWad by the actual L added.
6. Enforce sharesDelta ≥ MIN\_LIQUIDITY (or revert ERR\_ZERO\_SHARES).

Burn (sharesDelta < 0):

1. Compute L to remove as equityLWad × |sharesDelta| / sL\_totalSupply.
2. Remove liquidity from the pooled position.
3. Burn sL (ERC-6909 \_burn).
4. Credit the user with the resulting tokens via PoolManager.

### Fee accrual mechanics

Fees are earned passively by the pooled position as Uniswap swaps occur. Engine's fee model:

* The hook accumulates pending fees in \_pendingFees\[poolId] (per currency).
* On modifyLiquidity or qualifying reinvest triggers, pending fees above MIN\_REINVEST\_LIQUIDITY are minted back into the pooled position via Market.mintEquityFromFees.
* Only mintable fees count. If the pool is imbalanced such that the fees are single-sided, the one-sided residue stays in market.residues until a future fee collection pairs it up.

This deferral is important: it prevents dust-level fee events from triggering expensive modifyLiquidity calls on every swap.

### The two sets of units

A practical reference for integrators:

| Quantity                          | Type    | Unit                          | Precision |
| --------------------------------- | ------- | ----------------------------- | --------- |
| Token amounts                     | uint256 | wei (18 dec for most ERC-20s) | native    |
| Pool liquidity                    | uint128 | L-units                       | 0 dec     |
| Pool √P                           | uint160 | sqrt price                    | Q64.96    |
| Pool √K (geom mean reserves in L) | uint256 | L-WAD                         | 1e18      |
| WAD numbers                       | uint256 | unitless                      | 1e18      |
| BPS                               | uint256 | unitless                      | 1e4       |
| PIPS                              | uint256 | unitless                      | 1e6       |
| Tick                              | int24   | ticks                         | native    |
| Borrow index                      | uint128 | unitless compound index       | 1e18      |

Rounding discipline: floor on mint, floor on share-price computations; ceil on burns where it protects remaining holders.

### Events

Core Engine events (summary — see spec 0100 §11 for the full list):

* VaultCreated(vaultId, poolId, owner)
* IdleDeposit(vaultId, currency, amount) / IdleWithdraw(vaultId, currency, amount, to)
* BorrowL(vaultId, poolId, dL, idle0Added, idle1Added)
* RepayL(vaultId, poolId, dL, rLEff, xUsed, yUsed)
* FullRangeLiquidityChanged(poolId, liquidityDelta, amount0Used, amount1Used)
* Peel(vaultId, nftKind, tokenId, dL, cMinPre, cMinPost, bountyPaid)
* MicroLiq(vaultId, rL, tickLimit, twapOk, ltvPre, ltvPost, zeroForOne, amountIn)
* UserNftAttached(vaultId, nftKind, tokenId) / UserNftDetached(...)
* HookApproved(hook, allowed)

All numeric event fields carry units in NatSpec.

***

This page summarizes the core accounting. For the authoritative math, read R-0008 (√K static collateral floor), R-0009 (L-unit lender ledger), and R-0010 (peel + micro-liquidation) in the aegis-engine/docs/research/ directory of the repo.


# What is AEGIS DFM?

AEGIS DFM — Dynamic Fee Mechanism — is a Uniswap v4 hook that replaces a pool's static fee tier with a two-component dynamic fee, and automatically compounds a share of trading fees into protocol-owned full-range liquidity.

DFM is the longest-running, most battle-tested component of AEGIS. It predates Engine, ships with its own audit coverage (Spearbit / Cantina), and runs as a standalone v4 hook on its own. Pools can use DFM without Engine; Engine pools always include DFM behavior.

### What it does, in one paragraph

When a swap hits an AEGIS-enabled pool, the DFM hook quotes a fee that depends on two things: a base fee that tracks the pool's realized volatility over a medium horizon, and a surge fee that spikes briefly after sudden price moves and decays back toward zero. Both are computed entirely from pool state (ticks, block timestamps) — no external feeds. A configurable share of every fee is redirected into a protocol-owned full-range position, which grows pool depth over time. The rest flows to LPs as normal.

The practical effect: LPs capture more of the economic surplus during volatile periods, without actively managing their positions, and the pool's long-run liquidity depth grows with its own trading activity.

### The components

DFM is five contracts, each with a focused responsibility:

1. Spot.sol — the Uniswap v4 hook itself. Orchestrator. Installed as the pool's hooks address.
2. DynamicFeeManager.sol — the fee engine. Computes and stores per-pool fee state (baseFeePPM, surgeFeePPM, inCap, budget counters).
3. TruncGeoOracleMulti.sol — the adaptive truncated-geometric-mean oracle. Produces the tick observations that drive the base fee formula and enforces a max-ticks-per-block (MTB) cap.
4. PoolPolicyManager.sol — the policy registry. Stores per-pool risk parameters, fee splits, cap budgets, and step-engine settings.
5. FullRangeLiquidityManager.sol — the POL vault. Compounds a share of collected fees into the pool's full-range position.

They compose roughly like this:

Trader → PoolManager → Spot (hook)

&#x20;                        ├─→ DynamicFeeManager.prepareSwap()  // quote fee

&#x20;                        ├─→ TruncGeoOracleMulti.push()       // record observation

&#x20;                        ├─→ DynamicFeeManager.finalizeSwap() // update fee state

&#x20;                        └─→ FullRangeLiquidityManager.reinvest()  // POL compound

Policy parameters flow from PoolPolicyManager into all of the above at configuration time.

Each contract has its own one-pager in the AEGIS\_DFM/docs/one\_pagers/ directory with storage layout, invariants, and gas notes. For auditors, the AEGIS\_Statement\_of\_Intended\_Behavior.md in that same repo is the canonical behavioral spec.

### Why a hook, not a patch to Uniswap

Uniswap v4's core design lets hooks alter almost every lifecycle event of a pool — initialize, modify-liquidity, swap, donate — without modifying v4 itself. DFM uses this to:

* Quote a pool-specific fee per swap (instead of a static fee tier).
* Record observations into its own oracle (not v4's built-in oracle).
* Steer a configurable share of collected fees to the POL vault.
* Update fee state after each swap.

Because it's a hook, DFM is opt-in at pool-creation time. Pools that don't install the AEGIS hook behave like vanilla Uniswap v4 pools; pools that do inherit everything described here.

### The two-fee idea, intuitively

A static fee tier is a flat line. At the 30 bp tier, every swap pays 30 bp, regardless of the pool's volatility. This over-charges during calm markets (routers send flow to lower-fee venues) and under-charges during volatile markets (LPs eat adverse selection that the fee doesn't cover).

DFM's fee is not flat. It has two components:

* Base fee: a slow-moving number that follows realized volatility. In calm conditions it drifts low; in sustained volatility it rises. The step cadence and step size are both governed by policy.
* Surge fee: a fast-moving spike that fires on sudden volatility — specifically when a single-block price move exceeds the oracle's MTB cap. Surge adds multiplicatively to base, then decays linearly over a configured period (default 6 hours).

Because both components are computed from observable pool state, the fee automatically adapts to conditions. An LP doesn't choose a tier and hope; the pool itself finds the right fee.

### The POL story

A configurable share of every fee collected on an AEGIS-enabled pool is redirected into a protocol-owned full-range liquidity position managed by FullRangeLiquidityManager. Over time, this accumulates:

* Deeper full-range liquidity on the pool.
* Aligned interests between the protocol and LPs — POL grows when trading volume grows, and it grows without issuing new tokens.
* A natural backstop of liquidity that won't leave during volatility (protocol doesn't manage short-term LP positions).

In Engine pools, the POL is the same pooled full-range position that backs sL shares. The two stories are unified: the DFM's POL compounding mechanism and Engine's sL equity accretion are the same event, counted from different angles.

### What DFM is not

A few clarifications:

* Not a MEV extractor. DFM doesn't front-run, doesn't sandwich, doesn't route flow anywhere. It adjusts the fee on each pool independently.
* Not a rebalancer. DFM never moves LP positions. The POL is full-range; individual LPs manage their own positions.
* Not a liquidity bootstrapper. The POL grows from trading volume, not from token issuance or TVL incentives.
* Not an oracle for other protocols (directly). The TruncGeoOracleMulti exposes observe/consult views that external consumers can use, but its primary purpose is driving DFM's own fee formula.

### When to use DFM without Engine

Protocols, DAOs, and aggregators can install the DFM hook on a pool without ever touching Engine. Typical reasons:

* Better LP capture on a niche pair. If the pair has volatile windows and a static tier is leaving money on the table, DFM can improve LP economics without rebuilding the pool.
* Campaign pools. Launch a pool with a specific fee trajectory (high initial, decaying base, surge-on-volatility) via policy-manager configuration.
* Protocol-owned liquidity growth. Allocate a share of every swap to POL and grow the book organically.

### When Engine adds value

If a pool has:

* Meaningful volume (enough to generate lending demand).
* A borrower constituency (strategies, LPs, arbitrageurs who want to unlock liquidity without closing positions).
* No existing oracle path for lending (because oracle dependence is what borrowers are trying to avoid).

…then enabling Engine on top of DFM adds a second revenue stream for LPs (interest) and a primitive (pool-native credit) for borrowers. For the mechanics, see [What is AEGIS Engine](/)?.


# How Dynamic Fees Work?

This page is the mechanism. If What is AEGIS DFM? is the executive summary, this is the engineering summary: how base fee is computed, how surge triggers and decays, and what the CAP event mechanism does.

### The fee per swap, at a glance

When a swap arrives at an AEGIS-enabled pool, the hook quotes a total fee in PPM (parts-per-million):

totalFeePPM = baseFeePPM + surgeFeePPM

The fee is then applied by Uniswap v4 as it would apply any v4 dynamic-fee quote. LPs and POL receive their configured shares of the fee; the rest goes to the hook fee account.

All math is in PPM. Conversions: 1 PPM = 0.0001% = 0.000001. The familiar 30 bp fee tier is 3,000 PPM in DFM terms.

### Base fee

The base fee tracks realized pool volatility via the max-ticks-per-block (MTB) metric:

baseFeePPM = maxTicksPerBlock × baseFeeFactorPpm

Default baseFeeFactorPpm is 28 — meaning 28 PPM per tick, or \~0.0028% per MTB tick. In a calm pool where MTB drifts to a small value like 3, base fee is 3 × 28 = 84 PPM ≈ 0.0084%. In a volatile pool where MTB rises to 100, base fee is 100 × 28 = 2,800 PPM ≈ 0.28%.

#### Where MTB comes from

TruncGeoOracleMulti self-tunes maxTicksPerBlock to hit a configured targetCapsPerDay. The mechanism:

* Every block, the oracle measures the actual tick move of the pool.
* When the move exceeds the current MTB cap, the oracle records a CAP event — a moment where volatility breached the expected envelope — and emits MaxTicksPerBlockUpdated after the auto-tune step.
* The auto-tune loop adjusts MTB up (if CAP events are too frequent) or down (if they're too rare), asymptoting toward the target frequency.
* Bounds are enforced: minBaseFeePpm ≤ baseFeePPM ≤ maxBaseFeePpm.

Default bounds are 10 PPM to 100,000 PPM (0.001% to 10%), tunable per-pool via PoolPolicyManager.

#### Step cadence

The oracle does not continuously recompute base fee. It steps on a cadence configured by baseFeeUpdateIntervalSeconds (default: every few minutes, varies by pool). At each step, baseFeePPM moves at most baseFeeStepPpm toward the target. This ensures the fee doesn't whipsaw on short-lived outliers.

### Surge fee

The surge fee is designed to catch sudden price moves — moves big enough to likely reflect information the pool hasn't priced yet.

#### Initial surge

When a CAP event fires (tick move exceeds current MTB), the oracle notifies DynamicFeeManager, which sets:

surgeFeePPM₀ = baseFeePPM × surgeFeeMultiplierPpm / 1\_000\_000

With default surgeFeeMultiplierPpm = 3\_000\_000 (300%), the initial surge is 3 × baseFeePPM. The multiplier is capped at 300% by policy — a hard cap that can't be raised beyond sensibility.

#### Linear decay

Once set, the surge fee decays linearly to zero over surgeDecayPeriodSeconds (default 6 hours = 21,600 seconds):

t = current time − capStart

if t ≥ surgeDecayPeriodSeconds: surgeFeePPM = 0

else: surgeFeePPM = surgeFeePPM₀ × (1 − t / surgeDecayPeriodSeconds)

The decay starts only after the CAP event. If another CAP event fires during the decay, the surge fee re-arms to surgeFeePPM₀ at the new event time. This means during a sustained volatility episode, surge can stay elevated; during a single-shock event, it decays smoothly.

#### Why linear and not exponential

Linear decay is simpler to reason about, gas-cheap on-chain, and produces predictable behavior for integrators and routers. Exponential decay tends to have a long tail that adds fee to swaps long after the volatility is gone; linear cuts off cleanly.

### CAP events and the policy budget

CAP events are not free. Each CAP event ticks against a per-pool budget tracked by DynamicFeeManager. The budget:

* Has a daily cap (capFrequency).
* Tracks inCap state (whether surge is currently active).
* Blocks excessive CAP events if the budget is exhausted.

This prevents pathological pools (ones that would fire CAP continuously) from permanently elevating the surge fee. The budget refreshes on a policy-configured cadence.

### The auto-tune loop

The MTB auto-tune loop balances two objectives:

1. Base fee should track realized volatility — high when the pool is volatile, low when calm.
2. CAP events should fire at roughly targetCapsPerDay — enough to capture genuine spikes, not so many that surge is constant.

Both are governed by targetCapsPerDay, a per-pool policy parameter. The oracle adjusts MTB in both directions over time to hit this target. On cold pools (few swaps per day), the loop converges slowly; on busy pools, it converges quickly.

#### What this looks like in practice

* Cold pool, stable pair: MTB drifts low, base fee is near minBaseFeePpm, surge rarely fires. Swap fees are cheap.
* Active pool, volatile pair: MTB sits high, base fee is elevated, occasional CAP events pump surge. Swap fees are meaningfully higher than a static low tier would charge.
* Active pool, calm pair: MTB is moderate, base fee is moderate, surge rare. Behaves similarly to a well-chosen static tier.

### Swap-time mechanics

Inside a single swap, the hook flow is:

beforeSwap:

&#x20; dfm.prepareSwap(poolId, swapParams):

&#x20;   quote totalFeePPM = baseFeePPM + surgeFeePPM

&#x20; hookRuntime store (tick, fee, sender)

Uniswap v4 executes swap at quoted fee

afterSwap:

&#x20; oracle.pushObservation(poolId, tickAfter, liquidity)

&#x20; capped = oracle check

&#x20; dfm.finalizeSwap(poolId, capped):

&#x20;   if capped: set inCap, capStart, surgeFeePPM₀

&#x20;   if base-fee step due: adjust baseFeePPM

&#x20; spot.handleFeeSplit(pendingFees):

&#x20;   split between LP / protocol / POL per policy

&#x20; spot.maybeReinvest(poolId):

&#x20;   if thresholds met, reinvest POL fees

A sophisticated trader could, in principle, observe that surge is active and choose not to trade. This is expected and fine — it is exactly what the surge fee is supposed to do: make trading less attractive when the market is in the middle of a price discovery event, so LPs aren't run over by adverse selection.

### Parameter catalog

Key parameters exposed via PoolPolicyManager (one per pool, all governable):

| Parameter                    | Default       | Description                                    |
| ---------------------------- | ------------- | ---------------------------------------------- |
| minBaseFeePpm                | 10            | Lower bound on base fee.                       |
| maxBaseFeePpm                | 100,000       | Upper bound on base fee.                       |
| baseFeeFactorPpm             | 28            | PPM of base fee per MTB tick.                  |
| baseFeeStepPpm               | TODO          | Max change to base fee per step.               |
| baseFeeUpdateIntervalSeconds | TODO          | Seconds between base fee steps.                |
| surgeFeeMultiplierPpm        | 3,000,000     | Surge fee as multiple of base. Capped at 300%. |
| surgeDecayPeriodSeconds      | 21,600        | Surge decay duration (6 hours).                |
| targetCapsPerDay             | TODO per pool | MTB auto-tune target frequency.                |
| capFrequency                 | TODO          | Daily CAP event budget.                        |
| polSharePpm                  | TODO per pool | Fraction of fees that go to POL.               |

The full parameter inventory lives in PoolPolicyManager and is enumerated in the spec. Refer to the DFM repo's docs/one\_pagers/PoolPolicyManager.md for authoritative defaults and bounds.

### The integrator view

For aggregators and routers integrating with AEGIS-enabled pools:

* Quote at swap time, not ahead. The fee can change between blocks if a CAP event fires or base fee steps. Don't stale-cache quotes.
* Use DynamicFeeManager.getLatestFeeQuote(poolId) for a consistent simulation view.
* Surge is information. If surge is active, you can choose to skip routing, accept the higher fee, or wait. AEGIS doesn't have an opinion — surge exists to let LPs capture adverse selection.
* POL is an LP. If you're computing pool depth for a route, include the POL position. It's a full-range position that looks like any other full-range LP to v4.


# DFM for Liquidity Providers

This page is the LP-oriented view of AEGIS DFM. It is narrower than [AEGIS for Liquidity Providers](http://../03-using-engine/for-liquidity-providers.md), which covers the full Engine-plus-DFM story. If you're providing liquidity into a pool that has DFM installed but not Engine, start here.

### What you hold

For a DFM-only pool, you hold a standard Uniswap v4 liquidity position — either a concentrated-range NFT from the PositionManager, or a full-range position you manage yourself. DFM does not change what kind of position you hold.

What DFM does change is the economics of that position:

* The fee you earn per swap varies with pool conditions (dynamic) instead of being pinned to a static tier.
* A configurable share of every fee is redirected into a protocol-owned full-range position (POL), deepening the pool over time.
* Your position benefits from that deeper POL — more full-range depth means better trade prices and more volume.

### Fee capture, concretely

For any given swap through the pool:

swapValue × totalFeePPM / 1\_000\_000 = totalFee

totalFee × (1 − polSharePpm/1e6 − hookFeePpm/1e6) = LP fee

LP fee × yourShareOfRange / totalLiquidityInRange = your fee

* totalFeePPM is set by DFM dynamically (base + surge).
* polSharePpm is the configured share that goes to POL. Rest goes to LPs + hook.
* hookFeePpm is the hook fee configured per-pool (bounded by PPM\_SCALE).
* "Your share of range" is the standard Uniswap v4 concentrated-liquidity math — only in-range liquidity earns.

The important mental shift: a static 30 bp pool earns you exactly 30 bp on each swap through your range, always. A DFM pool earns you less than 30 bp during calm periods (e.g., 5-10 bp) and more during volatile periods (e.g., 100+ bp during surge). The average over a representative window should be competitive with or better than a static tier, and the distribution is biased toward capturing adverse selection.

### Why dynamic is better than static (usually)

The standard tiers (5, 30, 100 bp) are too coarse for real pool behavior. The concrete failure modes:

1. Wrong tier for the pair. A USDC/USDT pool at 30 bp is absurd; a wBTC/ETH pool at 5 bp is absurd. DFM sidesteps the choice — the pool finds its own level.
2. Volatility regime changes. A pair that was calm for six months and then enters a volatile regime can't migrate to a higher fee tier without a new pool and TVL-bootstrapping effort. DFM adapts in place.
3. Adverse selection isn't priced. The LVR cost to LPs is concentrated around price moves; a flat fee treats all swap volume identically. Surge pricing captures extra fee specifically during the swaps that would otherwise be most costly to LPs.

The mechanism claim: over a representative volatility distribution, DFM's fee trajectory captures more of the economic surplus a static tier leaves on the table. It is not a claim that every individual swap will be cheaper or more expensive than any given static tier — it is a claim about the average.

### What DFM does not do

* DFM does not rebalance your position. If you hold a concentrated range, it stays where you put it. In-range / out-of-range is determined by pool movement, not by DFM.
* DFM does not hedge you. Impermanent loss (or LVR) is a property of any Uniswap LP. DFM changes the fee side of the equation but not the inventory side.
* DFM does not guarantee higher fees. On pools that are persistently calm, base fee drifts low and surge rarely fires. Your total fee income may be modest.

### Concrete LP patterns

#### Full-range passive

Deposit a full-range position. In a DFM-enabled pool, this is the simplest exposure:

* You earn fees on every swap.
* You earn no IL from concentration (full-range is the baseline IL curve).
* The POL position is also full-range, so it "competes" with you for fee share proportional to liquidity.

This is the right choice if you want low-management, stable-fee exposure and are willing to accept lower absolute fees than a well-managed concentrated position.

#### Concentrated range with rebalancing

Deposit a concentrated range covering the expected price zone. Rebalance when price drifts out.

* Higher fee capture per dollar of liquidity while in-range.
* You earn zero while out-of-range.
* Rebalancing has gas and slippage costs.

DFM doesn't help or hurt this strategy directly, but the higher per-swap fee during volatility periods is a meaningful tailwind.

#### Via a CL manager vault (Gamma, Arrakis, etc.)

These vaults manage concentration and rebalancing for you. On a DFM-enabled pool, the managed strategy's PnL improves with DFM's dynamic fee — more so the more volatile the pair.

### Monitoring

For a DFM-enabled pool, the useful per-pool views:

* DynamicFeeManager.getLatestFeeQuote(poolId) → SwapQuote with current baseFeePPM and surgeFeePPM.
* DynamicFeeManager.getFeeState(poolId) → (base, surge) shortcut.
* DynamicFeeManager.isCapEventActive(poolId) → boolean; true during surge.
* DynamicFeeManager.getMaxTicksPerBlock(poolId) → current MTB from the oracle.
* OracleManager.observe(poolKey, secondsAgos) → tick history if you want to compute your own volatility.

For POL position size: the FullRangeLiquidityManager exposes a view for the protocol-owned position's L. That, combined with total pool L, gives you the POL share of the pool.

TODO: Wire into subgraph view URLs once DFM subgraph is public.

### Fee collection

Claiming fees in a DFM-enabled pool uses Uniswap v4's standard collect on your PositionManager NFT (for concentrated positions) or via whichever interface you used to mint your full-range position. DFM does not own your fees — it only adjusts what the pool charges per swap. Your fees are yours to collect on the normal v4 cadence.

### Risks specific to DFM LPs

* Volatile fee → unpredictable income. If you're used to static-tier income predictability, DFM income is noisier. It also responds more to regime changes in the pair.
* Hook paused or mis-configured. If the DFM policy manager pauses auto-tune (e.g., for safety), base fee freezes at its current value. This is safe but temporarily removes adaptivity.
* POL share in tight ranges. If the POL position becomes a large fraction of a pool's full-range liquidity and you hold a narrow range, your share of in-range liquidity is lower at the market price than it would be in a non-POL pool. This is usually a good thing (more full-range depth helps your in-range liquidity when price moves), but it's worth understanding.
* Hook external-call fallback. If the DFM manager contract is paused or reverts, the hook emits ExternalCallFailed and falls back to a default fee (policy-configured manualFeePpm). This keeps the pool functional but may deviate from expected DFM behavior for a short window.

### The bottom line for LPs

DFM's claim is specific and mechanical:

By pricing fees to volatility and compounding a share into POL, a DFM-enabled pool captures more of the economic surplus of trading than a static-tier pool can, and it grows pool depth without issuing any token incentives.

That's the pitch. Everything on this page follows from that mechanism. If you want the borrower side or the unified Engine-plus-DFM LP story, read [AEGIS for Liquidity Providers](http://../03-using-engine/for-liquidity-providers.md).


# Risk Overview

This page lists the risks of using AEGIS as an LP, borrower, integrator, or operator. It is written for honest assessment, not for marketing. If you are evaluating whether AEGIS is appropriate for your use case, read this page before you act.

Risks are grouped by category. For each one, we describe what the risk is, what mitigates it in the protocol design, and what residual risk remains for the user.

### Smart contract risk

The risk. Solidity contracts can have bugs that cause loss of funds or protocol malfunction. This risk applies to every DeFi protocol, including AEGIS.

What mitigates it.

* DFM is audited by Spearbit and Cantina. Reports are linked from [Security, Audits, and Status](http://./security-audits-and-status.md). DFM has been running on testnet and canary deployments for an extended period.
* Engine is audited by Trail of Bits.
* Invariants are documented in the AEGIS\_Statement\_of\_Intended\_Behavior.md files in each repo. These are the specific properties auditors and reviewers are invited to attack.
* Foundry test suites (including fuzzing and invariant testing) exercise both normal and pathological paths.

What remains. No amount of auditing eliminates smart contract risk entirely. Engine in particular introduces new code (pool-native credit, collateral floor math, peel + micro-liq) that does not have years of production exposure. Users should size positions according to their own risk tolerance.

### Solvency & liquidation risk (Engine)

The risk. A vault can become undercollateralized if its attached NFTs lose value (price moves out-of-range) or if its idle-token reserves change value faster than keepers can act. If keepers cannot fully heal the vault, sL holders bear the uncovered L as first-loss.

What mitigates it.

* The √K collateral floor is evaluated using current pool state (√P, NFT ranges, idle balances). There is no oracle dependence, so there is no stale-oracle risk.
* Keepers have two modes (peel, micro-liquidation) with overlapping economics — if one fails, the other often succeeds.
* MAX\_LTV\_PIPS = 980,000 (98%) is a compile-time constant; the health cushion between LTV and solvency is narrow but deliberate (borrowers trade off capital efficiency against liquidation margin).
* Session-end LTV check reverts the entire transaction if the vault is out of spec, preventing any action that would leave the vault unhealthy.

What remains.

* If the pool has insufficient depth for keepers to swap at acceptable slippage, keeper economics break down and liquidations may stall.
* Extreme adversarial scenarios (e.g., coordinated price attacks on thinly-traded pools) could leave some residual sL loss. The full set of attack vectors is discussed in [Solvency and Liquidation](http://./solvency-and-liquidation.md).

### Oracle risk (indirect)

The risk. DFM uses the TruncGeoOracleMulti for base-fee computation and for CAP event detection. If the oracle misbehaves, the fee computed may be wrong.

What mitigates it.

* The oracle is derived from the same pool's ticks — it cannot be manipulated in a way that doesn't also manipulate the pool price. Pool manipulation costs approximately the pool's depth, which self-bounds the attack cost.
* The max-ticks-per-block cap is an explicit design feature; price moves beyond the cap arm the surge fee rather than being silently accepted.
* If the oracle manager reverts or the hook's external call to DFM fails, the hook emits ExternalCallFailed and falls back to manualFeePpm from PoolPolicyManager. The pool continues to function; only the adaptive fee is disabled for the affected block.

What remains. Pool-internal oracles are only as robust as the pool's depth. Thin pools are easier to push; AEGIS pools with low liquidity are more vulnerable to short-term oracle distortions.

### Governance & admin key risk

The risk. AEGIS has owner-controlled functions: pausing auto-tune, updating policy parameters, adding/removing authorized hooks, setting the protocol fee rate.

What mitigates it.

* The complete inventory of privileged functions is documented in [Governance and Roles](http://./governance-and-roles.md).
* Critical parameters are bounded — MAX\_LTV\_PIPS, UTILIZATION\_CAP\_PIPS, MAX\_NFTS\_PER\_VAULT, MIN\_LIQUIDITY, and others are compile-time constants, not owner-tunable. An admin cannot increase LTV limits or change keeper gating.
* Owner keys are TODO: confirm — working assumption is a multisig with timelock for all parameter changes.
* Emergency pause exists for narrow pause points (e.g., auto-tune freeze). There is no "pause everything" super-kill switch.

What remains. If the owner key is compromised, an attacker could change tunable policy parameters (fee bounds, protocol fee rate, authorized hook list) within the bounded envelope. They cannot unilaterally drain user funds, but they can degrade protocol economics until the key is rotated.

### Liquidity & market risk

The risk. AEGIS pools depend on Uniswap v4 markets being functional and reasonably liquid.

* LPs earn fees only when volume exists.
* Borrowers need the pool to be liquid enough to repay at acceptable slippage.
* Keepers need the pool to be liquid enough to execute micro-liquidation profitably.

What mitigates it. AEGIS deployments are targeted at pairs with sustained volume. Thin pools are unlikely to be enabled for Engine.

What remains. Pool liquidity can change. A pair that was active for a year could become dormant; during dormant periods, keeper economics weaken. LPs should track the pools they're in; borrowers should not rely on specific exit paths being available at all times.

### Uniswap v4 dependency

The risk. AEGIS is built on Uniswap v4's PoolManager. If v4 core has a critical vulnerability or is paused, AEGIS inherits the impact.

What mitigates it. Uniswap v4 is itself heavily audited and has broad mainnet deployment. AEGIS does not modify v4 core; it only uses the hook interface.

What remains. AEGIS cannot protect users from issues in v4 core. Positions in AEGIS-enabled pools are Uniswap v4 positions, with Uniswap v4's risk profile.

### Cross-chain & deployment risk

The risk. AEGIS deploys to multiple chains via CREATE2 deterministic addressing. Each chain is a separate surface.

What mitigates it.

* Same bytecode + same CREATE2 salt → same address across chains. Verification is mechanical.
* Each chain's deployment is independently verifiable on its block explorer. Addresses are published in [Deployed Contracts](http://./deployed-contracts.md).
* Per-chain policy is configured separately; a policy change on Base does not silently propagate to Optimism.

What remains. Different chains have different security properties (validator sets, settlement finality, bridge risk for the underlying tokens). AEGIS does not run bridges itself, but an AEGIS-enabled pool on chain X is exposed to chain X's ecosystem risks.

### Keeper risk (Engine)

The risk. Micro-liquidation requires an economically-motivated keeper to submit transactions. If keepers are absent or unprofitable, unhealthy vaults may stay unhealthy longer than expected.

What mitigates it.

* Peel has a denominated bounty paid in the pair's tokens (from vault idle), making it naturally profitable for keepers when vaults are peel-eligible.
* Micro-liquidation has no bounty, but the AE-owned swap captures a discounted outcome; professional keepers will run micro-liq on any vault they can detect as unhealthy.
* The peel/micro-liq event streams are public; anyone can build a keeper.

What remains. Keeper performance depends on gas costs, MEV competition, and pool liquidity. During extreme gas spikes or when the pool has very little depth, keeper economics may weaken and vaults may stay unhealthy for longer windows than typical.

### Regulatory & jurisdictional risk

The risk. DeFi protocols face evolving regulatory treatment globally. Specific services, functions, or integrations may be restricted in some jurisdictions.

What mitigates it. AEGIS is a set of smart contracts; it is not a custodial service, has no KYC layer, and does not route user funds through AEGIS-controlled accounts.

What remains. Users are responsible for their own regulatory compliance. AEGIS publishes no legal opinions about the status of the protocol or its tokens in any jurisdiction. TODO: if the Foundation publishes a restricted-jurisdictions list, link it here.

### User-error risk

The risk. AEGIS Engine exposes a router-driven action system with multiple phases, a session model, and strict invariants. Integrators who misuse the API may unintentionally submit transactions that revert, leave dust, or interact with pools in non-obvious ways.

What mitigates it.

* The router enforces phase ordering, delta-zero, session lifecycle, and LTV at the end of every session. Revert early and clearly is the design goal.
* Events are published with full units in NatSpec.
* Integration patterns are documented in [Integrating AEGIS Engine](http://../03-using-engine/integrating-aegis-engine.md).

What remains. Integrators who don't read the docs can still lose gas on reverts. They can also, in principle, construct routes that deposit idle tokens without intending to (e.g., via an unexpected positive delta); the protocol won't prevent that, it's a consequence of how v4 PoolManager accounting works.

### What AEGIS does not protect against

To be explicit about the scope of the protocol's safety model:

* Impermanent loss / LVR. AEGIS does not hedge LP inventory. Dynamic fees capture more economic surplus on the fee side, but the inventory side is unchanged. LPs still bear standard Uniswap position risk.
* Your own private key. AEGIS has no recovery mechanism. If you lose your key, you lose access.
* Non-AEGIS counterparties. If you use a third-party vault wrapper (Gamma, Arrakis, etc.) on an AEGIS pool, the wrapper's contract risk is on top of AEGIS's.
* Price direction. AEGIS does not predict prices, speculate, or provide exposure. Every AEGIS position's underlying exposure is determined by the Uniswap v4 math; nothing in AEGIS alters that.

<br>

***

<br>

Next: [Governance and Roles](http://./governance-and-roles.md) — what is controlled by whom, and what remains fixed in the protocol.

<br>

***

## Governance and Roles

This page is the inventory of everything that is controlled by someone — and everything that is controlled by no one because it's a compile-time constant. Every privileged function is listed; every upgrade path is called out.

<br>

AEGIS believes in minimal admin surface area, but not zero. This page explains the surface area that exists.

### The principle

AEGIS draws a line:

<br>

* Compile-time constants. Parameters that define the safety envelope of the protocol. These cannot be changed without deploying new contracts. Examples: MAX\_LTV\_PIPS, UTILIZATION\_CAP\_PIPS, MAX\_NFTS\_PER\_VAULT, TWAP\_WINDOW, N\_TICKS, MIN\_LIQUIDITY.
* Policy-governed parameters. Parameters that can be tuned to reflect market conditions or pool-specific requirements. These are controlled by the policy owner. Examples: baseFeeFactorPpm, polSharePpm, marketProtocolFeeBps, surgeDecayPeriodSeconds, hook authorization.
* Pool-level configuration. Per-pool policy values set at pool creation, tunable by the policy owner thereafter within policy-manager bounds.

<br>

The separation is deliberate. The user-safety guardrails are not adjustable. The economic-tuning knobs are.

### Compile-time constants

These are hard-coded in the contract source. Changing any of them requires a new deployment (at a new address — CREATE2 makes the new address a different artifact). A list with references:

<br>

| Constant               | Value                | Where       | Purpose                                        |
| ---------------------- | -------------------- | ----------- | ---------------------------------------------- |
| MAX\_LTV\_PIPS         | 980,000 (98%)        | AegisEngine | Session-end LTV cap for vault solvency.        |
| UTILIZATION\_CAP\_PIPS | 950,000 (95%)        | AegisEngine | Max fraction of pool L that can be borrowed.   |
| MAX\_NFTS\_PER\_VAULT  | 4                    | AegisEngine | Cap on attached CL/LO NFTs per vault.          |
| MIN\_LIQUIDITY         | TODO: confirm        | AegisEngine | Minimum sL share delta for mint/burn.          |
| TWAP\_WINDOW           | 30 minutes (1,800 s) | AegisEngine | Oracle TWAP window for micro-liq gate.         |
| N\_TICKS               | 5                    | AegisEngine | Max TWAP-to-spot tick deviation for micro-liq. |
| MAX\_IDLE\_CURRENCIES  | TODO: confirm        | AegisEngine | Cap on distinct currencies held in vault idle. |

<br>

These are the safety envelope. If you are auditing AEGIS or reviewing the protocol for a consumer-protection perspective, these are the values to verify in the deployed bytecode.

### Owner-controlled functions

The protocol has an owner address. This address is the root authority over tunable policy. The owner key is expected to be held by a multisig; specific configuration is published in the deployed-contracts page.

<br>

TODO: confirm owner multisig signers, threshold, and timelock policy. Working assumption: 3/5 multisig with 48h timelock on all parameter changes except emergency pause, which has a shorter timelock or none.

#### Owner functions on AegisEngine

* setHookAllowed(address hook, bool allowed) — add or remove a hook from the authorized set. This gates which pools Engine will treat as Engine-enabled. Effect: new sessions on an unauthorized hook revert; existing sessions complete normally.
* setMarketProtocolFee(PoolId poolId, uint16 bps) — set the per-market protocol fee share (0-10,000 bps). Default: TODO: confirm — working assumption 500 bps (5%).
* setRateOracle(PoolId poolId, IVariableInterestRate) — set or unset the per-market rate oracle. Zero means use default.

#### Owner functions on PoolPolicyManager (DFM)

* setPoolPolicy(PoolKey, PolicyStruct) — write a new policy struct for a pool. All DFM-tuning parameters are inside this struct.
* setDefaultPolicy(PolicyStruct) — change the policy applied to new pools that don't specify one.
* setHookFeePpm(PoolKey, uint32) — update the hook fee portion for a pool. Bounded by PPM\_SCALE.
* setPolSharePpm(PoolKey, uint32) — update the POL share for a pool.
* pauseAutoTune(PoolKey, bool) — freeze baseFeePPM at its current value. Surge still works; only the MTB auto-tune loop is frozen.
* emergencyPause(PoolKey, bool) — TODO: confirm scope — expected to halt new swaps on the pool if invoked, but this is a pool-manager-level action.

#### Owner functions on TruncGeoOracleMulti

* setHookAllowed(address hook, bool) — authorize or deauthorize a hook for recording observations.
* setPolicyManager(address) — point the oracle at a policy manager (usually set once at deploy).

#### Owner functions on DynamicFeeManager

* setHookAllowed(address hook, bool) — authorize / deauthorize hooks.
* setOracleManager(address) — update the oracle reference.
* setPolicyManager(address) — update the policy manager reference.

#### Owner functions on FullRangeLiquidityManager

* setHookAllowed(address hook, bool) — authorize / deauthorize hooks for POL operations.
* TODO: additional owner functions — confirm from contract source.

### Functions anyone can call

A sampling of the public surface that doesn't require ownership:

<br>

* AegisEngine.aeUnlock(...) — start a session from a router (permissionless).
* Session actions (borrowL, repayL, modifyLiquidity, attach/detach, deposit/withdraw idle) — gated by vault ownership / authorization, not protocol ownership.
* Spot.pokeReinvest(poolId) — trigger POL compound. No reward, permissionless.
* OracleManager.increaseCardinalityNext(poolKey, n) — grow oracle capacity. Caller pays the gas.
* peel(...), microLiq(...) — keeper actions, permissionless.
* All DFM view functions (getLatestFeeQuote, getFeeState, isCapEventActive, etc.).

### Rate oracle: pluggable per market

Engine markets can point at any IVariableInterestRate-conforming oracle. The default is a Frax-style kinked utilization curve deployed with the engine. Owner can swap it per market with setRateOracle.

<br>

TODO: document the default rate curve's exact parameters once finalized.

<br>

The contract interface (simplified):

<br>

interface IVariableInterestRate {

<br>

&#x20;   function getNewRate(

<br>

&#x20;       uint256 totalRlBorrowed,

<br>

&#x20;       uint256 equityL,

<br>

&#x20;       uint64 oldFullUtilRate,

<br>

&#x20;       uint64 dt

<br>

&#x20;   ) external view returns (uint64 newRate, uint64 newFullUtilRate);

<br>

}

<br>

Protocol invariant: changing the rate oracle does not invalidate existing debt. The new oracle takes effect from the next accrue. Existing rLWad and borrowIndexWad persist.

### Protocol fee: structural, not surprise

The marketProtocolFeeBps is charged as a share of accrued interest. It is not a fee on borrow, repay, or deposit. The value is bounded \[0, 10,000] bps. When an accrue runs, the protocol share is minted as new sL to the protocol address.

<br>

The protocol address is TODO: confirm — working assumption is the same owner multisig, but could be a dedicated fee receiver.

<br>

Changing marketProtocolFeeBps does not retroactively affect already-accrued interest; it only affects future accrual.

### What is NOT governed

To be explicit about what the owner cannot do:

<br>

* Cannot drain user funds. There is no withdrawal function from the pooled full-range position except via sL burn or borrow/repay. The owner address is not privileged against the market's math.
* Cannot change outstanding debt. rLWad and borrowIndexWad are market state. An owner action cannot edit a specific vault's debt.
* Cannot raise MAX\_LTV. This is compile-time.
* Cannot remove the TWAP gate. This is compile-time.
* Cannot force-close a vault. Only peel + micro-liq (permissionless, economically driven) or owner-authorized close-out paths that respect the vault's economics.
* Cannot force sL transfers. ERC-6909 semantics; balances are user-owned.
* Cannot mint arbitrary sL. The only mint paths are modifyLiquidity (pays L), accrue (protocol share), and internal engine-side paths that are mathematically bound.

### Upgrade model

AEGIS contracts are not upgradeable in the proxy sense. There is no transparent proxy, no UUPS, no diamond. If a contract needs to change, a new version is deployed and pools are migrated to it.

<br>

This is a deliberate choice:

<br>

* No proxy admin key, no selector collisions, no storage-layout bugs.
* Every deployed contract is immutable at its address.
* Cross-chain verification is mechanical (same bytecode = same address via CREATE2).

<br>

The trade-off is that bug fixes require migration, not in-place patching. The protocol accepts this cost for the simplicity benefits.

### Deployment governance

The deployment process is:

<br>

1. Contracts are compiled at a specific commit (Git SHA recorded in the release).
2. Create2Deploy.s.sol runs across all target chains with a shared salt, producing identical addresses.
3. Deployed addresses are verified on each chain's block explorer.
4. Initial policy parameters are set on-chain via owner transactions.
5. Addresses are published in [Deployed Contracts](http://./deployed-contracts.md).

<br>

Each step is publicly verifiable. The deployment script is in the repo (script/Create2Deploy.s.sol for DFM, and the Engine equivalent).

### Emergency response

If a critical issue is detected post-launch, the primary response mechanisms are:

<br>

* Pause auto-tune on affected pools via PoolPolicyManager.pauseAutoTune. Freezes fee adaptation without stopping the pool.
* Deauthorize the hook via AegisEngine.setHookAllowed(hook, false). Stops new Engine sessions on that pool; existing positions can still close.
* Communicate. TODO: establish and document the AEGIS security disclosure channel — expected: a <security@aegis.markets> or equivalent, plus the usual channels (X, Discord) for status updates.

<br>

There is no "pause all lending" super-kill. This is a conscious trade-off: the safety envelope is already mathematically enforced in compile-time constants, so a universal pause would add operational risk without adding safety benefit.

### Role summary

| Role                     | What it controls                                        | Who holds it                                                                            |
| ------------------------ | ------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Owner (of each contract) | Tunable parameters, hook authorizations                 | TODO: multisig address — published per chain                                            |
| Protocol fee receiver    | Receives marketProtocolFeeBps share of accrued interest | TODO: confirm — likely owner multisig                                                   |
| Vault owner (per vault)  | Actions on that vault (borrow, repay, attach, withdraw) | End user, the address that created the vault                                            |
| sL holder                | LP shares, redeemable for L                             | End user, via modifyLiquidity with positive delta                                       |
| Keeper                   | Peel and micro-liq actions                              | Anyone — permissionless                                                                 |
| Router                   | Wraps session lifecycle                                 | Typically AEGIS-deployed, but not privileged; a third party can deploy their own router |

<br>

***

<br>

Next: [Security, Audits, and Status](http://./security-audits-and-status.md) — the audit history, production readiness, and open issue inventory.

<br>

***

## Security, Audits, and Status

This page is the honest status page. Read it before deciding whether to interact with AEGIS.

### The two products, separately

AEGIS ships two products on distinct timelines:

<br>

* DFM — production-hardened, audited, deployed on canary pools since TODO: start date.
* Engine — under active development, audit-in-progress, not yet deployed to mainnet production.

<br>

Treat them as two artifacts with independent maturity.

### DFM status

#### Audits

| Firm     | Scope                    | Date             | Report           |
| -------- | ------------------------ | ---------------- | ---------------- |
| Spearbit | Full DFM contract set    | TODO: month/year | TODO: report URL |
| Cantina  | Follow-up / delta review | TODO: month/year | TODO: report URL |

<br>

The DFM repo README notes "Spearbit / Cantina" as audit coverage. For the authoritative scope and findings, consult the report files in AEGIS\_DFM/docs/audits/ or the linked URLs once published.

#### Production exposure

TODO: confirm and update with real numbers once DFM has been running at scale.

<br>

Rough shape of expected evidence:

<br>

* Canary deployment: N months live on testnet + low-TVL mainnet pools.
* Cumulative swap volume processed: TODO: $X.
* CAP events observed: TODO: N (sanity-check that the auto-tune loop behaves as designed in live conditions).
* POL accrued: TODO: $X of protocol-owned liquidity accumulated from fee share.
* Incidents: none known (or list them here if any occur).

#### Known DFM caveats

* One-sided residues. If the pool becomes highly imbalanced, fee residues may accumulate one-sided and defer reinvest until paired. This is expected behavior, not a bug, but may cause POL growth to look "stuttering" on extreme days.
* Oracle cardinality. By default, OracleManager has a modest cardinality. If you need longer TWAP history, anyone can pay to grow it via increaseCardinalityNext.
* Pool-specific policy presets. The policy presets shipped with DFM are defaults; for unusual pairs, custom policy may be needed and is at the owner's discretion.

### Engine status

#### Audits

TODO: firm(s), scope, timeline.

<br>

Working assumption: at least one major firm (Spearbit, Trail of Bits, ChainSecurity, or OpenZeppelin) for the Engine contract set, with a follow-up review on any material changes. The Engine repo's README calls this out explicitly: "Engine contracts are currently a work in progress. They are not safe for production use."

#### Development status

As of 2026-04-20:

<br>

* Engine architecture is stable (documented in aegis-engine/docs/specs/0100-Architecture-Overview\.md and 0150-Statement-of-Intended-Behavior.md).
* Foundry test suite includes invariant tests for equity-neutrality, √K floor monotonicity, session isolation, LTV gating.
* Integration tests exercise Engine + DFM together on forked mainnet state.
* TODO: confirm active development phase — likely "final pre-audit review" or "in audit".

#### What is not yet done

* Public audit reports.
* Mainnet deployment at non-trivial TVL.
* Live keeper infrastructure (peel + micro-liq operational playbooks).
* Production analytics / subgraph / app integration.

#### Known Engine caveats and TODOs

The Engine repo openly flags the following areas as needing work before production:

<br>

* Rate oracle defaults (fullUtilizationRate initialization, curve parameters) are "reasonable starting values" that may need tuning based on simulation.
* MAX\_NFTS\_PER\_VAULT is currently 4; increasing this has gas implications and requires re-reviewing the C\_min SAFE bound.
* The MicroLiq TWAP gate parameters (TWAP\_WINDOW = 30min, N\_TICKS = 5) are working assumptions; live market data may suggest different values.
* Peel bounty defaults are placeholder.
* Keeper-side tooling (monitoring, transaction submission, MEV protection) is not yet published.

<br>

These are not bugs; they are pre-launch tuning work.

### The Statement of Intended Behavior

Each repo ships a Statement of Intended Behavior (SoIB) document:

<br>

* aegis-engine/docs/specs/0150-Statement-of-Intended-Behavior.md — canonical Engine spec.
* AEGIS\_DFM/docs/AEGIS\_Statement\_of\_Intended\_Behavior.md — canonical DFM spec.

<br>

SoIB documents are the source of truth for:

<br>

* Expected invariants (e.g., equity-neutrality of borrow/repay, share price monotonicity in normal operation, session isolation, LTV enforcement).
* Specific properties auditors and reviewers are invited to attack.
* Policy-tunable vs. compile-time parameters.

<br>

If a behavior in the deployed contract differs from the SoIB, the SoIB is the intent; the deviation is a bug.

### Bug bounties

TODO: confirm whether AEGIS runs a bug bounty program and, if so, the details (Immunefi, self-hosted, scope, top payout).

<br>

Working assumption: AEGIS will run an Immunefi-style program covering DFM and (once deployed) Engine. Scope, payouts, and disclosure process will be published when the program goes live.

<br>

Until a formal program exists, security researchers are encouraged to disclose responsibly via TODO: <security@aegis.markets> or equivalent channel.

### Monitoring

Live monitoring during operation:

<br>

* On-chain events. DFM and Engine emit comprehensive event streams. The event list is enumerated in each contract's NatSpec.
* Subgraph. TODO: subgraph URL — indexes the event set and exposes views for LP / borrower / keeper dashboards.
* Status page. TODO: status.aegis.markets or equivalent — for live protocol state (current authorizations, paused pools, any active incidents).

### Incident response

If an issue is detected, the AEGIS team's response will follow:

<br>

1. Identify scope and affected pools / users.
2. Apply the narrowest possible mitigation (pause auto-tune on affected pools, deauthorize a hook if necessary).
3. Publicly communicate via the status page and direct channels.
4. Coordinate with auditors if a code-level fix is required; deploy new contracts and migrate if needed.
5. Publish a post-mortem.

<br>

Historical post-mortems will live at TODO: docs.aegis.markets/incidents or a similar URL, once there is a history to publish.

### Risk framing for users

Given the status above, here is a direct framing for would-be users:

<br>

* DFM LP on an audited pool: a mature, audited product. Residual risks are smart contract risk (always non-zero) and pool-specific liquidity / volume risk. Reasonable for users comfortable with standard v4 LP exposure.
* DFM integrator (router, aggregator): safe to integrate. The fee model is deterministic and documented.
* Engine LP (pre-mainnet): should not be used in production. Testnet usage welcomed.
* Engine borrower (pre-mainnet): should not be used in production. Testnet usage welcomed.
* Engine integrator (pre-mainnet): can build against the documented API. Expect minor interface changes during the audit phase; lock in against a specific commit.

<br>

Once Engine is audited and deployed to mainnet, this page will be updated with production-readiness details. The date on the top of this page (TODO: last updated) indicates when the page was last reviewed.

### Comparison with related protocols

For context on how AEGIS's security posture compares to the DeFi environment:

<br>

* Uniswap v4 is the base layer AEGIS builds on. v4 is audited by multiple firms; AEGIS inherits any residual risk there.
* Aave / Compound are production money markets with multi-year track records. They have oracle dependencies AEGIS does not, but they also have far more time-tested code.
* Uniswap v3 LP managers (Gamma, Arrakis, etc.) are similar in code-maturity to DFM. They have different risk profiles (active rebalancing rather than passive fee adaptation).

<br>

The protocol does not claim to be more or less safe than these; the shape of the safety story is different. AEGIS trades oracle-dependence for pool-internal solvency checks, and static fees for adaptive ones. Whether those trade-offs are attractive is a user-by-user decision.

### Sources

* DFM repo: [github.com/labs-solo/AEGIS\_DFM](https://github.com/labs-solo/AEGIS_DFM)
* Engine repo: [github.com/labs-solo/aegis-engine](https://github.com/labs-solo/aegis-engine) (TODO: confirm public visibility)
* Audit reports: see the links table above once they are public.

<br>

***

<br>

Next: [Deployed Contracts](http://./deployed-contracts.md) — the contract address book per chain.

<br>

***

## Deployed Contracts

This page is the authoritative address book for AEGIS deployments. Addresses here are deterministic via CREATE2 — the same bytecode + the same salt yields the same address on every EVM chain AEGIS deploys to.

<br>


# Solvency and Liquidation

This page is the detailed safety story for AEGIS Engine: the collateral floor, how solvency is enforced, what keepers do to restore unhealthy vaults, and what residual loss can remain.

If you read nothing else, the thesis is:

A borrower's obligation is denominated in the same L-units as the collateral backing it; the floor is computed from observable pool state; and the keeper lane has two independent modes. Residual loss is bounded and allocated to sL first-loss.

### The collateral floor, precisely

Given a vault v on pool p at current √P, the minimum L-value of the vault's assets is:

C\_min(v) = L(idle0, idle1, √P) + sum over attached CL NFTs \[L\_burn(NFT, √P)]

&#x20;                                \+ sum over attached LO NFTs \[L\_burn(LO, √P)]

Where:

* L(idle0, idle1, √P) = the L-units that a full-range position would yield at the current √P given token balances idle0, idle1. Computed via the √K identity (see R-0008 in the Engine repo).
* L\_burn(NFT, √P) = the L-units returned if the NFT were fully burned at √P. For a CL NFT, this uses the NFT's liquidity, tickLower, tickUpper relative to the current tick — it collapses to a closed-form expression depending on whether √P is inside, above, or below the range.
* L\_burn(LO, √P) = for a Limit Order (LO NFT), this depends on whether the tick has been crossed. If crossed, the LO's filled side; if not, the bucket's pre-fill value.

The critical properties:

1. No oracle. C\_min is derived entirely from Uniswap v4's own state (tick, ranges, balances). There is no external price feed to be stale, manipulated, or fail to update.
2. Monotone in price moves. As √P changes, C\_min updates smoothly. There are no discontinuities; no sudden collateral drops from one block to the next beyond what the pool's tick move implies.
3. Conservative at session start. C\_min is evaluated at session-end (lockVault); the borrower's actions inside a session can temporarily push balances around, but the end-state is what matters.
4. Bounded NFT count. MAX\_NFTS\_PER\_VAULT = 4 (compile-time). This caps the computational cost of C\_min per session and makes the floor deterministic.

The SAFE multi-NFT bound (R-0008) ensures the sum-of-parts never overstates the actual L that would be recovered if all NFTs were burned simultaneously. This is the mathematical backbone of the Engine safety story.

### The solvency inequality

A vault is solvent at session-end if and only if:

rLWad × borrowIndexWad / 1e18 ≤ C\_min × MAX\_LTV\_PIPS / 1\_000\_000

Expanded:

* Left side: real debt in L-WAD (nominal principal times accumulated interest).
* Right side: MAX\_LTV\_PIPS = 980\_000 → 98% of the collateral floor.

If the inequality is false at lockVault, the entire transaction reverts. There is no partial success path.

#### Why 98%, not 80%

A 98% max-LTV is aggressive by money-market standards (Aave uses 75-85% typical, highest risk markets up to 90%). The reason AEGIS can run so tight:

* The collateral is the same asset as the debt (L in the pool). There is no cross-asset basis risk.
* Price moves of the pair move collateral and debt denomination together — if √P moves, both the L-units owed and the L-units held move the same way relative to token0/token1 balances.
* LVR-style losses for the borrower's NFT positions do show up in the floor, but they are bounded by the same pool's math.

The 2% cushion exists to absorb:

* Interest accrual between blocks (not much, but non-zero).
* Minor tick-move noise between when the borrower constructed the transaction and when it lands on-chain.
* Keeper bounty room (peel bounty eats a fraction of the idle balance).

A 98% setting is the tightest AEGIS can safely run; loosening it would waste capital efficiency without material safety benefit, tightening would make too many routine operations revert.

### The utilization cap

In addition to the per-vault LTV check, the market has a global constraint:

totalRlBorrowed ≤ (equityLWad / 1e18) × UTILIZATION\_CAP\_PIPS / 1\_000\_000

UTILIZATION\_CAP\_PIPS = 950\_000 (95%). This ensures there is always some L in the pool that is not borrowed — important because:

* If 100% of the market's L were borrowed, there would be nothing left in the pool for trading, and swap depth would collapse.
* Repay requires the pool to absorb returned liquidity; leaving room means repayment paths are always available.
* Keepers need pool depth to execute micro-liquidation; some unborrowed L is a precondition for the keeper lane's functioning.

### Keeper mode 1: peel

Peel reduces the amount of attached NFT liquidity in a vault, increasing the vault's idle balance, without touching the debt ledger.

#### When peel applies

* The vault's LTV is above a peel-eligibility threshold (TODO: confirm threshold — working assumption is slightly below MAX\_LTV\_PIPS, e.g. 900\_000 PIPS / 90%).
* The vault has at least one attached CL or LO NFT.

#### What the keeper does

1. Picks an NFT in the vault.
2. Calls peel(vaultId, nftIndex, dL, bounty) on the router, reducing the NFT's liquidity by dL.
3. Receives a bounty denominated in the pair's tokens, paid from the vault's post-peel idle balance.

#### What the protocol enforces

* dL is capped such that the post-peel vault is still solvent (or at least closer to solvent than pre-peel).
* The bounty is bounded by policy — TODO: confirm bounty cap in BPS — working assumption is 50 bps of the peeled L value.
* The peel cannot push the vault into a worse state than before.

#### The Peel event emits

Peel(vaultId, nftKind, tokenId, dL, cMinPre, cMinPost, bountyPaid).

Keepers and indexers can reconstruct the full peel history of a vault from this stream.

### Keeper mode 2: micro-liquidation

Micro-liquidation is the action of last resort when peel is insufficient (e.g., all NFTs are either fully unwound or would be pushed past their tick boundaries by further peel, or the vault has only idle balances left). It performs an AEGIS-owned swap inside the pool to rebalance the vault's idle into a shape that can repay debt.

#### When micro-liq applies

* The vault is above LTV threshold.
* A TWAP gate is passing: the pool's 30-minute TWAP is within N\_TICKS = 5 of the current price. If the TWAP deviation is too large, the action reverts with TwapBandExceeded.
* There is sufficient pool depth to complete the swap within configured slippage.

#### What the keeper does

1. Calls microLiq(vaultId, dRl, tickLimit, zeroForOne, amountIn) on the router.
2. The router executes a swap on the pool: tokenIn → tokenOut at the pool's current price (within tickLimit).
3. Uses the resulting tokens to mint L and reduce the vault's rLWad.

#### What the protocol enforces

* TWAP gate. This is the key defense. An attacker who manipulates the pool price to trigger micro-liq at an unfair level can't succeed because the TWAP won't follow. N\_TICKS = 5 means the instantaneous price can't drift more than 5 ticks from the 30-minute average before micro-liq is blocked.
* LTV improvement. Post-swap LTV must be <= pre-swap LTV (the swap must help, not hurt).
* Session guards. The entire micro-liq is wrapped in the standard session lifecycle; if anything is off, the transaction reverts.

#### The MicroLiq event emits

MicroLiq(vaultId, rL, tickLimit, twapOk, ltvPre, ltvPost, zeroForOne, amountIn).

#### Why no bounty

Peel has a bounty because it is pure service work: the keeper takes on gas, the protocol compensates. Micro-liquidation has no bounty because the keeper benefits from the price action of the swap itself — they pick the most favorable moment to act on the swap side, and the protocol lets them capture that. This aligns incentives without needing an explicit fee.

#### Why micro-liq doesn't seize collateral

Notice what micro-liquidation does not do: it does not transfer collateral to the keeper, does not rebate liquidators with discounted assets, does not have "liquidation bonus" parameters. The keeper's only compensation is the favorable price at which the AE-owned swap executes. This is intentional:

* No "liquidation cascade" spiraling down as each liquidation signals the next.
* No keeper getting a windfall on a market stress event.
* Keeper competition pushes the action toward the most efficient possible price for the vault.

### The residual loss path

If peel and micro-liquidation together cannot restore a vault to solvency — e.g., the pool is too thin for micro-liq and the NFTs have been fully unwound — the vault's debt can exceed its collateral floor. When the vault is eventually closed (or force-closed), the uncovered L is debited from equityLWad, reducing the sL share price.

This is the only mechanism by which sL share price can decrease. In every other situation (fee accrual, interest accrual, POL reinvestment), share price only goes up.

#### Who bears it

* Only sL holders of the affected pool. Cross-pool insulation is strict: a loss on pool A does not touch pool B.
* sL holders have always been first-loss. There is no junior tranche, no insurance fund, no external backstop.

#### How much can it be

Bounded by:

* The vault's outstanding debt at the moment of default.
* Capped at 100% of the vault's debt (you cannot lose more than was borrowed against a single vault).
* In aggregate, capped at the pool's total equity (you cannot lose more than the pool's LPs collectively deposited).

Realistically, for most vaults in most markets, the peel + micro-liq lane successfully restores solvency long before the vault reaches the point where loss would be debited from equityLWad. The residual-loss scenario is a stress-state for thin pools or extreme market conditions.

### How much margin for error do LPs have

The protocol's structural margin for sL holders on a given pool:

1. Borrowers have a 2% gap between LTV (98%) and their hard solvency line. Practically, many borrowers maintain a larger buffer to avoid peel risk, so actual LTV distributions skew lower.
2. Keeper bounty on peel eats a small fraction of the vault's idle before any loss reaches sL.
3. Peel itself is the primary safety lever — it can unwind NFT liquidity long before the vault's debt gets anywhere close to default.
4. Micro-liq catches the long tail where idle rebalancing is needed.

For LPs evaluating a pool: the risk of sL loss is essentially the risk that the pool's liquidity becomes too thin for the keeper lane to function, and a specific vault goes insolvent, and the lost L is larger than the per-pool equityLWad cushion. These conditions layer; the probability of all three co-occurring on a liquid pool is small, but not zero.

### Stress scenarios

Some scenarios worth thinking through:

#### Pair volatility spike

A large move in the pair price moves every vault's NFT positions in and out of range. Vaults with concentrated NFTs may see sharp C\_min drops. Keepers peel the worst offenders; the surge fee in DFM extracts LVR compensation during the move. Most vaults are fine; a few may need micro-liq.

#### Pool TVL drain

If LPs withdraw sL in bulk during a stress event, equityLWad drops. Utilization rises. The utilization-rate curve steepens. Borrowers see higher interest costs, incentivizing repay. Keepers find it harder to execute micro-liq if the pool is shallow. In the worst case, sL redemptions may face temporary constraints if the full-range pooled position doesn't have enough idle liquidity to meet them.

#### Oracle mis-behavior

If TruncGeoOracleMulti reverts, the DFM hook emits ExternalCallFailed and falls back to manualFeePpm. DFM fees are wrong for that block, but the pool remains functional. Engine's safety model doesn't depend on the DFM oracle — C\_min is computed from v4 state directly. So oracle issues are a fee-quality issue, not a solvency issue.

#### Hook authorization removed mid-session

AegisEngine.hookAllowed(hook) = false while sessions are in flight: existing sessions complete normally; new sessions on that pool revert. This is designed for emergency response. LPs and borrowers can always withdraw / repay; no one is locked in.

#### Governance action

If governance changes a tunable parameter (e.g., raises marketProtocolFeeBps), existing positions continue to function. There are no position-invalidating governance actions — all changes are parameter tweaks within bounded ranges, and all compile-time constants are, by definition, unchangeable without a new deployment.

### Summary

The AEGIS Engine safety model rests on four structural properties:

1. The collateral floor is computed from observable pool state, not oracles.
2. Debt and collateral are denominated in the same asset (L), eliminating cross-asset basis risk.
3. The keeper lane has two independent modes (peel, micro-liq) with gating that prevents manipulation.
4. Any uncovered loss is bounded and allocated to sL holders of the affected pool, who have always been first-loss.

The residual risk is real and bounded. Nothing in this design eliminates risk; it shapes where risk sits and who bears it.


# Technical Info

Implementation reference and integration details for working with the Aegis DFM hook

#### Hook Overview

* **Compatibility**: Works with any Uniswap V4 pool
* **Hook Address**: `0x...` (refer to registry or deployment documentation)
* **Key Function**:\
  `getCurrentFee() → uint24` — returns the current fee in basis points

***

#### Events

Aegis emits standard and custom events to support transparency, analytics, and automation:

* `DynamicFeeApplied(fee, volMetric)` — shows applied fee and volatility metric
* `LiquidityAdded`, `LiquidityRemoved` — logs LP changes
* `OracleUpdated` — indicates oracle adjustments or cap events

***

#### Migrations

| From                                | To              | Action                                                         |
| ----------------------------------- | --------------- | -------------------------------------------------------------- |
| **v3 pool**                         | **v4 w/ Aegis** | Withdraw LP position and redeposit into the Aegis-enabled pool |
| **Aegis v1 → v2** *(if applicable)* | **Aegis v2**    | One-click migration via the official UI (announced in advance) |


# Routers

## Routers

> **TL;DR – Why AEGIS DFM Is the Least‑Surprise Dynamic‑Fee Hook for Aggregators**
>
> * **Deterministic pricing.** Every fee component is a pure, on‑chain function.
> * **“Never‑higher” guarantee.** Your user pays ≤ the fee you simulated 99.75 % of the time …
> * **Edge‑case bounded & predictable.** That surge fee hits exactly one follower trade, then linearly decays to zero over the next few hours.
> * **No exotic math.** Pool invariant stays constant‑product; keep your usual Uniswap‑V3/V4 price‑impact logic.
> * **Gas‑friendly.** The only extra storage write burdening swaps is the oracle observation, similar to Uniswap V3.

***

### 1 — How the Fee Works

#### Base fee

**File & line:** `DynamicFeeManager.sol` 180‑222\
**Logic:** Clamps between per‑pool min / max using on‑chain volatility (`maxTicksPerBlock`).

#### Surge fee (only after a cap event)

| Step                     | What happens                                                                                              |
| ------------------------ | --------------------------------------------------------------------------------------------------------- |
| **Trigger**              | If a single block’s tick‑move exceeds `maxTicksPerBlock`, `TruncGeoOracleMulti` (150‑169) sets `capStart` |
| **Cap trade**            | Pays **no** surge (fee = base)                                                                            |
| **First follower trade** | Pays **base + surge**                                                                                     |
| **Subsequent trades**    | Surge decays linearly to 0 over `decaySecs` (228‑252)                                                     |

#### Public view for routers

```solidity
function getFeeState(bytes32 poolId)
        external view returns (uint24 base, uint24 surge);
```

*Returns the exact numbers the **next swap** will use—no hidden maths.*

#### No invariant changes

Permission bitmap excludes `beforeModifyPosition`; only swap callbacks enabled (`Spot.sol` 90‑111). Constant‑product forever.

### 2 — Why This Beats Other Dynamic‑Fee Hooks

| Usual pain‑point                | Typical dynamic hooks                   | **AEGIS DFM**                                         |
| ------------------------------- | --------------------------------------- | ----------------------------------------------------- |
| Hidden admin fee changes        | Upgradeable proxies, opaque owner calls | **On‑chain fee logic**                                |
| Fee can spike after simulation  | No public pre‑swap view                 | `getFeeState()` = what you will pay (unless rare cap) |
| Multiple paths to fee inflation | Policy gates, `beforeModifyPosition`    | **Single path** (cap event) + bounded surge           |
| Complicated invariants          | Logistic/TWAMM curves                   | Same *xy = k* maths you already implement             |

### 3 — Optional “Extra” Steps

1. **Whitelist byte‑code hash** (one‑time).
2. **Display live fee** to users via the same `getFeeState()` call.


# Stay In The Loop

Where to track rewards, get support, and stay updated on Aegis deployments and improvements

* **Live Pools**: [app.uniswap.org](https://app.uniswap.org) → Look for “Hook: Aegis DFM”
* **Rewards Dashboard**: [merkl.xyz](https://merkl.xyz)
* **Audit Report**: Coming soon via [Spearbit](https://spearbit.com)
* **Codebase**: [github.com/labs-solo/AEGIS\_DFM](https://github.com/labs-solo/AEGIS_DFM)
* **Support & Feedback**: Join Discord in `#aegis-dfm` or open an issue on GitHub
* **Follow Updates**: @labs-solo on X | Uniswap Governance Forum


# Deployed Contracts

Unichain (v1.0)

<table><thead><tr><th width="258.19921875">Contract</th><th width="435.015625">Address</th></tr></thead><tbody><tr><td>PoolPolicyManager</td><td><a href="https://uniscan.xyz/address/0xaaf29f2c9516062dc43c4465ef6d43ce8397cdda"><code>0xaaf29f2C9516062dc43c4465EF6d43cE8397CDDa</code></a></td></tr><tr><td>TruncGeoOracleMulti</td><td><a href="https://uniscan.xyz/address/0x22e086c085fbda3214db238f8a930d22228facb5"><code>0x22e086c085fbDA3214DB238F8a930d22228Facb5</code></a></td></tr><tr><td>DynamicFeeManager</td><td><a href="https://uniscan.xyz/address/0xe6768629156d4b713e266b87502fbeccc218a028"><code>0xe6768629156d4B713e266b87502fbeccc218a028</code></a></td></tr><tr><td>FullRangeLiquidityManager</td><td><a href="https://uniscan.xyz/address/0xa7ce35e886e58926ad0c149f4cc558e27d458a2c"><code>0xA7ce35E886e58926Ad0C149f4cc558e27D458a2c</code></a></td></tr><tr><td>Spot Hook</td><td><a href="https://uniscan.xyz/address/0xa0b0d2d00fd544d8e0887f1a3cedd6e24baf10cc"><code>0xA0b0D2d00fD544D8E0887F1a3cEDd6e24Baf10cc</code></a></td></tr></tbody></table>

Unichain (v1.1)

<table><thead><tr><th width="258.19921875">Contract</th><th width="435.015625">Address</th></tr></thead><tbody><tr><td>PoolPolicyManager</td><td><a href="https://uniscan.xyz/address/0xE072D3C7567D4B326dDdE0F77062E6758Aec0eE7"><code>0xE072D3C7567D4B326dDdE0F77062E6758Aec0eE7</code></a></td></tr><tr><td>TruncGeoOracleMulti</td><td><a href="https://uniscan.xyz/address/0xbe4F23B9488300c532A5d9d8D11B0b8163Be6ABF"><code>0xbe4F23B9488300c532A5d9d8D11B0b8163Be6ABF</code></a></td></tr><tr><td>DynamicFeeManager</td><td><a href="https://uniscan.xyz/address/0xD7deABAb9ECd3BcB3B89ab0cE2c84C8565bB8D98"><code>0xD7deABAb9ECd3BcB3B89ab0cE2c84C8565bB8D98</code></a></td></tr><tr><td>FullRangeLiquidityManager</td><td><a href="https://uniscan.xyz/address/0xa7b3e2306a0B46dbdE3B533c4e2C3Be6a24352a3"><code>0xa7b3e2306a0B46dbdE3B533c4e2C3Be6a24352a3</code></a></td></tr><tr><td>Spot Hook</td><td><a href="https://uniscan.xyz/address/0x88C9fF9FC0B22cCa42265d3f1D1C2C39E41cDACC"><code>0x88C9fF9FC0B22cCa42265d3f1D1C2C39E41cDACC</code></a></td></tr></tbody></table>


# Audits

Spearbit Audit

* **Project**: Aegis DFM (Uniswap V4 Hook)
* **Auditor**: Spearbit via Cantina
* **Status**: Audit complete - June 6, 2025
* [Read the full report](https://drive.google.com/file/d/1vUWENoQsiQxDuex0WU6mZtwJVmE50Ddc/view?usp=sharing)

Trail of Bits Audit

* **Project**: Aegis Engine (Uniswap V4 Hook + Credit Engine)
* **Auditor**: Trail of Bits
* **Status**: Audit complete - May 7, 2026
* [Read the full report](https://drive.google.com/file/d/1Lzfe49__CXGmBffFLmQ8-v7M6kSUfN8l/view?usp=sharing)


