---
title: "Pool Contract"
description: "Main AMM operations: swap, deposit, withdraw, donate, liability swap"
audience: tech
type: reference
status: live
lang: en
updated: "2026-09-03"
publish: true
---
# Pool Contract

`Pool` is the contract an integrator actually calls: swap, deposit, withdraw, donate, liability swap, flash and hook entry points are all functions on one pool proxy. This page is the reference for that surface - signature, parameters, return struct, events and revert conditions for each operation - plus the anchor-tree routing and the state rules every operation shares. The pricing maths behind the numbers returned here lives under [Inventory Management](/docs/1-1-1-inventory-management) and [Spread & Fees](/docs/1-1-4-spread-fees).

---

## 1. Overview

`Pool` (`Pool.sol`) implements AIMM's primary liquidity operations as an ERC-1967 beacon proxy deployed via `PoolFactory` (every pool reads `PoolFactory.implementation()`, the factory being the beacon, so a timelocked impl swap re-points them all: [Deployment & Upgrades §4.1](/docs/3-2-deployment-upgrades#41-pool-beacon-upgrade-poolfactory-7-day-timelock)). It is the whole pool-side entry surface: swap, liquidity, admin, flash and hook entry points all live on it and it dispatches to linked libraries (`deposit`/`withdraw`/`withdrawTo`/`donate`/`swapLiability` to `PoolLiquidity.sol`, `swap` to `Pricing.sol`, config writes to `PoolConfig.sol`); see [AIMM Overview §2.1](/docs/1-overview#21-standalone-singletons--pool-proxies) for the full no-Diamond architecture.

Marks come from the `IOracle` each asset's `OracleConfig` names - in the shipped fleet the keeper-pushed `ExternalOracle` - and never from the pool's own reserves or trade history; see [Oracles](/docs/3-4-oracles).

**Key Functions:**
- `swap()` - exchange tokens with oracle-aware pricing (single entry; there is no on-chain batch/multi-swap call)
- `deposit()` - add single-sided liquidity
- `withdraw()` / `withdrawTo()` - remove liquidity with coverage-based haircuts (same or different asset)
- `donate()` - gift tokens to LPs (raises the liquidity index; no LP mint)
- `swapLiability()` - swap LP positions between assets

---

## 2. Swap

### 2.1. Function Signature

```solidity
function swap(
    address tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 minAmountOut,
    address recipient,
    uint256 deadline
) external payable returns (uint256 amountOut)
```

(The full `SwapQuote` struct is only returned by the view `getSwapQuote()`.)

### 2.2. Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `tokenIn` | address | Token being sold (or NATIVE sentinel) |
| `tokenOut` | address | Token being bought |
| `amountIn` | uint256 | Exact amount to sell |
| `minAmountOut` | uint256 | Slippage protection |
| `recipient` | address | Receives output tokens |
| `deadline` | uint256 | Unix timestamp, inclusive. Reverts `Err.Expired` if `block.timestamp > deadline`. Pass `type(uint256).max` to opt out; there is no `0` sentinel |

### 2.3. Execution Flow

```mermaid
flowchart TB
  V[Validate inputs and feature flags] --> P[Pull input]
  P --> R[Resolve anchor-tree path]
  R --> L[Per-leg mark skew traverse]
  L --> S[Settle spread toll fee]
  S --> U[Update reserves and fees]
  U --> M[Validate minAmountOut]
  M --> T[Transfer output]
```

(No oracle write happens on swap - the keeper is the only feed writer.)

### 2.4. Return Value

`swap()` returns `uint256 amountOut`. The view `getSwapQuote()` returns:

```solidity
struct SwapQuote {
    uint256 amountOut;       // Tokens received
    uint256 amountIn;        // Tokens paid
    uint16 spreadPbps;        // Path ROUND-TRIP spread width (0.0001% units); this swap pays half
    uint256 protoFee;        // Protocol share of that one half-spread fee
    uint256 lpFee;           // LP share of the same fee
    int8 skewIn;             // Input asset inventory skew
    int8 skewOut;            // Output asset inventory skew
    uint256 markPrice;       // Path oracle mark, exact WAD, tokenOut per tokenIn, chained across legs
    uint256 midPrice;        // Inventory-skewed center the book quotes around, exact WAD
    uint256 covToll;         // Coverage toll withheld from gross output BEFORE the fee (tokenOut units)
    address[] routeHops;     // Full routing path
    uint256[] hopAmounts;    // Amount at each hop
    uint256[] hopPrices;     // Per-leg realized exec price, WAD; analytics views only
}
```

OEV decomposes exactly off the two price fields: `(exec - mid)/mid` is the extractable part, `(mid - mark)/mark` is inventory skew.

`markPrice` and `midPrice` are exact WAD, not the packed B64 the V1 oracle stores: both are computed and handed straight out as a struct field and a log arg, never into a storage slot, so the packed encode saves no log bytes (ABI pads every non-indexed event arg to a full word) while costing 917 to 1,557 gas per swap and truncating the mantissa to 52 bits. Where a packed mark does buy the slot, the oracle uses it: [Oracles §5](/docs/3-4-oracles#5-b64-float-encoding).

### 2.5. Events

```solidity
event Swapped(
    address indexed sender,
    address indexed recipient,
    address indexed tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 amountOut,
    uint16 spreadPbps,
    uint256 protoFee,
    uint256 lpFee,
    uint256 markPrice,
    uint256 midPrice,
    uint256 covToll
);
```

`protoFee + lpFee` is the whole fee and is denominated in `tokenOut`: there is no input-leg fee, so an indexer credits 100% of a swap's fee to `tokenOut`.

---

## 3. Deposit

### 3.1. Function Signature

```solidity
function deposit(
    address token,
    uint256 amount
) external payable returns (DepositResult memory result)
```

LP shares are credited to `msg.sender`; there is no `minLPAmount` / `recipient` parameter (share pricing is deterministic off the liquidity index).

### 3.2. Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | address | Asset to deposit (NATIVE sentinel for native + `msg.value`) |
| `amount` | uint256 | Amount to deposit |

### 3.3. Mechanics

```mermaid
flowchart TB
  V[Validate inputs] --> P[Pull tokens]
  P --> L[Compute lpAmount]
  L --> U[Update reserves and mint]
```

After state update, optional `postInflow` dispatch when the asset hook has `HOOK_POST_INFLOW` (see §10).

### 3.4. Coverage Impact

Deposits always improve coverage when the leg starts under-covered:

$$
c_0 = R/L, \qquad c_1 = \frac{R + x}{L + x}
$$

If $c_0 < 1$ then $c_1 > c_0$. Proof: [Invariants I-6](/docs/1-1-8-invariants#i-6-deposits-do-not-worsen-coverage-donations-do-not-change-it).

### 3.5. Return Value

```solidity
struct DepositResult {
    uint256 lpAmount;       // LP tokens minted (net of deadLp)
    uint256 actualDeposit;  // Tokens accepted
    uint256 deadLp;         // Shares carved out and sunk to address(0) if this opened the leg
}
```

---

## 4. Withdraw

### 4.1. Function Signature

```solidity
function withdraw(
    address token,
    uint256 lpAmount,
    uint256 minAmountOut,
    uint256 deadline
) external returns (WithdrawResult memory result)
```

Output goes to `msg.sender` (no `recipient` parameter). `withdrawTo(tokenFrom, tokenTo, lpAmount, minAmountOut, deadline)` withdraws into a **different** asset, priced through the anchor-path quote. `deadline` is inclusive, same convention as `swap`. Deposit and donate carry no deadline: they set no `minOut` and mint at the current index, so there is nothing stale to protect.

### 4.2. Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | address | Asset to withdraw |
| `lpAmount` | uint256 | LP shares to burn |
| `minAmountOut` | uint256 | Slippage protection |
| `deadline` | uint256 | Unix timestamp, inclusive |

### 4.3. Haircut Mechanism

When coverage < 100%, withdrawals take a **linear** haircut (`PoolLiquidity.applyHaircut`):

```solidity
function applyHaircut(
    uint256 amount,
    uint128 reserves,
    uint128 liabilities,
    uint16 haircutSuppressorBps
) internal pure returns (uint256 actualAmount, uint256 haircutAmount) {
    if (liabilities == 0 || reserves >= liabilities) return (amount, 0);
    uint256 deficit = ((liabilities - reserves) * 1e18) / liabilities;
    // No `>= 20000 ? 0` branch: config rejects that range, initAsset seeds BPS.
    uint256 factor = 1e18 - (haircutSuppressorBps * 1e18 / HAIRCUT_SUPPRESSOR_FULL_BPS);  // 20_000
    uint256 haircutRatio = (deficit * factor) / 1e18;      // LINEAR, no power law
    if (haircutRatio > 1e18) haircutRatio = 1e18;
    haircutAmount = (amount * haircutRatio + 1e18 - 1) / 1e18;   // rounds UP, dust stays with the pool
    actualAmount = amount - haircutAmount;
}
```

`setAssetParams` rejects `haircutSuppressorBps >= HAIRCUT_SUPPRESSOR_FULL_BPS` (20_000) outright, so the haircut can never be fully switched off: an under-covered leg always socializes at least part of its deficit, and the first mover cannot exit at face value. Out-of-range stored state underflows here instead of silently zeroing the haircut, which fails closed. A coverage-walled leg (`kappaCovBps > 0`) is forced to `haircutSuppressorBps = 0`, the full haircut.

### 4.4. Haircut Examples

| Coverage | Suppression | Factor | Haircut |
|----------|-------------|--------|---------|
| 90% | 0 | 1.0 | 10% |
| 80% | 0 | 1.0 | 20% |
| 80% | 10000 | 0.5 | 10% |
| 50% | 15000 | 0.25 | 12.5% |

Suppression 20000 is not a configurable row: it is the rejected upper bound. **Nor are the 10000 and 15000 rows reachable on a listed asset**: `requireNeverDepletable` forces `kappaCovBps != 0` at every writer and `requireWallOk` then forces `haircutSuppressorBps == 0` alongside it, so every leg of the Arc fleet runs suppression 0 and the full linear haircut. The suppressed rows are the arithmetic of a value the write path will not admit.

### 4.5. Return Value

```solidity
struct WithdrawResult {
    uint256 amountOut;  // Tokens received (after haircut)
    uint256 lpBurned;   // LP tokens burned
}
```

---

## 5. Donate

### 5.1. Function Signature

```solidity
function donate(
    address token,
    uint256 amount
) external payable
```

### 5.2. Purpose

Gifts value to existing LPs (protocol fee redistribution, third-party yield injection, recovery top-ups). The donation raises the **liquidity index**, so every existing LP share is worth more underlying.

`donate()` accrues no LP credit to the sender: it mints no shares and takes no position. The sender **is** logged - `event Donated(address indexed sender, address indexed token, uint256 amount)` (`IPool.sol`) - so the call is traceable, just not creditable.

**Donating to a leg that has never been credited strands the gift.** `raiseIndex` no-ops when `liabBefore == 0`, so everything above the dead-share seed lands in `liabilities` backing no share and is inherited by nobody. Deposit first if the intent is to open a leg; `donate` is the non-dilutive top-up for a leg that already has LPs.

### 5.3. Mechanics

```mermaid
flowchart TB
  V[Validate token] --> P[Pull tokens]
  P --> R[Add to reserves]
  R --> L[Add to liabilities]
  L --> I[Raise liquidity index]
```

### 5.4. Coverage Impact

Coverage is **unchanged** by a donation (reserves and liabilities rise equally). The value transfer to LPs happens through the liquidity index, not through coverage:

`c_1 = (R + a) / (L + a) ~ c_0`

This still helps an under-covered pool in absolute terms (the deficit `L - R` stays constant while `L` grows, so the *relative* deficit shrinks), but the coverage ratio itself does not improve.

---

## 6. Liability Swap

### 6.1. Function Signature

```solidity
function swapLiability(
    address tokenIn,
    address tokenOut,
    uint256 lpAmountIn,
    uint256 minLpAmountOut,
    uint256 deadline
) external returns (uint256 lpAmountOut)
```

(The function is named `swapLiability`, not `liabilitySwap`; shares move for `msg.sender` - no `recipient` parameter.)

### 6.2. Purpose

Moves an LP position from one asset to another without a withdraw + deposit cycle.

### 6.3. Mechanics

```mermaid
flowchart TB
  E[Enable bits and halt checks] --> L[Compute liabIn]
  L --> H1[Haircut input if under-covered]
  H1 --> Q[Anchor-path quote]
  Q --> C[Mark cap on the payout]
  C --> D[Depeg band guard]
  D --> H2[Haircut output if under-covered]
  H2 --> S[Adjust liabilities burn mint]
  S --> M[Check minLpAmountOut]
```

The input-side haircut is what stops a liability swap out of an under-covered leg from escaping the exit toll and dumping the deficit on the destination leg's LPs. The **mark cap** (`PoolLiquidity._markCap`, shared with `withdrawTo`'s cross path) closes the other half: the haircut face is credited through the path's oracle **mark**, `min(amountOut, fair · markPrice · 10^(d_to - d_from))`, never through the inventory-skewed mid. The skew is a level that cancels on a trader's paired crossings but not on a one-way LP conversion, and on an under-covered leg it is one-sided; the explicit decimal factor is required because `markPrice` is a whole-unit WAD ratio while the amount walk is rescaled per leg. Liability re-denomination is deliberately protocol-fee exempt: it moves no reserves, so there is no physical outflow to skim from. Both fee legs land as reduced net liability instead.

### 6.4. Requirements

- `LIABILITY_SWAP_ENABLED_BIT` must be set on **both** assets

---

## 7. Swap Paths (Anchor Tree)

### 7.1. Topology

Every asset anchors to one parent, which need not be the base. Every chain terminates at the base, the root.

```mermaid
flowchart TB
  WBTC[WBTC base] --> WETH
  WBTC --> USDC
  WETH --> stETH
  WETH --> weETH
  USDC --> USDT
  USDC --> DAI
```

Examples:
- `stETH → DAI`: path `[stETH, WETH, WBTC, USDC, DAI]` (4 legs; endpoints settle, interiors do not)
- `USDT → DAI`: path `[USDT, USDC, DAI]` (2 legs; LCA is USDC; BTC mark never read)

### 7.2. Path Computation

The path is the unique tree path between the endpoints: walk up from each endpoint to the lowest common ancestor, then concatenate. There is nothing to search and no alternative decomposition. A malformed tree (an anchor chain that does not terminate at the root) reverts.

### 7.3. Path Constraints

| Constraint | Value | Purpose |
|------------|-------|---------|
| Max depth | `MAX_DEPTH = 4` | Bounds gas and the summed path fee |
| Max nodes | `MAX_PATH_LENGTH = 9` (8 legs) | Two walks meeting at the LCA; depth bounds one walk, a path is two of them ([Anchor Path Pricing §1](/docs/1-1-3-anchor-path-pricing#1-topology)) |
| No cycles | enforced | `validateAnchor` walks to the root with an explicit `current == asset` check **and** an unconditional step cap. A disconnected 2-cycle never reaches the root, so a depth counter alone is insufficient |

Why a tree rather than a star: correlated assets get their own edge, their own feed, their own sigma and their own fee floor. A BTC-based pool quotes `USDT -> DAI` off two stable feeds instead of round-tripping through the BTC mark.

**Topology is configuration, not a contract state.** The tree is general - any asset may anchor to a non-base parent up to `MAX_DEPTH = 4`, which is how curators price correlated pairs against their own reference and run distinct fee profiles inside one pool. Each pool's `anchor` cells decide its actual shape: an empty cell anchors the leg directly to the base and makes it an endpoint leg; a filled cell deepens the tree and activates the interior fence for the routes through it. Depth 4 is the contract capability: the schema, the deploy scripts and the fence budgets are all general to it and nothing special-cases a shallower tree. Activating a deep edge needs the mark feed serving the cross pair rather than any contract change; the sequence is `addAnchorFeeds()` then `requestAnchorTree()` then, after the critical timelock, `executeAnchorTree()`. Every deep leg must carry `quoteUnit = 0` and its own reference feed.

The depeg picture does **not** generalize for free. What does scale with depth is the feed-relative band: `PoolIOLib.priceBandGuardPath` runs it on every interior node except the base, and `priceBandGuard` runs it on both endpoints. The base parity halt does not scale: it is gated on the hop being the base token, so it tests the base mark against `1e18` and nothing else. Neither is a peg test for a parent: `OracleConfig.refFeedId` is a same-unit agreement check between two independent attestations of one pair, and a parent-depeg breaker needs a field that does not exist. See [Anchor Path Pricing §7.1](/docs/1-1-3-anchor-path-pricing#71-what-reffeedid-is-and-what-it-is-not).

---

## 8. State Management

### 8.1. Transient Storage (EIP-1153)

Transient storage holds:
- **Reentrancy guard**: Prevents recursive calls
- **Oracle cache**: Avoid redundant price lookups within transaction

### 8.2. Reentrancy Protection

`Pool` inherits solady's `ReentrancyGuardTransient` via `@btr-shared/base/TransientGuard.sol`, which overrides `_useTransientReentrancyGuardOnlyOnMainnet()` to return `false`. Solady defaults that to `true` and so TSTOREs only on chainid 1, falling back to SSTORE everywhere else at a measured +4,874 gas cold / +2,874 warm per guarded call; every target chain runs Cancun-or-later, so the fallback is pure cost. `nonReentrant` is not hand-rolled here.

A **second, independent** transient slot, `FLASH_INFLIGHT_SLOT` (`PoolIOLib.sol`), is set for the duration of a flash callback and read by `PoolIOLib.requireNoFlash` at four sites: `PoolIOLib.pull` (the reserve-crediting chokepoint behind `deposit`, `donate` and `swap`), `PoolLiquidity.withdrawTo`, `PoolLiquidity.swapLiability`, and the hook writers on `Pool`. The reentrancy slot is not held across the ERC-3156 callback, which is why the flash flag exists at all.

### 8.3. Liquidity Index Writers

Coverage is never rewritten by a background process: it moves only when a swap, deposit, withdrawal or donation moves it. Two live pricing mechanisms restore it, neither on a clock and neither reducing liabilities on its own.

- **The exit haircut** (§4.3) makes every withdrawal from an under-covered leg pay a deficit-proportional penalty, so the leaver takes its own share of the deficit rather than handing it to those who stay. At the shipped `haircutSuppressorBps = 0` a same-asset exit is coverage-**neutral**: the payout is exactly `x·R/L` against a full-face `x` liability burn, so `c` is unchanged. What raises the source leg's `c` is a **cross** exit - `withdrawTo` into a different token, or `swapLiability` - which burns the full face from this leg's liabilities while debiting the payout from the destination leg.
- **The convex coverage toll** (`kappaCovBps`) prices any swap that drains a walled leg further, rising superlinearly as `c` falls.

Every listed asset including the hub must have `kappaCovBps > 0` ([Invariants §I-9](/docs/1-1-8-invariants#i-9-coverage-toll-is-charge-only-and-terminal-only)). The toll is output-only: hub κ prices taking the hub out.

`liquidityIndexWad` has a small, explicit set of writers, each emitting `IndexUpdated` with a reason code (`PoolConstantsLib.sol`):

| Reason | Constant | Direction | Site |
|---|---|---|---|
| 0 | `INDEX_REASON_DONATE` | up | `PoolLiquidity.donate` |
| 1 | `INDEX_REASON_YIELD` | up | `Pool.hookCreditYield` |
| 3 | `INDEX_REASON_WRITEDOWN` | down | `Pool.hookWriteDown` |
| 4 | `INDEX_REASON_FEE` | up | `PoolLiquidity.accrueLpFee` |

`hookWriteDown` is the only writer that lowers the index, and it is reachable only from a configured asset hook booking a realized loss on rehypothecated reserves.

`RiskConfig.flags` live bits: 0 `HALT_RISK_BIT`, 1 `SWAP_ENABLED_BIT`, 2 `LIABILITY_SWAP_ENABLED_BIT`, 4 `FLASH_ENABLED_BIT`, 6 `HALT_GUARDIAN_BIT`; `HALT_MASK = 0x0041`.

---

## 9. Native Token Support

### 9.1. Sentinel Address

```solidity
address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
```

### 9.2. Auto-Wrapping

```solidity
// PoolIOLib.wrap
if (token != NATIVE) return token;
if ($.wnative == address(0)) revert InvalidInput();   // fail closed on a no-wrapper chain
return $.wnative;
```

### 9.3. Transfer Handling

```solidity
// PoolIOLib.pull
requireNoFlash();
if (token == NATIVE) {
    if ($.wnative == address(0)) revert InvalidInput();          // no wrapper on this chain
    if (msg.value < amount) revert InsufficientAmount(msg.value, amount);
    IWETH9($.wnative).deposit{value: amount}();
    uint256 excess = msg.value - amount;                          // overpay is refunded,
    if (excess > 0) safeTransferETH(msg.sender, excess);          // not rejected
    return amount;
}
// Payable entrypoints exist solely for the native sentinel.
if (msg.value != 0) revert InvalidInput();
uint256 balBefore = _balanceOf(token);
safeTransferFrom(token, msg.sender, address(this), amount);
return _balanceOf(token) - balBefore;                             // FoT captured by delta
```

```solidity
// PoolIOLib.push
if (token == NATIVE) {
    if ($.wnative == address(0)) revert InvalidInput();
    IWETH9($.wnative).withdraw(amount);
    safeTransferETH(to, amount);
} else {
    safeTransfer(token, to, amount);
}
```

Three details a naive `msg.value != amount` reading would get wrong: the native branch compares with `<` and **refunds** the excess rather than reverting on it; the ERC-20 branch rejects any attached value outright, so value cannot land outside the reserve ledger; and the amount credited to reserves is the measured balance **delta**, so a fee-on-transfer token books what actually arrived. **The native path is dead on Arc**: the chain ships no WETH9-style wrapper, `initialize` was called with `wnative_ == address(0)` (`Pool.sol`), and every `NATIVE` call therefore reverts `InvalidInput` at the wrapper check.

---

## 10. Hook Integration

Per-asset `IPoolHooks`: **2** void callbacks (`preOutflow`, optional `postInflow`).

- **Storage**: `HookSlot {target, flags}` + `invested` ($R_{\mathrm{inv}}$).
- **Dual ledger**: $R = R_{\mathrm{liq}} + R_{\mathrm{inv}}$; Pricing / coverage use $R$, executable capacity is $R_{\mathrm{liq}}$.
- **Dispatch**: liquid check first (0 CALL if buffer OK); shortfall → hard recall via `preOutflow`, fail-closed. Deploy on `postInflow` / keeper, not on swap.
- **Yield** via harvest → `hookCreditYield` / `hookWriteDown`.
- **Admin**: timelocked queue via generic Admin `requestOp(UPDATE_HOOK)` (execute via `executeSetAssetHook`, cancel via `cancelTimelock`); direct `clearAssetHook` (`invested == 0`).

See [Hooks](/docs/5-1-3-hooks).

---

## 11. Error Handling

| Error | Condition |
|-------|-----------|
| `ZeroValue()` | Amount is zero |
| `InsufficientAmount(available, required)` | Not enough reserves/balance |
| `ThresholdViolation(value, threshold)` | Slippage exceeded (`minAmountOut` / `minLpAmountOut`); also a `minLiquidity` breach on withdraw (`PoolLiquidity.withdrawTo`) and the confidence-interval halt at `MAX_CONFIDENCE_HALT_BPS` (`FeedMathLib.gate`) |
| `FeatureDisabled(resource)` | Swap/flash disabled |
| `Reentrancy()` | Recursive call attempt |
| `InvalidState()` | Pool not initialized (`Pool.whenInitialized`); also a wiped leg, `liquidityIndexWad == 0`, at `PoolLiquidity.mintIndex`, and a deposit into a leg holding `reserves == 0` against non-zero `liabilities` |

---

## 12. Gas Costs

Swap figures measured cold, end-to-end (quartic preset curve on the hot path), by `test/gas/GasProbe.t.sol`. Warm repeats of the same swap cost roughly a third of the cold figure (56.6k / 55.6k / 71.1k):

| Operation | Gas (Cold) |
|-----------|------------|
| Swap base -> spoke | 192.5k |
| Swap spoke -> base | 191.5k |
| Cross-spoke swap (spoke -> base -> spoke) | 222.4k |
| Deposit | ~100k |
| Withdraw | ~120k |
| Donate | ~60k |

Curve components: eval 5.4k cold; O(1) range integral 11.2k cold (flat in trade size and segments crossed).

---

## 13. Related Documentation

- [Inventory Management](/docs/1-1-1-inventory-management) - Coverage and skew
- [Liquidity Shaping](/docs/1-1-2-liquidity-shaping) - Preset depth curves
- [Spread & Fees](/docs/1-1-4-spread-fees) - Fee calculation
- [Feed Oracle](/docs/1-2-2-internal-oracle) - external keeper-pushed price feed (`ExternalOracle`)
- [Hooks](/docs/5-1-3-hooks) - Dual ledger / rehypothecation buffer
- [Admin](/docs/1-2-3-admin), [Flash](/docs/1-2-4-flash) - peripheral singletons calling `Pool` externally
