> For the complete documentation index, see [llms.txt](https://gotts.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gotts.gitbook.io/docs/gotts-vaults/vault/06-contracts.md).

# Solidity Contracts

> **Part of**: [Vault PRD](/docs/gotts-vaults/vault.md) | **Last Updated**: 2026-02-16

***

## Solidity Contract Specifications

Contracts are organized into three groups:

* **Part A (Core v1)**: Contracts that MUST ship for mainnet launch. Numbered 10.0-10.11.
* **Part B (Phase 3 Safety)**: Proxy and safety contracts that are mainnet-blocking. Numbered 10.12-10.17.
* **Part C (Expansion)**: Post-v1 contracts for deferred tracks. Numbered 10.E1-10.E11.

> **Section numbering note**: Sections 10.0-10.17 are sequentially numbered. Expansion contracts use a `10.E` prefix to clearly distinguish them from core scope. Cross-references from other documents should use section numbers from this file.

### v1 Contract Scope (Normative)

Only the following contracts MUST ship for the core-v1 launch. All other contracts in this document are expansion-track and should not block audit or deployment timelines.

| Contract                                 | Section | Part       | Phase   | Status                       |
| ---------------------------------------- | ------- | ---------- | ------- | ---------------------------- |
| `IdentityRegistryAdapter`                | 10.0    | A (Core)   | Phase 1 | Not started                  |
| `AgentVaultFactory`                      | 10.1    | A (Core)   | Phase 1 | **Blocked** -- not yet built |
| `AgentVaultCore`                         | 10.2    | A (Core)   | Phase 1 | Partial                      |
| `VaultHook`                              | 10.3    | A (Core)   | Phase 1 | Built                        |
| `FeeModule`                              | 10.5    | A (Core)   | Phase 1 | Not started                  |
| `AgentGatedPool`                         | 10.6    | A (Core)   | Phase 1 | Built                        |
| `NAVAwareHook`                           | 10.7    | A (Core)   | Phase 1 | Not started                  |
| `LaunchFeeHook`                          | 10.8    | A (Core)   | Phase 1 | Not started                  |
| `OnboardRouter`                          | 10.15   | B (Safety) | Phase 2 | Not started                  |
| `IdentityGuardian`                       | 10.15a  | B (Safety) | Phase 2 | Not started                  |
| `StrategyAuctionModule`                  | 10.16   | B (Safety) | Phase 3 | Not started                  |
| `AgentProxy` (announce + delay + cancel) | 10.12   | B (Safety) | Phase 3 | **CRITICAL -- Not started**  |

**Not in v1 scope (Part C)**: `RecursiveLendingAdapter` (10.E1), `TrancheModule` (10.E2), `PendleAdapter` (10.E3), `CreditDelegationAdapter` (10.E4), `LiquidityRouter` (10.E5), `BondMMHook` (10.E6), `RiskEngine expansion features` (10.E7), `ParameterDecisionTable expansion` (10.E8), `Hook Kill-Switch` (10.E9), `RebalanceIntentModule` (10.E10).

**v1 `totalAssets()` simplification**: For v1 (no CCA, no adapters), the formula is:

```
totalAssets = idleCapital + sum(lpPositions[k].totalValue) - accruedFees
```

Two components instead of the full five. CCA bid valuation, claimed token mark-to-market, and external yield positions are excluded until those modules ship.

***

***

## Part A: Core v1 Contracts (Must Ship for Mainnet)

The following contracts are required for the core-v1 launch. They cover vault creation, core operations, fee management, V4 hook integration, and on-chain risk infrastructure.

***

### 10.0 IdentityRegistryAdapter (ERC-8004 Decoupling)

**Rationale**: ERC-8004 was deployed January 29, 2026 and is still in Draft status. The standard's interface types, registration costs, and anti-spam mechanisms are being refined. Since ERC-8004 is the protocol's spine (every deposit, withdrawal, vault creation, and reputation lookup depends on it), a change to the standard's interface would break every contract that makes direct calls to fixed registry addresses. An adapter/proxy pattern absorbs interface changes without protocol-wide migration.

```solidity
interface IIdentityRegistryAdapter {
    /// @notice Check if an agent is registered and not frozen
    function isValidAgent(uint256 agentId) external view returns (bool);

    /// @notice Get the wallet address for a registered agent
    function getAgentWallet(uint256 agentId) external view returns (address);

    /// @notice Get the effective reputation score (post-decay if transferred)
    function getEffectiveReputation(uint256 agentId) external view returns (uint256);

    /// @notice Check if an agent's credential is frozen
    function isCredentialFrozen(uint256 agentId) external view returns (bool);
}
```

**Design**:

* The factory stores a single `identityAdapter` address (not the raw registry address)
* All vault contracts call identity functions through the adapter, never directly
* The factory owner can update `identityAdapter` via `setIdentityAdapter(address)` with `longTimelock` (3-7 days, D-059)
* If the ERC-8004 interface changes, only the adapter contract needs redeployment -- vaults continue operating unchanged
* The adapter validates inputs and returns normalized outputs regardless of the underlying registry version

**v1 implementation**: The v1 adapter is a thin pass-through to the current ERC-8004 contracts (`0x8004A818...` and `0x8004B663...`). No logic beyond interface normalization. The value is in the indirection layer, not the v1 logic.

***

### 10.1 AgentVaultFactory.sol

The factory is the protocol's core deployment surface. Any ERC-8004 registered agent can call `createVault()` to deploy a new vault instance via CREATE2, producing deterministic addresses. The factory maintains an on-chain registry of all deployed vaults, enabling discovery, enumeration, and composition.

Design choices follow the Morpho Vault pattern: zero protocol fees on the base layer (vault creators keep 100% of their configured fees), permissionless creation (any registered agent can deploy), and immutable core parameters with mutable strategy parameters controlled by the vault creator.

**Auto-Market Creation**: When `autoPoolEnabled` is true (the default for hook-enabled vaults), the factory automatically creates a Uniswap V4 pool for the vault's share token paired with the base asset. This mirrors ClankerHook's proven model (164K+ auto-created pools, $3.1B+ volume) but applied to yield-bearing vault shares. The auto-created pool uses the NAVAwareHook (Section 10.7) for fair pricing and optionally the LaunchFeeHook (Section 10.8) for MEV protection during the initial trading window. The factory flow becomes: deploy vault → deploy hooks → `PoolManager.initialize()` → share pool exists → first depositor's shares are immediately tradeable on Uniswap.

```solidity
interface IAgentVaultFactory {
    /// @notice Deploy a new vault. Caller must be ERC-8004 registered.
    /// @param config Vault configuration (base asset, fee params, CCA settings, hook config)
    /// @return vault Address of the deployed AgentVaultCore
    /// @return hook Address of the deployed VaultHook (if configured)
    function createVault(VaultConfig calldata config) external returns (address vault, address hook, PoolId sharePool);

    /// @notice All vaults deployed by a specific agent
    function getVaultsByCreator(uint256 agentId) external view returns (address[] memory);

    /// @notice All vaults in the registry
    function getAllVaults() external view returns (address[] memory);

    /// @notice Total number of deployed vaults
    function vaultCount() external view returns (uint256);

    /// @notice Check if an address is a factory-deployed vault
    function isVault(address vault) external view returns (bool);
}

struct VaultConfig {
    address baseAsset;            // USDC, WETH, etc.
    uint256 creatorAgentId;       // ERC-8004 token ID of the creating agent
    uint16 managementFeeBps;      // Annual management fee (max 500 = 5%)
    uint16 performanceFeeBps;     // Performance fee above HWM (max 5000 = 50%)
    uint256 minReputation;        // Minimum reputation score to deposit (0 = open)
    bool ccaEnabled;              // Whether this vault participates in CCAs
    bool hookEnabled;             // Whether to deploy a VaultHook
    HookConfig hookConfig;        // V4 hook parameters (if enabled)
    string metadataURI;           // IPFS/HTTPS URI for vault description, strategy docs

    // --- V4 Share Pool Configuration (auto-market) ---
    bool autoPoolEnabled;         // Auto-create V4 pool for share token (default: true when hookEnabled)
    uint16 navSpreadBps;          // NAV-aware hook spread (default: 50 = 0.50%)
    bool launchFeeEnabled;        // Enable descending-fee MEV protection on share pool (default: true)
    uint16 launchFeeMaxBps;       // Max fee at pool creation (default: 8000 = 80%)
    uint32 launchFeeDecaySeconds; // Duration of fee decay (default: 120 seconds)

    // --- Rehypothecation Configuration (opt-in) ---
    bool rehypothecationEnabled;  // Route idle out-of-range liquidity to lending (default: false)

    // --- Liquidity Density Functions (opt-in) ---
    bool ldfEnabled;              // Use Bunni v2-style continuous density functions instead of discrete ticks (default: false)

    // --- Strategy Auction Configuration (opt-in) ---
    bool strategyAuctionEnabled;  // Enable am-AMM management auctions for this vault (default: false)

    // --- On-Chain Disclosure (required) ---
    VaultDisclosure disclosure;   // Mandatory transparency metadata (see D-024)
}

/// @notice On-chain vault disclosure standard.
/// @dev Implements the minimal disclosure framework proposed by Zbandut et al.
///      "Institutionalizing Risk Curation in Decentralized Credit" (arXiv:2512.11976, Dec 2025).
///      Factory enforces non-empty disclosure at vault creation. Updateable by Curator with 48h timelock.
struct VaultDisclosure {
    bytes32 strategyHash;        // IPFS hash of strategy description document
    uint8 riskRating;            // Self-assessed risk rating 1-10 (1=conservative, 10=aggressive)
    address[] activeAdapters;    // List of active strategy adapter contracts
    uint16 maxDrawdownBps;       // Declared maximum drawdown tolerance (basis points)
    uint16 targetApyBps;         // Declared target APY (basis points, 0 = unspecified)
    uint256 lastAuditTimestamp;  // Unix timestamp of last formal audit (0 = unaudited)
    bytes32 auditReportHash;     // IPFS hash of audit report (bytes32(0) = none)
}
```

**Events**:

```solidity
event VaultCreated(
    address indexed vault,
    address indexed hook,
    uint256 indexed creatorAgentId,
    address baseAsset,
    bool ccaEnabled,
    bool hookEnabled,
    PoolId sharePool         // V4 pool for share token (zero if autoPoolEnabled=false)
);
```

#### Vault Registry Data Structure

The factory maintains three on-chain data structures for vault discovery:

```solidity
mapping(address => bool) public isVault;                    // O(1) membership check
address[] internal _allVaults;                               // enumeration via index
mapping(uint256 => address[]) internal _vaultsByCreator;    // per-creator lookup
```

`getAllVaults()` returns the full array. For large registries (1000+ vaults), use paginated access:

```solidity
function getVaults(uint256 offset, uint256 limit) external view returns (address[] memory);
function getVaultsByCreator(uint256 agentId, uint256 offset, uint256 limit) external view returns (address[] memory, uint256 total);
```

**Gas analysis**: `getAllVaults()` costs \~5,000 gas base + \~200 per vault. At 10,000 vaults this reaches \~2M gas which exceeds some RPC `eth_call` limits. Pagination is required for production use; `getAllVaults()` is retained for backward compatibility and small registries.

#### CREATE2 Salt Derivation

Vault addresses are deterministic via CREATE2. The salt incorporates creator identity and a nonce to allow multiple vaults per creator with the same base asset:

```solidity
bytes32 salt = keccak256(abi.encode(msg.sender, config.creatorAgentId, config.baseAsset, nonce));
```

Agents can pre-compute their vault address before deployment:

```solidity
function computeVaultAddress(VaultConfig calldata config, uint256 nonce) external view returns (address);
```

This enables counterfactual deposits (agents can approve the vault address before it exists) and deterministic cross-chain deployment (same config + nonce = same address on every chain).

#### Upgrade Path

**Decision: Immutable vaults, upgradeable factory template** (following the Morpho pattern).

* Individual vaults are NOT upgradeable. Once deployed, a vault's core logic is immutable. This eliminates upgrade-related governance attacks and simplifies auditing.
* The factory stores an `implementation` address used as the template for new vault deployments (minimal proxy / EIP-1167 clone pattern). The factory owner can update `implementation` via `setImplementation(address)` with `longTimelock` (3-7 days, D-059).
* Existing vaults are NOT affected by implementation upgrades. Only newly created vaults use the updated template.
* Migration path for existing vaults: deploy new vault with updated implementation, migrate deposits via withdraw-from-old + deposit-to-new (no admin migration).

#### Factory Parameter Validation

`createVault()` validates all `VaultConfig` fields before deployment:

| Parameter                 | Validation Rule                                                   | Revert Reason       |
| ------------------------- | ----------------------------------------------------------------- | ------------------- |
| `baseAsset`               | Must be non-zero address, must be ERC-20                          | `InvalidBaseAsset`  |
| `creatorAgentId`          | Must be valid ERC-8004 ID, caller must control the agent's wallet | `InvalidCreator`    |
| `managementFeeBps`        | Must be <= `MAX_MANAGEMENT_FEE_BPS` (500)                         | `FeeExceedsCap`     |
| `performanceFeeBps`       | Must be <= `MAX_PERFORMANCE_FEE_BPS` (5000)                       | `FeeExceedsCap`     |
| `minReputation`           | No upper bound; 0 means open to all registered agents             | --                  |
| `disclosure.strategyHash` | Must be non-zero (mandatory transparency, D-024)                  | `MissingDisclosure` |
| `disclosure.riskRating`   | Must be 1-10                                                      | `InvalidRiskRating` |
| `hookConfig`              | If `hookEnabled`, hook parameters must pass VaultHook validation  | `InvalidHookConfig` |

#### Complete Event Schema

```solidity
event VaultCreated(address indexed vault, address indexed hook, uint256 indexed creatorAgentId, address baseAsset, PoolId sharePool);
event VaultPaused(address indexed vault, address indexed pausedBy, string reason);
event VaultUnpaused(address indexed vault, address indexed unpausedBy);
event ImplementationUpgraded(address indexed oldImpl, address indexed newImpl, uint256 effectiveBlock);
event IdentityAdapterUpdated(address indexed oldAdapter, address indexed newAdapter, uint256 effectiveBlock);
event FactoryParameterChanged(bytes32 indexed paramId, uint256 oldValue, uint256 newValue, address changedBy);
```

**Vault Templates**: The factory offers pre-configured templates via convenience functions:

| Template         | CCA | Hook | Auto Pool | Rehypothecation | LDF      | Default Fee         |
| ---------------- | --- | ---- | --------- | --------------- | -------- | ------------------- |
| **Simple Yield** | No  | No   | Optional  | No              | No       | 1% mgmt, 10% perf   |
| **CCA Hunter**   | Yes | No   | Optional  | No              | No       | 2% mgmt, 20% perf   |
| **LP Manager**   | No  | Yes  | Yes       | Optional        | Optional | 1.5% mgmt, 15% perf |
| **Full Stack**   | Yes | Yes  | Yes       | Optional        | Optional | 2% mgmt, 25% perf   |
| **Meta-Vault**   | No  | No   | Optional  | No              | No       | 0.5% mgmt, 5% perf  |

All hook-enabled templates (LP Manager, Full Stack) have `autoPoolEnabled=true` by default. Non-hook templates can opt in to auto-pool creation. LDF is only meaningful for hook-enabled templates since it requires the VaultHook; non-hook templates ignore `ldfEnabled`.

**Meta-Vault Circularity Check**: Meta-Vault templates (composition depth limited to 2 per D-046) MUST enforce a circularity check in the factory. A vault CANNOT deposit into any vault that holds shares in it (direct or 1-hop). This prevents infinite loops in `totalAssets()` calculations and reentrancy attacks.

```solidity
/// @notice Revert if depositing into vault B would create a circular dependency
/// @dev Checks: (1) vault B does not hold shares in this vault, (2) no vault C
///      holds shares in both this vault and vault B
function _checkCircularity(address targetVault) internal view {
    require(!IAgentVaultCore(targetVault).hasSharesIn(address(this)), "CIRCULAR_DIRECT");
    // 1-hop check via factory registry
    address[] memory targetDepositors = factory.getDepositorsOf(targetVault);
    for (uint i = 0; i < targetDepositors.length; i++) {
        require(!IAgentVaultCore(targetDepositors[i]).hasSharesIn(address(this)), "CIRCULAR_1HOP");
    }
}
```

***

### 10.1a CrossVaultCoordinator (Factory-Level Defensive Rebalancing)

> **Research basis**: Devorsetz, Herlihy — "Defensive Rebalancing for AMMs" (arXiv, Jan 2026, Brown University). Proves that for any set of CFMMs with log-concave trading functions, the **arbitrage-free configuration is unique and equivalent to Pareto efficient**. The search for this configuration is a **convex optimization problem** with a guaranteed unique solution. (D-032)

When the factory deploys multiple vaults with overlapping token pairs (e.g., three vaults all holding ETH/USDC LP positions), external arbitrageurs can profit from price discrepancies between those vaults' pools. The CrossVaultCoordinator eliminates this leakage by periodically computing and applying the arbitrage-free cross-vault state.

**How it works**: The coordinator identifies factory vaults that share token pairs, solves a convex optimization problem to find the unique arbitrage-free price configuration, and submits the result on-chain. **Any agent or bot** can run the off-chain solver and submit the result — `submitCoordinatedRebalance()` is **permissionless** (see Section 10.1b). The coordinator verifies the proof on-chain and rewards the submitter proportional to the arbitrage leakage prevented. Participating vaults adjust their pool prices to the coordinated state, eliminating inter-vault arbitrage that would otherwise be extracted by third-party searchers. The optimization runs off-chain (the convex solver is too expensive for on-chain execution) with results verified on-chain.

**Active/Passive Framework**: Vaults opt into the coordination network (active) or remain independent (passive). The opt-in decision maps to the `defensiveRebalancingEnabled` flag in VaultConfig. Only vaults at Verified tier or above can participate, ensuring the coordination network is composed of trusted operators.

**Execution model**: The CrossVaultCoordinator implements the `IExecutable` interface (Section 10.1b). Execution is fully permissionless — no registration, bonding, or allowlisting is required to submit a solution. The `vault-executor` agent (see [09-agents-skills.md](/docs/gotts-vaults/vault/09-agents-skills.md)) is the canonical AI agent role for running the solver and submitting results, but any EOA or contract can call `submitCoordinatedRebalance()`. Executors are incentivized through the reward mechanism described below.

```solidity
interface ICrossVaultCoordinator is IExecutable {
    /// @notice Register a vault for cross-vault coordination
    /// @param vault Address of the factory-deployed vault
    /// @dev Vault must be factory-deployed (verified via isVault) and creator must be Verified+
    function registerVault(address vault) external;

    /// @notice Unregister a vault from coordination
    function unregisterVault(address vault) external;

    /// @notice Submit a coordinated rebalance solution computed off-chain.
    ///         Callable by anyone (permissionless). Executor receives a reward
    ///         proportional to the arbitrage leakage prevented.
    /// @param vaults Array of participating vault addresses
    /// @param targetPrices Array of target sqrtPriceX96 values for each vault's primary pool
    /// @param proof Proof that the solution is arbitrage-free (verifiable on-chain)
    /// @return executorReward Amount paid to msg.sender for successful submission
    /// @dev The solution must satisfy: for all pairs (i,j) of participating vaults with
    ///      overlapping tokens, no profitable circular arbitrage exists at the target prices.
    ///      Verification uses the Pareto efficiency ↔ arbitrage-free equivalence theorem.
    function submitCoordinatedRebalance(
        address[] calldata vaults,
        uint160[] calldata targetPrices,
        bytes calldata proof
    ) external returns (uint256 executorReward);

    /// @notice Get all vaults registered for coordination with overlapping pairs
    /// @param tokenA First token address
    /// @param tokenB Second token address
    /// @return vaults Array of coordinated vault addresses holding this pair
    function getCoordinatedVaults(address tokenA, address tokenB)
        external view returns (address[] memory);

    /// @notice Check if a cross-vault arbitrage opportunity exists
    /// @return leakageBps Estimated arbitrage leakage in basis points (0 = arbitrage-free)
    function estimateArbitrageLeakage(address tokenA, address tokenB)
        external view returns (uint256 leakageBps);
}
```

**Coordination Frequency**: The coordinator runs every N blocks (configurable, default 100 blocks on Base = \~200 seconds). The frequency balances gas cost against arbitrage leakage. At Base gas prices ($0.01-$0.10 per tx), running every 200 seconds costs \~$4-$40/day — recoverable if it prevents even modest arbitrage extraction.

**Executor Reward Mechanism**: When `submitCoordinatedRebalance()` is called, the coordinator measures `totalLeakagePrevented` by comparing the pre- and post-coordination arbitrage state. The executor (`msg.sender`) receives `executorRewardBps` of the leakage prevented, plus a gas refund. If no submission occurs within `coordinationFrequencyBlocks`, the reward escalates linearly from `baseRewardBps` to `maxRewardBps` over `escalationBlocks` (Dutch auction — see Section 10.1b). This guarantees eventual execution: even in low-activity periods, the escalating reward will eventually cross the profitability threshold.

**Gas funding**: Participating vaults contribute pro-rata to a gas pool on registration. The pool auto-replenishes from a small fraction of the leakage prevented. Executors never pay gas out of pocket for successful submissions.

**Configuration** (added to VaultConfig):

| Parameter                     | Default | Description                                                          |
| ----------------------------- | ------- | -------------------------------------------------------------------- |
| `defensiveRebalancingEnabled` | false   | Opt-in to cross-vault coordination network                           |
| `coordinationFrequencyBlocks` | 100     | Blocks between coordination rounds (Base)                            |
| `maxCoordinationSlippageBps`  | 10      | Maximum price adjustment per coordination round                      |
| `executorRewardBps`           | 500     | Executor reward as bps of leakage prevented (5%)                     |
| `maxExecutorRewardBps`        | 2000    | Maximum reward after full escalation (20%)                           |
| `escalationBlocks`            | 200     | Blocks over which reward escalates from base to max (\~400s on Base) |

**Events**:

```solidity
event VaultRegisteredForCoordination(address indexed vault, uint256 indexed agentId);
event CoordinatedRebalanceExecuted(
    address[] vaults, uint256 totalLeakagePrevented, uint256 blockNumber
);
event ExecutorRewarded(
    address indexed executor, uint256 reward, uint256 leakagePrevented, uint256 gasRefunded
);
```

***

### 10.1b Permissionless Executor Framework (D-061)

> **v1 Executor Surface**: This section specifies the **only** execution infrastructure that ships with core v1. The full bonded ExecutionMarket (D-057) is deferred to Track D. All v1 contracts, tools, and UX MUST use the `IExecutable` interface defined here. References to `register_executor`, bonded keepers, or slashing in other documents are Track D / deferred unless explicitly marked otherwise.

Multiple vault operations require off-chain computation followed by on-chain submission: CrossVaultCoordinator solutions (Section 10.1a), LVR-theta fee floor calibrations (Section 10.5), behavioral regime classifications (Section 10.14), and proxy transaction execution (Section 10.19). In all cases, the PRD must specify **who submits, how gas is paid, and what incentivizes timely execution**.

The Permissionless Executor Framework is the unified answer. It is a lightweight v1 pattern — forward-compatible with the full ExecutionMarket (D-057) when that ships in Track D — that makes every off-chain-compute/on-chain-submit operation **permissionless and self-funding**.

**Core principle**: Every operation that needs external submission is callable by anyone. The operation itself creates measurable value (arbitrage leakage prevented, fees correctly calibrated, reputation accurately scored), and the executor captures a share of that value.

```
Off-chain computation ──► submitResult() on-chain
                           │
                           ├── Verify proof / result on-chain
                           ├── Measure benefit (leakage prevented, fees saved, etc.)
                           ├── Pay executor: min(gasRefund + benefitShareBps × benefit, maxRewardCap)
                           └── If overdue: reward escalates linearly (Dutch auction for execution)
```

**Interface**: All executor-dependent contracts implement `IExecutable`:

```solidity
interface IExecutable {
    /// @notice Execute a pending job. Callable by anyone (permissionless).
    /// @param jobData Encoded job parameters (contract-specific)
    /// @return reward Amount paid to msg.sender for successful execution
    function executeJob(bytes calldata jobData) external returns (uint256 reward);

    /// @notice Check whether a job is ready for execution and its estimated reward.
    /// @return ready True if the job condition is satisfied and execution will succeed
    /// @return estimatedReward Estimated payout to the executor at current escalation level
    function getJobCondition() external view returns (bool ready, uint256 estimatedReward);

    /// @notice Get current reward escalation state.
    /// @return currentRewardBps Current reward as bps of measured benefit (increases over time)
    /// @return blocksUntilMax Blocks remaining until reward reaches maxRewardBps
    function getRewardEscalation() external view returns (uint256 currentRewardBps, uint256 blocksUntilMax);
}
```

**Reward Model**:

| Component                | Description                                                                                                                               | Default                                                                |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Gas refund               | Executor is refunded actual gas cost from the reward pool                                                                                 | Included in all rewards                                                |
| Benefit share            | Percentage of the measurable on-chain benefit (leakage prevented, fee savings, etc.)                                                      | `baseRewardBps` = 300 (3%)                                             |
| Dutch auction escalation | If no executor submits within the target window, reward increases linearly from `baseRewardBps` to `maxRewardBps` over `escalationBlocks` | `maxRewardBps` = 2000 (20%), `escalationBlocks` = 200 (\~400s on Base) |
| Reward cap               | Hard cap prevents pathological overpayment                                                                                                | `maxRewardCap` = per-contract configurable                             |

**Escalation curve**: `currentReward = baseReward + (maxReward - baseReward) × min(blocksOverdue / escalationBlocks, 1)`. This is a Dutch auction for execution priority — the longer the job goes unexecuted, the more profitable it becomes, guaranteeing eventual execution. Early executors earn less but face no competition; late executors earn more but risk being front-run.

**Gas funding**: Each contract that implements `IExecutable` maintains a small gas pool funded by its beneficiaries. For CrossVaultCoordinator, participating vaults contribute pro-rata. For LVR-theta calibration, the vault's fee accumulator funds it. For behavioral classification, the reputation engine's fee allocation covers it. Gas pools are auto-replenished from ongoing protocol revenue — no manual top-up required.

**Key properties**:

* **No registration required** — any address can call `executeJob()`. No bonding, no staking, no allowlisting. This maximizes executor diversity and eliminates single points of failure.
* **Self-funding** — rewards come from the measurable benefit created by the operation, not from a treasury subsidy or token inflation. Operations that create no measurable benefit have fixed-tip rewards funded by the beneficiary contract.
* **Verifiable on-chain** — every job includes proof verification. The executor cannot submit an incorrect result and collect a reward. Invalid proofs revert.
* **Liveness guarantee** — the Dutch auction escalation ensures that even if current gas prices make execution unprofitable at `baseRewardBps`, the escalating reward will eventually cross the profitability threshold.
* **Forward-compatible with ExecutionMarket (D-057)** — when the full ExecutionMarket ships in Track D, the `IExecutable` interface is unchanged. ExecutionMarket wraps it with a job registry, bonding, slashing, and priority queues. Existing permissionless executors can bond and upgrade to bonded status for higher-reward jobs.

**UX for agents**: The `vault-executor` agent (see [09-agents-skills.md](/docs/gotts-vaults/vault/09-agents-skills.md)) scans all `IExecutable` contracts via `getJobCondition()`, estimates profitability via `getRewardEscalation()`, and calls `executeJob()` on profitable opportunities. This is a **non-capital-intensive yield path** — agents earn by providing computation and infrastructure, not by deploying capital. Successful execution earns reputation milestones (D-007), creating a progression path from executor to higher-tier roles.

**Events** (emitted by all `IExecutable` implementations):

```solidity
event JobExecuted(
    address indexed executor,
    bytes32 indexed jobType,
    uint256 reward,
    uint256 measuredBenefit,
    uint256 gasRefunded
);
event RewardEscalationTriggered(bytes32 indexed jobType, uint256 newRewardBps, uint256 blocksOverdue);
```

**Monitoring thresholds** (integrated into [10-safety.md](/docs/gotts-vaults/vault/10-safety.md) alerting):

| Metric                       | Warning                       | Critical                      | Notes                                                 |
| ---------------------------- | ----------------------------- | ----------------------------- | ----------------------------------------------------- |
| Job execution latency (p95)  | >3x target frequency          | >5x target frequency          | Signals insufficient executor competition             |
| Executor concentration (HHI) | Top-3 executors >60% of jobs  | Top-3 executors >75% of jobs  | Triggers diversity incentive (higher escalation rate) |
| Reward escalation frequency  | >30% of jobs reach escalation | >50% of jobs reach escalation | Base reward may be too low; recalibrate               |
| Gas pool balance             | <10 job executions remaining  | <3 job executions remaining   | Auto-replenishment should prevent this                |

***

### 10.2 AgentVaultCore.sol

ERC-4626 vault extended with ERC-8004 agent identity gating, reputation-tiered access, circuit breaker protection, and optional CCA participation.

**Inherits**: OpenZeppelin `ERC4626`, `ReentrancyGuard`, `Ownable`

**Critical: Inflation Attack Mitigation (D-017)**

Every factory-deployed vault MUST use OpenZeppelin v5.x's `_decimalsOffset()` set to 3-6 to prevent ERC-4626 inflation attacks. Venus Protocol lost \~86 WETH on ZKsync (Feb 2025) and the Resupply exploit demonstrated that empty vaults deployed by permissionless factories are prime targets. Since every factory-deployed vault starts empty, this is an existential risk.

The vault combines two defenses:

1. **Virtual shares offset**: `_decimalsOffset()` returns 6 (configurable 3-6). This introduces virtual shares that make inflation attacks "orders of magnitude more expensive than profitable" (OpenZeppelin).
2. **Internal asset accounting**: `totalAssets()` is tracked via internal bookkeeping (deposits, withdrawals, strategy returns), NOT via `balanceOf(address(this))`. This makes donation attacks irrelevant to share pricing since donated tokens do not affect the internal accounting.

```solidity
function _decimalsOffset() internal pure override returns (uint8) {
    return 6; // Virtual shares offset -- prevents inflation attacks
}
```

**Linear Profit Unlock (D-018)**

Profits from AI strategy returns are accumulated in a buffer and linearly unlocked over `profitMaxUnlockTime` (default 6 hours). This prevents share price manipulation from flash donations or lumpy AI strategy returns -- particularly important for AI-managed vaults where strategy returns may arrive in large, irregular bursts.

```solidity
uint256 public profitMaxUnlockTime = 6 hours;     // Yearn V3 pattern
uint256 public lockedProfit;                        // Unreleased profit buffer
uint256 public lastProfitUpdateTime;                // Last profit report timestamp
uint256 public maxSharePriceIncreaseBps = 500;      // 5% max increase per unlock period (Morpho V2 maxRate)

/// @notice Report profit from strategy execution. Profit enters the linear unlock buffer.
function reportProfit(uint256 profit) external onlyManager {
    lockedProfit += profit;
    lastProfitUpdateTime = block.timestamp;
}

/// @notice Unlocked profit available for share price calculation
function unlockedProfit() public view returns (uint256) {
    uint256 elapsed = block.timestamp - lastProfitUpdateTime;
    if (elapsed >= profitMaxUnlockTime) return 0; // Fully unlocked
    return lockedProfit * (profitMaxUnlockTime - elapsed) / profitMaxUnlockTime;
}
```

`totalAssets()` returns: `internalBalance - unlockedProfit()` (deducting not-yet-unlocked profit from the reported total). This smooths share price increases and prevents sandwich attacks around profit reporting events.

#### v1 `totalAssets()` (Normative)

For v1 (no CCA, no external adapters), the formula is:

```
totalAssets = idleCapital + sum(lpPositions[k].totalValue) - accruedFees - unlockedProfit()
```

Two position components instead of the full five-component formula. CCA bid valuation, claimed token mark-to-market, and external yield positions are excluded until those modules ship.

**Oracle requirements**:

* LP position valuation uses Uniswap V4 pool `slot0` price with TWAP validation (10-30 min window per D-067)
* If TWAP diverges from spot by > `oracleMaxDivergenceBps` (default 200 bps = 2%), position is valued at the more conservative (lower) price
* Oracle staleness: if the last observation is older than `oracleMaxStaleness` (default 30 min), the position is valued at the last known value and a `RiskWarning` event is emitted

**Edge cases**:

| Condition                                                      | Behavior                                                                                                           |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| LP position cannot be valued (pool paused, oracle unavailable) | Mark at last known value; emit `RiskWarning(vaultAddress, "POSITION_UNVALUABLE", ...)`                             |
| Oracle stale                                                   | Widen NAV bounds per D-062; emit `RiskWarning`                                                                     |
| All oracles fail                                               | Fall back to `idleCapital - accruedFees` only (exclude all LP positions from NAV)                                  |
| Rounding                                                       | Round DOWN for `totalAssets()` (conservative for share pricing); use `_decimalsOffset()` to absorb rounding errors |

**State Variables**:

```solidity
IIdentityRegistry public immutable identityRegistry;
IReputationRegistry public immutable reputationRegistry;
IFeeModule public feeModule;
ICCABidAdapter public ccaBidAdapter;  // Optional, set if CCA enabled
address[] public trustedReviewers;

// Agent state
mapping(uint256 => uint256) public agentShares;        // agentId -> shares
mapping(uint256 => uint256) public agentDepositTotal;   // agentId -> cumulative deposits

// Creator
uint256 public immutable creatorAgentId;

// Tier configuration
uint256 public minReputation;
mapping(uint8 => TierLimits) public tierConfig;

// Circuit breaker
uint256 public highWaterMark;
uint256 public lastHWMTimestamp;
uint16 public maxDrawdownBps = 1000;                    // 10%
bool public paused;

// Vault hook integration
address public vaultHook;
```

**Reputation Tiers** (5 tiers):

| Tier       | Score Range                   | Max Deposit (USD) | Max Rebalances/Day | Max Daily Aggregate |
| ---------- | ----------------------------- | ----------------- | ------------------ | ------------------- |
| Unverified | 0 (registered, no reputation) | $1,000            | 2                  | $5,000              |
| Basic      | 10+                           | $10,000           | 5                  | $50,000             |
| Verified   | 50+                           | $50,000           | 10                 | $250,000            |
| Trusted    | 100+                          | $100,000          | 25                 | $1,000,000          |
| Sovereign  | 500+                          | $500,000          | 50                 | $10,000,000         |

> **Sovereign tier daily cap**: Even Sovereign agents MUST have a configurable daily aggregate cap (default $10M/day, adjustable by vault creator). Unlimited caps are prohibited. Rationale: if a Sovereign agent is compromised, the lack of rate limits would allow an attacker to drain the vault at maximum speed without triggering per-operation limits that protect lower tiers. The time-delayed proxy (Layer 4) is the intended protection, but until the proxy module is fully shipped and hardened, an aggregate cap provides a safety backstop.

**Core Functions**:

```solidity
/// @notice Deposit with agent identity. Caller must control the agent's wallet.
function deposit(uint256 agentId, uint256 assets)
    external nonReentrant whenNotPaused
    onlyAgent(agentId) hasReputation(agentId) withinTierLimit(agentId, assets)
    returns (uint256 shares);

/// @notice Withdraw with agent identity.
function withdraw(uint256 agentId, uint256 assets)
    external nonReentrant onlyAgent(agentId)
    returns (uint256 shares);

// === Strategy Execution (creator/manager only) ===

/// @notice Submit a bid into a CCA auction on behalf of the vault
function submitCCABid(address auction, uint256 amount, uint256 maxPrice)
    external onlyManager;

/// @notice Exit a CCA bid (completed or outbid)
function exitCCABid(address auction, uint256 bidId)
    external onlyManager;

/// @notice Claim tokens after CCA graduation
function claimCCATokens(address auction, uint256 bidId)
    external onlyManager;

/// @notice Deploy liquidity to a V4 pool
function deployLiquidity(address pool, DeployParams calldata params)
    external onlyManager;

/// @notice Rebalance existing LP positions
/// @param params Rebalance parameters including target allocations
/// @dev When params.useTWAMM is true, submits a TWAMM order instead of immediate swap,
///      splitting the operation over params.twammDuration seconds to minimize price impact.
///      Recommended for rebalances exceeding 1% of pool liquidity.
function rebalance(RebalanceParams calldata params)
    external nonReentrant whenNotPaused onlyManager;

// === Permit2 Agent Delegation ===

/// @notice Grant bounded Permit2 sub-approval to the managing agent
/// @param agent Agent wallet address
/// @param amount Maximum token amount the agent can move per rebalance
/// @param expiration Unix timestamp when the delegation expires
/// @dev Enables gasless agent operations via SignatureTransfer. The agent constructs
///      Universal Router command batches (V4_SWAP + V4_POSITION_MODIFY + SETTLE_ALL)
///      and signs with their ERC-8004 identity. Flash accounting ensures only net
///      token deltas are settled — a 5-step rebalance requires only 2 actual transfers.
function grantAgentPermit2(address agent, uint256 amount, uint48 expiration)
    external onlyCreator;

// === Circuit Breaker ===

function pause() external; // Triggered automatically or by creator
function unpause() external; // Creator only, after cooldown
```

#### ERC-4626 Compatibility Guarantees (Normative)

External protocols (Morpho, Pendle, aggregators like vaults.fyi and DefiLlama) depend on accurate ERC-4626 interface behavior. The following guarantees are normative for composability.

| Function                  | Behavior                                                                                                                                                                                                    | Accuracy                         |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `maxDeposit(receiver)`    | Returns the maximum deposit amount accounting for: (1) agent's reputation tier cap minus current deposits, (2) vault idle capital limits if configured, (3) 0 if vault is paused or agent is not registered | Exact                            |
| `maxWithdraw(owner)`      | Returns only **instantly-available liquidity** (idle capital proportional to owner's share). Does NOT include capital locked in LP positions or adapters. For full withdrawal, use ERC-7540 async redeem.   | Conservative (may underestimate) |
| `maxRedeem(owner)`        | Returns shares redeemable for instantly-available assets. Same constraint as `maxWithdraw`.                                                                                                                 | Conservative                     |
| `previewDeposit(assets)`  | Returns expected shares, accounting for entry fees if configured (D-069). Must be accurate within `_decimalsOffset` rounding tolerance.                                                                     | Within 1 share of actual         |
| `previewMint(shares)`     | Returns required assets, accounting for entry fees.                                                                                                                                                         | Within 1 wei of actual           |
| `previewWithdraw(assets)` | Returns shares to burn. Does NOT account for withdrawal queue delays.                                                                                                                                       | Exact for instant withdrawals    |
| `previewRedeem(shares)`   | Returns assets received.                                                                                                                                                                                    | Exact for instant withdrawals    |

**Non-standard revert conditions:**

| Condition                   | Reverts On                                                   | Error                    |
| --------------------------- | ------------------------------------------------------------ | ------------------------ |
| Unregistered agent          | `deposit`, `mint`, `withdraw`, `redeem`                      | `AgentNotRegistered`     |
| Frozen credential           | `deposit`, `mint`                                            | `CredentialFrozen`       |
| Tier cap exceeded           | `deposit`, `mint`                                            | `TierCapExceeded`        |
| Vault paused                | All write operations                                         | `VaultPaused`            |
| Circuit breaker active      | `withdraw`, `redeem` (dampened, not fully blocked per D-043) | `CircuitBreakerDampened` |
| Oracle stale + NAV disabled | `deposit`, `mint` (may reject to protect depositors)         | `OracleStale`            |

**Entry fee handling (D-069)**: When entry fees are configured, `previewDeposit()` returns fewer shares than a zero-fee vault. This is ERC-4626 compliant -- the spec requires preview functions to account for fees. Aggregators call `previewDeposit(assets)` to show the actual shares received.

***

**Share Price Calculation** (`totalAssets()`):

```
totalAssets = idleCapital
            + sum(activeCCABids[i].currentValue)    // At clearing price or bid price
            + sum(claimedTokens[j].markToMarket)     // TWAP from V4 pool
            + sum(lpPositions[k].totalValue)          // Token0 + Token1 at current tick
            + sum(yieldPositions[l].redeemable)       // In lending protocols
            - accruedFees
```

For CCA positions: active bids are valued at bid price (conservative), graduated-but-unclaimed positions at clearing price, and claimed tokens at V4 pool TWAP with a freshness discount (5-10% to mitigate manipulation).

**Oracle Architecture** (extends D-021):

> **Research basis**: Ormer — "Manipulation-resistant and Gas-efficient Blockchain Pricing Oracle" (arXiv:2410.07893, Oct 2024 revised Jun 2025); SecPLF — "Secure Protocols for Loanable Funds against Oracle Manipulation" (arXiv:2401.08520, Jan 2024).

NAV calculations use **median-based pricing** rather than vulnerable TWAP for high-frequency L2 operations. Median pricing provides inherent outlier robustness and lower latency than TWAP. The oracle pipeline:

1. **Primary**: RedStone push oracle (secured $9B+, sub-second updates on Base)
2. **Secondary**: Uniswap V4 pool median (median of last N price observations, replacing simple TWAP)
3. **Tertiary**: Chainlink price feed (for blue-chip pairs)
4. **Divergence guard**: Auto-pause if primary vs secondary diverge >2%

**Per-asset price state tracking** (SecPLF pattern): The vault only queries an external oracle if the internally tracked price deviates beyond a defined bound from the last recorded value. This negates profitable oracle manipulation attacks — an attacker must first move the real market price before the oracle is consulted, making manipulation economically unprofitable. Implementation: `_shouldRefreshOracle(asset, lastPrice, bound) returns (bool)`.

**ERC-7540 Async Deposit/Redeem Interface** (opt-in for CCA and auction-based operations):

The vault optionally implements ERC-7540 (finalized June 2024, used by Centrifuge at \~$250M TVL) for operations that require asynchronous settlement — such as CCA participation where deposits are locked during auction periods. The Pending → Claimable → Claimed lifecycle maps directly to auction submission → settlement → share distribution.

**Sync vs Async Flow Routing**: The factory sets an `asyncRequired` flag at deployment time based on the vault template. This flag determines whether standard ERC-4626 synchronous `deposit()`/`redeem()` are available or must be routed through ERC-7540's request lifecycle.

| Template         | Async Required | Rationale                                                                                          |
| ---------------- | -------------- | -------------------------------------------------------------------------------------------------- |
| **Simple Yield** | No             | All positions are instantly liquid (idle capital + lending venues with atomic withdrawal)          |
| **CCA Hunter**   | Yes            | CCA bids are non-withdrawable while in range; capital locked during auction periods                |
| **LP Manager**   | No (default)   | Concentrated liquidity positions are atomically removable; async opt-in if using illiquid adapters |
| **Full Stack**   | Yes            | CCA + hook positions may include illiquid components                                               |
| **Meta-Vault**   | Inherits       | Async if any underlying vault requires async; sync otherwise                                       |

When `asyncRequired == true`, synchronous `redeem()` and `withdraw()` MUST revert with `SyncRedemptionDisabled()`. Depositors must use `requestRedeem()` → wait for settlement → `claimRedeem()`. Synchronous `deposit()` remains available for all templates (capital acceptance is always instant; it is the *exit* that may be async).

```
ERC-7540 Async Redemption State Machine:

  requestRedeem(shares)        Vault settles position       claimRedeem(requestId)
        │                           │                              │
        ▼                           ▼                              ▼
  ┌──────────┐              ┌─────────────┐                ┌───────────┐
  │ PENDING  │ ──────────►  │ CLAIMABLE   │ ────────────►  │ CLAIMED   │
  │          │  settlement  │             │   user claims   │           │
  │ shares   │  complete    │ assets      │   assets        │ (final)   │
  │ locked   │              │ available   │                 │           │
  └──────────┘              └─────────────┘                └───────────┘
        │
        ▼ (optional, ERC-7887 extension)
  ┌──────────┐
  │ CANCELLED│  ← cancelRedeem(requestId) while still PENDING
  │ shares   │    Shares returned to owner. Only available when
  │ returned │    position is not yet committed (e.g., CCA bid not yet placed).
  └──────────┘
```

The `requestId` uniquely identifies each request and can be used to query status via `pendingRedeemRequest()` and `claimableRedeemRequest()` view functions.

```solidity
// === ERC-7540 Async Operations (opt-in) ===

/// @notice Request a deposit that settles after auction/CCA clearing
/// @param assets Amount of base asset to lock
/// @param controller Address authorized to claim shares
/// @param owner Address that owns the request (for cancellation)
/// @return requestId Unique ID representing this request (maps to auction epoch)
function requestDeposit(uint256 assets, address controller, address owner)
    external nonReentrant whenNotPaused onlyAgent(agentId)
    returns (uint256 requestId);

/// @notice Request a redemption that settles after position unwinding
/// @param shares Number of shares to redeem
/// @param controller Address authorized to claim assets
/// @param owner Address that owns the request
/// @return requestId Unique ID representing this request
function requestRedeem(uint256 shares, address controller, address owner)
    external nonReentrant onlyAgent(agentId)
    returns (uint256 requestId);

/// @notice Claim settled shares from a completed deposit request
function claimDeposit(uint256 requestId, address receiver) external returns (uint256 shares);

/// @notice Claim settled assets from a completed redeem request
function claimRedeem(uint256 requestId, address receiver) external returns (uint256 assets);
```

The `requestId` parameter can represent auction epochs, enabling batched settlement across multiple depositors participating in the same CCA round.

**ERC-6909 Internal Share Accounting**:

The vault factory uses a single ERC-6909 multi-token contract for all vault shares, where each vault gets a unique `tokenId`. This eliminates deploying separate ERC-20 contracts per vault, making vault creation **dramatically cheaper**. The recommended hybrid approach: use ERC-6909 internally for gas efficiency, but allow users to "claim" as standard ERC-20 when needed for external composability (collateral on Morpho, listing on Pendle). Uniswap V4's PoolManager uses this exact pattern for claim tokens.

**V4 Flash Accounting for Compound Deposits**:

Uniswap V4's singleton architecture enables revolutionary deposit flows for hook-integrated vaults. Within a single `unlock` callback, the vault can receive assets, swap a portion via a V4 pool, add liquidity, and auto-compound — with only **net token movement** requiring actual ERC-20 transfers. Intermediate operations use transient storage (**\~100 gas** vs \~2,000+ gas for regular `SSTORE`), yielding up to **20x cheaper intermediate accounting**. Example flow: agent deposits 1000 USDC → hook unlocks PoolManager → swaps 500 USDC → ETH (delta tracked) → adds 500 USDC + ETH as LP (deltas netted) → only 1000 USDC actual transfer → vault shares minted. Multi-hop swaps save **30-50% gas** vs V3.

**Events**:

```solidity
event AgentDeposit(uint256 indexed agentId, uint256 assets, uint256 shares);
event AgentWithdraw(uint256 indexed agentId, uint256 assets, uint256 shares);
event DepositRequested(uint256 indexed requestId, uint256 indexed agentId, uint256 assets);
event RedeemRequested(uint256 indexed requestId, uint256 indexed agentId, uint256 shares);
event DepositClaimed(uint256 indexed requestId, uint256 shares);
event RedeemClaimed(uint256 indexed requestId, uint256 assets);
event Rebalance(uint256 indexed agentId, bytes32 strategyHash, uint256 newTotalAssets);
event ManagerAssigned(uint256 indexed agentId, address indexed manager);
event CircuitBreakerTriggered(uint256 navBps, uint256 highWaterMark, uint256 currentNav);

// D-066 normative observability events (MUST be emitted by every vault)
event NAVUpdated(uint256 navPerShare, address oracleSource, uint256 timestamp);
event RiskStateChanged(bytes32 flagSet, bytes32 flagCleared);
event JobExecuted(bytes32 jobType, address executor, uint256 gasUsed, uint256 reward);
event HookStateChanged(bool enabled, string reason);
```

> **D-066 compliance**: All core contracts MUST emit the standardized events defined in [10-safety.md](/docs/gotts-vaults/vault/10-safety.md) Section "Canonical Event Schema (D-066)". Dashboards MUST be derivable from on-chain events alone.

**Tier-Differentiated Loss Socialization (ADL Trilemma)**:

> **Research basis**: Chitra, "Autodeleveraging: Impossibilities and Optimization" (arXiv:2512.01112, Nov 2025). Proves the ADL Trilemma: no loss socialization policy can simultaneously satisfy solvency, revenue, and fairness. Validated on the Hyperliquid Oct 2025 dataset ($2.1B positions closed in 12 min).

When a vault incurs losses from strategy failure, exploit, or market dislocation, the ADL Trilemma applies. The vault implements a tier-modulated loss socialization policy:

| Depositor Tier      | Priority       | Loss Sharing Policy                                                     | Rationale                                           |
| ------------------- | -------------- | ----------------------------------------------------------------------- | --------------------------------------------------- |
| Unverified / Basic  | Solvency-first | Capital protected up to tier cap; losses absorbed by higher tiers first | Protects new/small participants — builds trust      |
| Verified            | Balanced       | Proportional loss sharing after lower tiers are made whole              | Fair middle ground                                  |
| Trusted / Sovereign | Fairness-first | Full proportional sharing; absorb residual losses first                 | Sophisticated agents accept risk for higher returns |

The loss socialization waterfall: (1) Protocol insurance pool absorbs first loss up to `maxLossShareBps`. (2) Remaining loss is distributed tier-down: Sovereign → Trusted → Verified → Basic. (3) If total loss exceeds all buffers, pro-rata across all depositors (emergency only).

```solidity
/// @notice Distribute losses across depositors using tier-weighted socialization
/// @param totalLoss Total loss in base asset units
/// @dev Implements the "water filling" optimization strategy from the ADL Trilemma paper.
///      Lower tiers are protected first; higher tiers absorb residual losses proportionally.
function socializeLoss(uint256 totalLoss) external onlySentinel;
```

***

### 10.3 VaultHook.sol

Combined agent-gated access, dynamic volatility-based fees, and ERC-4626 yield wrapping in a single V4 hook. Follows patterns from Arrakis Pro (first whitelisted dynamic fee hook) and the ERC4626Hook.sol PR (#452) to v4-periphery.

> **Security**: This contract MUST satisfy all five V4 hook security requirements defined in [10-safety.md](/docs/gotts-vaults/vault/10-safety.md). In particular: inherit from `BaseHook`, apply `onlyPoolManager` on every callback, validate pool keys, use property-based fuzzing + formal verification, and pass a V4-specialized audit before mainnet deployment. The Cork Protocol ($11M) and Bunni v2 ($8.4M) exploits demonstrate the consequences of incomplete hook security.

**Inherits**: `BaseHook` (v4-periphery)

**Hook Permissions**: `beforeSwap`, `afterSwap`, `beforeAddLiquidity`, `afterAddLiquidity`, `beforeRemoveLiquidity`

**Three Integrated Functions**:

1. **Agent-Gated Access**: `beforeSwap` verifies the swapper holds an ERC-8004 identity with sufficient reputation. Creates agent-exclusive pools where MEV bots without ERC-8004 identities are excluded. Identity verification uses a **local cache bitmap** to avoid per-swap external calls to the ERC-8004 registry (see Identity Cache below).
2. **Dynamic Fees**: `beforeSwap` calculates fees based on recent volatility. Fee ranges from `feeFloor` to `feeCeiling` based on a rolling window of tick observations.
3. **ERC-4626 Yield Wrapping**: `afterAddLiquidity` deposits idle pool assets into a yield-bearing vault. `beforeRemoveLiquidity` retrieves assets. LP positions earn trading fees + underlying yield simultaneously.

```solidity
contract VaultHook is BaseHook {
    IIdentityRegistry public immutable identityRegistry;
    IReputationRegistry public immutable reputationRegistry;
    uint256 public immutable minReputationForSwap;

    // Dynamic fee state
    uint256 public lastVolatilityUpdate;
    uint256 public currentFeeBps;
    uint16 public feeFloorBps;    // default: 1 (0.01%)
    uint16 public feeCeilingBps;  // default: 100 (1.00%)

    // ERC-4626 yield wrapping
    IERC4626 public yieldVault;

    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeSwap: true,           // Identity gate + dynamic fee
            afterSwap: true,            // Record tick, update metrics
            beforeAddLiquidity: true,   // Identity gate
            afterAddLiquidity: true,    // Deposit to yield vault
            beforeRemoveLiquidity: true // Withdraw from yield vault
        });
    }

    function _beforeSwap(...) internal override returns (bytes4, BeforeSwapDelta, uint24) {
        require(authorized[sender], "VaultHook: not authorized");
        uint24 fee = _calculateFee(key.toId());
        return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee);
    }

    function _afterAddLiquidity(...) internal override returns (bytes4, BalanceDelta) {
        // Deposit unused pool assets into yield vault
        uint256 idle = _getIdleBalance(key);
        if (idle > 0 && address(yieldVault) != address(0)) {
            yieldVault.deposit(idle, address(this));
        }
        return (BaseHook.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
    }
}
```

**Identity Cache for Gas-Efficient Agent Verification**: The `beforeSwap` hook must verify agent identity on every swap, but making an external call to the ERC-8004 Identity Registry on every swap adds \~2,600 gas (cold SLOAD + external call) and creates a potential DoS vector if the registry is slow or congested. The VaultHook uses a **local cache bitmap** to eliminate per-swap registry lookups:

```solidity
// Local identity cache — avoids per-swap external calls to ERC-8004 registry
mapping(address => bool) public verifiedAgentCache;
uint256 public cacheLastRefreshed;
uint256 public constant CACHE_REFRESH_INTERVAL = 300; // blocks (~10 min on Base)

function _beforeSwap(...) internal override returns (bytes4, BeforeSwapDelta, uint24) {
    // Hot path: check local cache (single SLOAD, ~200 gas)
    if (verifiedAgentCache[sender]) {
        uint24 fee = _calculateFee(key.toId());
        return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee);
    }
    // Cold path: cache miss → live registry lookup + cache update
    require(_verifyAndCacheAgent(sender), "VaultHook: not authorized");
    uint24 fee = _calculateFee(key.toId());
    return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee);
}

/// @notice Batch refresh the cache from the ERC-8004 registry.
///         Callable by anyone (permissionless, gas-refundable via D-061).
function refreshAgentCache(address[] calldata agents) external;

/// @notice Invalidate a specific agent from cache (e.g., on ERC-8004 revocation event).
///         Callable by the Sentinel role or by the registry via callback.
function invalidateCacheEntry(address agent) external;
```

The cache reduces per-swap gas from \~2,800 (external call) to \~200 (single warm SLOAD). Cache entries are invalidated reactively via ERC-8004 `IdentityRevoked` event listeners or proactively via periodic `refreshAgentCache()` calls. The `CACHE_REFRESH_INTERVAL` ensures stale entries are refreshed even if event listeners fail.

**Dynamic Fee Calculation**: Delegated to the `DynamicFeeEngine` (Section 10.3a). The engine implements a three-regime threshold model based on the convergent findings of three independent research groups (Campbell-Bergault-Milionis-Nutz 2025, Baggiani-Herdegen-Sánchez-Betancourt 2025, Milionis-Moallemi-Roughgarden 2025).

**PA-AMM Activeness Control**: The hook integrates an activeness parameter `lambda` (Section 10.3b) from the Partially Active AMM framework (arXiv:2602.09887) that controls the tradeoff between active reserves (trading, earning fees, exposed to LVR) and passive reserves (idle, no LVR, but tracking error).

**Deployment**: Via HookMiner + CREATE2 with flags encoding the 5 required permission bits.

***

### 10.3a DynamicFeeEngine (Research-Backed)

> **Research basis**: Five independent results converge on a unified dynamic fee model. Three groups establish threshold-type regimes as approximately optimal; a fourth derives a fee floor from LVR option theory; a fifth provides a zero-cost on-chain volatility oracle from fee data alone.
>
> * Campbell, Bergault, Milionis, Nutz — "Optimal Fees for Liquidity Provision in AMMs" (arXiv:2508.08152, Aug 2025)
> * Baggiani, Herdegen, Sánchez-Betancourt — "Optimal Dynamic Fees in AMMs" (arXiv:2506.02869, Jun 2025, Oxford)
> * Milionis, Moallemi, Roughgarden — "AMM and Arbitrage Profits in the Presence of Fees" (arXiv:2305.14604, rev. Jul 2025; FC 2024)
> * Singh et al. — "Modeling LVR via Continuous-Installment Options" (arXiv:2508.02971, Aug 2025; AFT 2025) — LVR-theta fee floor (D-029)
> * Bichuch, Feinstein — "Implied Volatility of AMM Fees" (Sep 2025) — Fee-implied volatility oracle (D-030)

The DynamicFeeEngine replaces the simple rolling-window volatility multiplier with a three-regime threshold model that is both robust and approximately optimal under normal conditions. Additionally, the engine enforces an **LVR-theta fee floor** that guarantees LPs are compensated for adverse selection cost regardless of the active fee regime, and can derive implied volatility directly from on-chain fee data as a zero-cost oracle source.

**Three Fee Regimes**:

| Regime       | Condition                      | Fee Formula                                | Behavior                                          |
| ------------ | ------------------------------ | ------------------------------------------ | ------------------------------------------------- |
| **Stable**   | σ\_realized < σ\_low           | `f = f_base` (e.g. 5 bps)                  | Low, constant fee — attracts noise traders        |
| **Elevated** | σ\_low ≤ σ\_realized < σ\_high | `f = f_base + k * \|inventory_imbalance\|` | Inventory-linear fee — deters informed order flow |
| **Spike**    | σ\_realized ≥ σ\_high          | `f = f_max` (e.g. 100 bps)                 | Maximum fee — deters arbitrageurs during high vol |

The inventory-linear regime (Baggiani et al. closed-form approximation) distinguishes two sub-modes: one deterring arbitrageurs and another attracting noise traders. AI agents can detect the current market condition in real time using oracle feeds.

**L2 Block-Time Calibration** (Milionis et al. square-root decay model + Nezlobin & Tassy constant-block-time correction, D-037):

On Base's 2-second blocks vs Ethereum's 12-second blocks, per-block arbitrage is lower but frequency is higher. The expected LVR scales as:

```
expectedLVR ≈ σ² × √(blockTime) × blocksPerYear
```

The square-root decay factor changes by \~2.45x between chains. On Base, the net effect is approximately **5x lower total LVR cost** than mainnet, supporting more aggressive LP strategies. See Section 10.18 for the full L2 calibration table.

**Constant-Block-Time Correction** (Nezlobin & Tassy, arXiv:2505.05113, May 2025): The sqrt formula above assumes Poisson (PoW) block-time distributions. Nezlobin & Tassy prove that **constant block-time uniquely minimizes asymptotic LVR** among all distributions with the same mean. For Base's deterministic 2-second blocks, their Corollary 3.1 closed-form constant-block-time formula should be used instead of the Poisson-derived sqrt, yielding fees **5-15% lower** than naive sqrt(2/12) scaling. This correction, combined with Gogol et al.'s empirical finding that price disparities persist for 10-20 blocks on L2s (arXiv:2406.02172), means per-block LVR metrics overestimate actual L2 arbitrage by \~5x.

**Sigma Threshold Calibration** (Loesch, arXiv:2502.04097, Feb 2025): The three temporal regimes of the IL/LVR relationship map directly to the stable/elevated/spike fee structure. The **arbitrage time** `τ_arb = f²/σ²` — the expected time between successive profitable arbitrages given fee `f` and volatility `σ` — provides a principled calibration method: set `sigmaLow` where `τ_arb ≈ 10 × blockTime` (arbitrage is rare, \~1 per 10 blocks) and `sigmaHigh` where `τ_arb < blockTime` (arbitrage every block).

**Pair-Specific Calibration** (Fritsch & Canidio, arXiv:2404.05803, WWW'24): Empirical data across major Uniswap pools shows arbitrage losses decrease 20-70% when moving from 12s to 100ms blocks — **not a uniform sqrt relationship** across all pairs. Volatile long-tail pairs see larger reductions than stablecoin pairs. Vault-specific fee calibration should use pair-specific empirical LVR-block-time tables when available.

```solidity
contract DynamicFeeEngine {
    // Volatility thresholds (configurable per vault)
    uint256 public sigmaLow;       // Threshold for stable regime (annualized, 18 decimals)
    uint256 public sigmaHigh;      // Threshold for spike regime
    uint16 public feeBaseBps;      // Base fee in stable regime (default: 5 on both L1 and L2 per D-037)
    uint16 public feeMaxBps;       // Maximum fee in spike regime (default: 100)
    uint256 public inventoryK;     // Inventory sensitivity coefficient for elevated regime

    // LVR-theta fee floor (Singh et al., arXiv:2508.02971, D-029)
    // Guarantees LPs are compensated for adverse selection cost = theta of replicating
    // perpetual continuous-installment put option. The floor is:
    //   lvrThetaFloorBps = f(sigma_implied, liquidity_profile)
    // Calibrated off-chain from implied volatility term structure; submitted on-chain
    // by any executor via the Permissionless Executor Framework (Section 10.1b, D-061).
    uint16 public lvrThetaFloorBps;        // Current LVR-theta fee floor (default: 3 bps)
    uint256 public lastLvrThetaUpdate;     // Timestamp of last calibration
    uint256 public lvrThetaCalibrationTip; // Fixed tip (in base asset) for calibration executor

    // L2 calibration
    uint256 public blockTime;      // Chain block time in seconds (Base=2, Ethereum=12)

    // Oracle configuration — multiple sources, best-of selection
    address public volatilityOracle;       // V4 TWAP, Chainlink, or on-chain realized vol estimator
    bool public feeImpliedVolEnabled;      // Enable fee-implied vol as secondary source (D-030)
    bool public kalmanFilterEnabled;       // Use Kalman filter for sigma estimation (D-038; Nadkarni et al.)

    /// @notice Compute the optimal fee for a given pool state
    /// @param poolId The V4 pool identifier
    /// @param inventoryImbalance Signed inventory imbalance (token0 - token1 in value terms)
    /// @return feeBps The computed dynamic fee in basis points
    /// @dev The fee is the maximum of the regime-based fee and the LVR-theta floor.
    ///      This ensures LPs are always compensated for adverse selection cost even
    ///      in stable regimes where the base fee alone might be insufficient.
    function computeFee(PoolId poolId, int256 inventoryImbalance)
        external view returns (uint16 feeBps)
    {
        uint256 sigma = _getRealizedVolatility(poolId);
        uint16 regimeFee;

        if (sigma < sigmaLow) {
            // Regime 1: Stable — constant base fee
            regimeFee = feeBaseBps;
        } else if (sigma < sigmaHigh) {
            // Regime 2: Elevated — inventory-linear fee (Baggiani et al.)
            uint256 linearComponent = (inventoryK * _abs(inventoryImbalance)) / 1e18;
            uint16 computedFee = uint16(feeBaseBps + linearComponent);
            regimeFee = computedFee > feeMaxBps ? feeMaxBps : computedFee;
        } else {
            // Regime 3: Spike — maximum fee
            regimeFee = feeMaxBps;
        }

        // LVR-theta floor: guarantee LP adverse selection compensation (Singh et al.)
        // The floor ensures fees never drop below the theta decay of the replicating
        // continuous-installment option, regardless of the active regime.
        return regimeFee > lvrThetaFloorBps ? regimeFee : lvrThetaFloorBps;
    }

    /// @notice Update the LVR-theta fee floor from off-chain calibration.
    ///         Callable by anyone (permissionless, per Section 10.1b / D-061).
    /// @param newFloorBps New fee floor in basis points
    /// @param proof Encoded proof derived from the implied volatility term structure.
    ///        On-chain verification: the contract checks the proof against recent pool
    ///        fee data using the bijective fee-implied-vol mapping (D-030, Bichuch &
    ///        Feinstein). The new floor must be consistent with the observed fee-implied
    ///        volatility within a configurable tolerance (default ±15%).
    /// @return reward Fixed tip paid to msg.sender from the vault's fee accumulator
    /// @dev The calibration uses the implied volatility term structure to compute the
    ///      theta of the replicating perpetual continuous-installment put option
    ///      (Singh et al., AFT 2025). Staleness check: calibration accepted at most
    ///      once per `minCalibrationInterval` (default 1 hour).
    function updateLvrThetaFloor(uint16 newFloorBps, bytes calldata proof)
        external returns (uint256 reward)
    {
        require(newFloorBps <= feeMaxBps, "DynamicFeeEngine: floor exceeds max");
        require(
            block.timestamp >= lastLvrThetaUpdate + minCalibrationInterval,
            "DynamicFeeEngine: too frequent"
        );
        require(_verifyLvrThetaProof(newFloorBps, proof), "DynamicFeeEngine: invalid proof");
        lvrThetaFloorBps = newFloorBps;
        lastLvrThetaUpdate = block.timestamp;
        reward = lvrThetaCalibrationTip;
        // Transfer tip to executor from vault fee accumulator
        emit LvrThetaCalibrated(msg.sender, newFloorBps, reward);
    }

    /// @notice Read realized volatility from the configured oracle
    /// @dev Supports V4 TWAP-derived vol, Chainlink implied vol, or on-chain estimator.
    ///      When feeImpliedVolEnabled is true, cross-references with fee-implied vol
    ///      derived from the Bichuch-Feinstein bijection (see _getFeeImpliedVolatility).
    ///      When kalmanFilterEnabled is true, uses Kalman filter Bayesian updating (D-038)
    ///      instead of heuristic EWMA for sigma estimation — provides principled convergence.
    function _getRealizedVolatility(PoolId poolId) internal view returns (uint256);

    /// @notice Derive implied volatility from on-chain fee data (Bichuch & Feinstein, D-030)
    /// @dev The bijective mapping: σ_implied = √(8/T × ln(2√P_x₀ / (2√P_x₀ − fee)))
    ///      where P_x₀ is the current pool price and fee is the observed swap fee.
    ///      This provides a zero-cost on-chain volatility oracle requiring no external
    ///      oracle dependency — derived entirely from pool fee revenue data.
    ///      Used as a secondary/validation source alongside the primary volatility oracle.
    ///      When the primary oracle is stale (>10 minutes), fee-implied vol becomes primary.
    function _getFeeImpliedVolatility(PoolId poolId) internal view returns (uint256);
}
```

**Default Parameters**:

| Parameter              | Base (L2, 2s blocks) | Ethereum (12s blocks) | Description                                                                                                                                            |
| ---------------------- | -------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sigmaLow`             | 20% annualized       | 15% annualized        | Below this: stable regime                                                                                                                              |
| `sigmaHigh`            | 80% annualized       | 60% annualized        | Above this: spike regime                                                                                                                               |
| `feeBaseBps`           | 5                    | 5                     | Stable regime fee — lowered from 8 to 5 on L2 per constant-block-time correction (D-037); research supports 2-3 bps but 5 provides conservative margin |
| `feeMaxBps`            | 150                  | 100                   | Spike regime cap                                                                                                                                       |
| `inventoryK`           | 0.5                  | 0.5                   | Inventory sensitivity                                                                                                                                  |
| `blockTime`            | 2                    | 12                    | Chain block time                                                                                                                                       |
| `lvrThetaFloorBps`     | 3                    | 2                     | LVR-theta fee floor — minimum fee guaranteeing LP adverse selection compensation (D-029)                                                               |
| `feeImpliedVolEnabled` | true                 | true                  | Enable fee-implied vol as secondary oracle source (D-030)                                                                                              |

**Fee-Implied Volatility Oracle** (D-030):

The fee-implied volatility mechanism (Bichuch & Feinstein, Sep 2025) provides a **zero-cost on-chain volatility oracle** derived entirely from pool fee data. The bijective mapping `sigma_implied = sqrt(8/T * ln(2*sqrt(Px0) / (2*sqrt(Px0) - fee)))` means every observed swap fee uniquely determines an implied volatility. This serves three purposes:

1. **Secondary oracle**: Cross-references the primary volatility oracle (RedStone/Chainlink) for divergence detection
2. **Fallback oracle**: When the primary oracle is stale (>10 minutes), fee-implied vol becomes primary — the engine always has a volatility estimate
3. **Manipulation detection**: If the primary oracle diverges >20% from fee-implied vol, the engine flags potential oracle manipulation and defaults to the higher (more conservative) volatility estimate

The fee-implied vol additionally enables a novel **fixed-for-floating fee swap** derivative: locked LP tokens yield their fee stream in exchange for upfront payment. This is an entirely new DeFi primitive not yet implemented anywhere. While the swap instrument itself is deferred post-v1, the oracle infrastructure is implemented in core.

**Events**:

```solidity
event FeeRegimeChanged(PoolId indexed poolId, uint8 regime, uint256 sigma, uint16 feeBps);
event VolatilityOracleUpdated(address indexed oldOracle, address indexed newOracle);
event LvrThetaFloorUpdated(uint16 oldFloor, uint16 newFloor, uint256 timestamp);
event LvrThetaCalibrated(address indexed executor, uint16 newFloorBps, uint256 reward);
event FeeImpliedVolDivergence(PoolId indexed poolId, uint256 primaryVol, uint256 feeImpliedVol);
```

**Inventory-Aware Fee Adjustment**:

The three-regime volatility model determines the *magnitude* of fees; the inventory signal determines the *direction* of fee asymmetry. Inspired by Arrakis Pro's inventory-aware hook (the first whitelisted dynamic fee hook on Uniswap V4), the DynamicFeeEngine applies a signed inventory correction that makes fees cheaper for trades that restore pool balance and more expensive for trades that worsen imbalance. When the pool's token ratio deviates from its target allocation, the `inventoryK` parameter scales the fee asymmetry:

* **Imbalance-correcting trades** (e.g., selling the overweight token): Fee reduced by up to `inventoryK × imbalance%` basis points, incentivizing natural rebalancing by external traders
* **Imbalance-worsening trades** (e.g., buying the already-overweight token): Fee increased by the same factor, discouraging further imbalance

This transforms vault rebalancing from a cost center into a revenue opportunity. Instead of the vault agent paying slippage and gas to rebalance, the fee structure incentivizes *external traders* to rebalance the pool naturally — and the vault earns asymmetric fees in the process. The `inventoryImbalance` parameter passed to `computeFee()` is a signed value computed from the pool's current token ratio relative to the vault's target allocation. Arrakis Pro's production deployment validates this approach: their inventory-aware hook adjusts fees based on pool token ratio rather than external volatility signals alone, achieving measurably better LP returns on imbalanced pools.

***

### 10.3b PA-AMM Activeness Controller

> **Research basis**: "Partially Active Automated Market Makers (PA-AMM)" — arXiv:2602.09887, February 2026

The PA-AMM framework divides AMM reserves into **active** (trading, earning fees, exposed to LVR) and **passive** (idle, no LVR, tracking error to NAV) portions. A single parameter `lambda` (λ) controls the tradeoff. This maps directly to how Gotts Vaults manage active vs idle capital.

**Integration with VaultHook**: The activeness controller is embedded in the VaultHook as a state variable per pool. The vault's managing agent dynamically tunes lambda based on market conditions.

```solidity
// Added to VaultHook state
mapping(PoolId => uint256) public lambda; // 0 = fully passive, 1e18 = fully active (18 decimals)

// Tier-bounded lambda ranges
mapping(uint8 => LambdaRange) public tierLambdaBounds;

struct LambdaRange {
    uint256 minLambda; // Minimum activeness (18 decimals)
    uint256 maxLambda; // Maximum activeness (18 decimals)
}
```

**Tier-Bounded Lambda**:

| Tier       | Lambda Range  | Rationale                                                           |
| ---------- | ------------- | ------------------------------------------------------------------- |
| Unverified | \[0.3, 0.7]   | Restricted range — new agents cannot take extreme positions         |
| Basic      | \[0.2, 0.8]   | Slightly wider                                                      |
| Verified   | \[0.1, 0.9]   | Near-full range                                                     |
| Trusted    | \[0.05, 0.95] | Wide range                                                          |
| Sovereign  | \[0.0, 1.0]   | Full range — institutional agents can fully passive or fully active |

**Interaction with Rehypothecation**: Passive reserves (controlled by `1 - lambda`) are the natural candidates for lending rehypothecation via the `RehypothecationAdapter`. When lambda decreases (more passive), more capital is available for lending yield. When lambda increases (more active), capital is withdrawn from lending to serve as trading liquidity.

**Efficient Frontier**: The PA-AMM paper derives an efficient frontier between LVR cost and tracking error. The `vault-strategist` agent uses this frontier to recommend optimal lambda:

* **High volatility periods**: Decrease lambda (more passive) — reduces LVR exposure at the cost of increased tracking error
* **Stable periods**: Increase lambda (more active) — maximizes fee capture while LVR is low
* **Agent-specific risk preference**: Conservative agents target the low-LVR end of the frontier; aggressive agents target the high-fee end

**Waterfilling Nash Equilibrium for Range Selection (D-041)**:

> **Research basis**: Tang et al. — "Game Theoretic Liquidity Provisioning in CLMMs" (arXiv:2411.10399, Nov 2024; SIGMETRICS 2025). Nash equilibrium follows a **waterfilling strategy**: capital-constrained LPs exhaust budgets, while well-capitalized LPs invest equally across ticks. LPs can improve median daily returns by **$116/day** by moving closer to NE. Open-source code: GitHub WeizhaoT/CLMM-game. Complemented by Xu & Brini (arXiv:2501.07508, Jan 2025): PPO agent for dynamic concentrated liquidity adjustment under regime shifts.

The `vault-strategist` agent uses waterfilling as the **default range selection algorithm** for concentrated liquidity positions. Given the current tick distribution and vault capital, the algorithm computes the Nash equilibrium tick allocation that maximizes expected fee income while accounting for competing LP positions. A PPO reinforcement learning overlay (per Xu & Brini) can dynamically adjust the waterfilling allocation for short-term non-stationary dynamics such as volatility regime shifts.

**Events**:

```solidity
event LambdaUpdated(PoolId indexed poolId, uint256 oldLambda, uint256 newLambda, uint256 agentId);
```

***

### 10.4 CCABidAdapter.sol

Optional module enabling vault collective participation in Continuous Clearing Auctions. This is a novel primitive -- no existing protocol has built an ERC-4626 vault purpose-built for collective CCA bidding.

```solidity
interface ICCABidAdapter {
    /// @notice Submit a collective bid. Amount drawn from vault's idle capital.
    function submitBid(
        address auction,
        uint256 amount,
        uint256 maxPrice,
        uint256 prevTickPrice  // Hint for gas-efficient insertion
    ) external returns (uint256 bidId);

    /// @notice Exit a bid (refund or claim partial fill)
    function exitBid(address auction, uint256 bidId) external;

    /// @notice Claim tokens after auction graduation
    function claimTokens(address auction, uint256 bidId) external;

    /// @notice Post-graduation: deploy claimed tokens as V4 LP
    function deployToPool(address auction, uint256 bidId, DeployParams calldata params) external;

    /// @notice All active bids for this vault
    function getActiveBids() external view returns (BidPosition[] memory);

    /// @notice Valuation of all CCA positions (for totalAssets)
    function totalCCAValue() external view returns (uint256);
}
```

**Integration with CCA Contracts**:

* The adapter wraps `ContinuousClearingAuction.submitBid()`, setting the vault address as bid `owner`
* Refunds and token claims flow back to the vault automatically
* The adapter provides `prevTickPrice` hints computed off-chain for gas-efficient bid insertion
* `exitPartiallyFilledBid()` is called when clearing price exceeds `maxPrice`
* After graduation, `claimTokens()` or `claimTokensBatch()` retrieves ERC-20 tokens

**Risk Parameters** (enforced by the adapter):

| Parameter                  | Default    | Description                                                    |
| -------------------------- | ---------- | -------------------------------------------------------------- |
| `maxSingleAuctionExposure` | 20% of AUM | Max capital in any single CCA auction                          |
| `maxConcurrentAuctions`    | 5          | Max active unsettled auction positions                         |
| `maxTotalCCAExposure`      | 50% of AUM | Max total capital in CCA positions                             |
| `lossFloorBps`             | 1.2 (Base) | LVF-derived minimum unavoidable loss per auction round (D-034) |

**LVF-Calibrated Auction Loss Floor** (D-034):

> **Research basis**: Moallemi, Robinson — "Loss-Versus-Fair: Dutch Auctions on Blockchains" (arXiv, Jun 2024; AFT 2024)

The LVF formula establishes an irreducible loss floor for Dutch auctions on blockchains: **LVF >= sigma \* sqrt(dt/2)**, where sigma is asset volatility and dt is mean interblock time. For Base's 2-second blocks with a typical 5% daily volatility asset (sigma \~ 0.0026/sec), this yields approximately **1.2 basis points** of unavoidable loss per auction round.

The practical rule: a platform wanting Dutch auctions losing less than 2 bps needs block times under 2.75 seconds. **Base satisfies this constraint**, confirming its architectural suitability for CCA operations.

The adapter uses `lossFloorBps` to:

1. **Set minimum bid spread**: Bids must account for at least `lossFloorBps` of expected loss, preventing overly aggressive bidding that would result in guaranteed losses
2. **Calibrate exit timing**: The GDA decomposition theorem (`ARB = VOL * LVF+`) estimates total arbitrage leakage as the product of dollar volume and loss rate — both directly measurable on-chain — informing optimal bid exit decisions
3. **Pareto-optimal starting price**: Per the efficient frontier analysis, the auction start price should never exceed current estimated value (`z0 <= 0` is Pareto optimal when value is known). The adapter enforces this constraint on vault-initiated CCA bids.

**Combinatorial Bid Matching** (future enhancement):

> **Research basis**: Canidio & Henneke, "Fair Combinatorial Auction for Blockchain Trade Intents" (arXiv:2408.12225, 2024 revised 2025).

When multiple factory vaults simultaneously bid in the same CCA, their combined intents can be matched combinatorially for efficiency via coincidence of wants. The adapter supports a `batchSubmitBids()` function that the factory can coordinate across vaults:

* **Surplus sharing**: Uses the counterfactual fairness benchmark from individual bids — each vault receives at least what it would get bidding independently, plus a pro-rata share of any combinatorial surplus
* **Gas optimization**: Batched bids reduce per-vault gas costs by amortizing CCA insertion overhead
* **Factory coordination**: The `AgentVaultFactory` exposes a `coordinateCCABids()` view function that identifies vaults bidding in the same auction epoch and suggests combinatorial groupings

***

### 10.5 FeeModule.sol

Configurable fee layer with contract-enforced caps, reputation-weighted discounts, and ERC-4626 preview compliance (D-069).

**Contract-Enforced Fee Caps** (Morpho V2 pattern): The FeeModule enforces immutable maximum caps at the contract level. No vault creator, curator, or governance action can exceed these caps — they are constants, not parameters.

```solidity
uint16 public constant MAX_MANAGEMENT_FEE_BPS = 500;   // 5% annual — immutable cap
uint16 public constant MAX_PERFORMANCE_FEE_BPS = 5000;  // 50% of profit — immutable cap
```

Curators set actual fees below these caps. The contract reverts on any `setFee()` call that exceeds the immutable maximum.

**Vault Creator Fees**: Creators configure management fees (annual % of AUM, capped at 5%) and performance fees (% of profit above high-water mark, capped at 50%). The protocol takes zero cut — creators keep 100%.

**Reputation-Based Fee Discounts**:

| Depositor Tier | Management Fee Discount | Performance Fee Discount |
| -------------- | ----------------------- | ------------------------ |
| Unverified     | 0%                      | 0%                       |
| Basic          | 5%                      | 5%                       |
| Verified       | 10%                     | 10%                      |
| Trusted        | 20%                     | 15%                      |
| Sovereign      | 30%                     | 25%                      |

**Creator Reputation Fee Caps**: Creators with higher reputation can set higher fees:

| Creator Tier      | Max Management Fee | Max Performance Fee |
| ----------------- | ------------------ | ------------------- |
| Unverified        | 1%                 | 20%                 |
| Basic             | 2%                 | 30%                 |
| Verified          | 3%                 | 40%                 |
| Trusted/Sovereign | 5%                 | 50%                 |

**Fee Collection via Accountant Pattern** (Yearn V3): Fees are not deducted implicitly per-transaction. Instead, a separate `report()` function is called periodically (by the `REPORTING_MANAGER` role, D-053) to calculate profit since the last report, apply management and performance fees, and mint fee shares to the fee recipient. This pattern:

* Prevents per-transaction fee rounding errors from accumulating
* Enables transparent fee accounting (every fee mint is an explicit `FeeCollected` event)
* Integrates with the linear profit unlock buffer (D-018): fees are calculated on *unlocked* profit only, not raw `totalAssets` changes

**ERC-4626 Preview Function Compliance**: `previewDeposit()` and `previewMint()` MUST return values that account for any entry fees. `previewRedeem()` and `previewWithdraw()` MUST account for any exit fees. This is a hard requirement of ERC-4626 compliance — vault aggregators (vaults.fyi, DefiLlama) and composing protocols (Morpho, Pendle) rely on preview functions returning accurate values. Failure to include fees in previews causes depositors to receive fewer shares than expected, breaking trust and aggregator integrations.

Fees are collected by minting additional vault shares to the fee recipient address, diluting existing shares proportionally (standard ERC-4626 pattern).

***

### 10.6 AgentGatedPool.sol

Factory contract for creating Uniswap V4 pools with the VaultHook attached.

```solidity
function createPool(
    PoolKey calldata key,
    uint160 sqrtPriceX96
) external onlyVault returns (PoolId poolId);

function addInitialLiquidity(
    PoolKey calldata key,
    int24 tickLower,
    int24 tickUpper,
    uint128 liquidity
) external onlyVault returns (uint256 tokenId);

function getPoolIds() external view returns (PoolId[] memory);
```

**Access Control**: Only callable by AgentVaultCore contract addresses (registered in the factory).

**Events**:

```solidity
event PoolCreated(PoolId indexed poolId, address indexed hook, uint160 sqrtPriceX96);
event InitialLiquidityAdded(PoolId indexed poolId, uint256 tokenId, uint128 liquidity);
```

***

## V4 Integration Contracts (Auto-Market, Launch Protection, Rehypothecation)

The following three contracts implement the Uniswap V4 volume flywheel: every vault lifecycle event generates Uniswap transactions, turning the protocol from a consumer of Uniswap liquidity into a generator of Uniswap volume.

### 10.7 NAVAwareHook.sol

Custom V4 hook that prices vault share tokens at net asset value, creating a fair and manipulation-resistant secondary market for every vault. Deployed automatically by the factory when `autoPoolEnabled` is true.

**Inherits**: `BaseHook` (v4-periphery)

**Hook Permissions**: `beforeSwap` (with `BEFORE_SWAP_RETURNS_DELTA_FLAG`), `afterSwap`, `beforeInitialize`

**Three core functions**:

1. **NAV-Anchored Custom Curve** (NoOp pattern): `beforeSwap` returns a `BeforeSwapDelta` with `specifiedDelta = -amountSpecified`, completely bypassing the native concentrated liquidity AMM. Instead, the hook prices vault shares at NAV ± a configurable spread (default 50 bps). The spread dynamically adjusts based on redemption queue depth and vault liquidity, preventing the toxic arbitrage that plagues yield-bearing tokens on standard AMMs.
2. **Asymmetric Fee Logic**: Buying vault shares (depositing indirectly) pays a low fee (5 bps), while selling shares (redeeming indirectly) pays a higher fee (25–50 bps) that scales with recent withdrawal velocity. This naturally protects vaults from bank-run dynamics while making entry attractive.
3. **Arbitrage Event Emission**: The `afterSwap` callback emits structured events that agents can index to detect when vault shares trade at persistent premiums or discounts, triggering cross-vault arbitrage strategies.

```solidity
contract NAVAwareHook is BaseHook {
    mapping(PoolId => address) public vaultFor;  // Pool -> vault mapping
    uint16 public buyFeeBps;       // Default: 5 (0.05%)
    uint16 public baseSellFeeBps;  // Default: 25 (0.25%)
    uint16 public maxSellFeeBps;   // Default: 50 (0.50%)
    uint16 public navSpreadBps;    // Default: 50 (0.50%)

    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeSwap: true,          // NAV pricing + asymmetric fees (RETURNS_DELTA)
            afterSwap: true,           // Emit arbitrage indexing events
            beforeInitialize: true,    // Register vault-pool mapping
            afterInitialize: false,
            beforeAddLiquidity: false,
            afterAddLiquidity: false,
            beforeRemoveLiquidity: false,
            afterRemoveLiquidity: false,
            beforeDonate: false,
            afterDonate: false
        });
    }

    function beforeSwap(address sender, PoolKey calldata key,
        IPoolManager.SwapParams calldata params, bytes calldata hookData)
        external returns (bytes4, BeforeSwapDelta, uint24)
    {
        uint256 navPerShare = IVault(vaultFor[key.toId()]).convertToAssets(1e18);
        uint256 spread = _computeSpread(key.toId());

        // NoOp: bypass native AMM entirely
        int128 specifiedDelta = -params.amountSpecified;
        // Custom pricing based on NAV ± spread
        int128 unspecifiedDelta = _priceAtNav(navPerShare, spread, params);

        return (this.beforeSwap.selector,
                toBeforeSwapDelta(specifiedDelta, unspecifiedDelta), 0);
    }

    function afterSwap(address sender, PoolKey calldata key,
        IPoolManager.SwapParams calldata params, BalanceDelta delta,
        bytes calldata hookData) external returns (bytes4, int128)
    {
        uint256 navPerShare = IVault(vaultFor[key.toId()]).convertToAssets(1e18);
        uint256 poolPrice = _getCurrentPoolPrice(key);

        emit ShareTraded(
            key.toId(),
            vaultFor[key.toId()],
            navPerShare,
            poolPrice,
            params.amountSpecified,
            sender
        );

        return (this.afterSwap.selector, 0);
    }

    /// @notice Dynamic spread based on withdrawal velocity
    function _computeSpread(PoolId poolId) internal view returns (uint256) {
        uint256 recentWithdrawals = _getRecentWithdrawalVolume(poolId);
        uint256 vaultLiquidity = IVault(vaultFor[poolId]).totalAssets();
        uint256 velocityBps = (recentWithdrawals * 10000) / vaultLiquidity;
        // Higher withdrawal velocity → wider spread
        return navSpreadBps + (velocityBps / 10);
    }
}
```

**Events**:

```solidity
event ShareTraded(
    PoolId indexed poolId,
    address indexed vault,
    uint256 navPerShare,
    uint256 poolPrice,
    int256 amount,
    address trader
);
```

**Design decisions**:

* NAV is read from discrete snapshots (D-062), not live `vault.convertToAssets(1e18)` — decouples pricing from instantaneous manipulable state
* The NoOp pattern (bypassing native AMM) prevents LPs from suffering toxic arbitrage on NAV accrual
* Asymmetric fees create natural anti-bank-run protection without withdrawal queue complexity
* Events enable off-chain indexing for cross-vault arbitrage detection (see Persona 6)

**NAV Guardrail System (D-062)**:

The hook implements four hardening mechanisms against flash-loan manipulation and stale oracle exploitation:

```solidity
// Additional state for NAV guardrails
uint256 public lastNavSnapshot;         // NAV per share at last snapshot
uint256 public lastSnapshotBlock;       // Block number of last snapshot
uint16 public navMaxChangeBps;          // Max NAV change per interval (default 50)
uint16 public navSnapshotCadence;       // Blocks between snapshots (default 50)
uint16 public stalenessSpreadMultiplier; // Spread multiplier on stale oracle (default 200 = 2x)
bool public marketSafetyMode;           // True when Tier 2+ circuit breaker active

function _getEffectiveNav(PoolId poolId) internal view returns (uint256 nav, uint256 effectiveSpread) {
    address vault = vaultFor[poolId];
    uint256 liveNav = IVault(vault).convertToAssets(1e18);

    // 1. Use snapshot if within cadence, else compute fresh snapshot
    if (block.number - lastSnapshotBlock < navSnapshotCadence) {
        nav = lastNavSnapshot;
    } else {
        // 2. Rate-of-change clamp
        uint256 maxDelta = (lastNavSnapshot * navMaxChangeBps) / 10000;
        if (liveNav > lastNavSnapshot + maxDelta) {
            nav = lastNavSnapshot + maxDelta;
        } else if (liveNav < lastNavSnapshot - maxDelta) {
            nav = lastNavSnapshot - maxDelta;
        } else {
            nav = liveNav;
        }
    }

    // 3. Oracle staleness gates
    effectiveSpread = navSpreadBps;
    uint256 oracleStaleness = IRiskEngine(riskEngine).getOracleStalenessRatio(vault);
    if (oracleStaleness > 8000) { // >80% of max staleness
        effectiveSpread = navSpreadBps * stalenessSpreadMultiplier / 100;
    }
    if (oracleStaleness >= 10000) { // 100% stale — disable NAV pricing
        revert NAVPricingDisabled();
    }

    // 4. Market safety mode
    if (marketSafetyMode) {
        revert MarketSafetyModeActive(); // Fallback to constant-product
    }
}
```

| Parameter                   | Default                    | Range   | Description                                        |
| --------------------------- | -------------------------- | ------- | -------------------------------------------------- |
| `navSnapshotCadence`        | 50 blocks (\~100s on Base) | 10-500  | Blocks between NAV snapshot updates                |
| `navMaxChangeBps`           | 50                         | 10-200  | Max NAV change per snapshot interval (bps)         |
| `stalenessSpreadMultiplier` | 200 (2x)                   | 100-500 | Spread multiplier when oracle approaches staleness |
| `marketSafetyModeEnabled`   | true                       | —       | Auto-enter safety mode on Tier 2+ circuit breaker  |
| `navOverrideTimelock`       | `longTimelock`             | —       | Delay for temporarily increasing the clamp         |

**UX impact**: Depositors can exit positions instantly via a Uniswap swap at NAV-fair pricing, instead of waiting for vault withdrawal processing. This is the single most impactful UX improvement for onboarding.

***

### 10.8 LaunchFeeHook.sol

Descending-fee hook that protects early vault share pool traders from MEV extraction. Adapted from ClankerHook's production-tested `ClankerMevDescendingFees` module ($3.1B+ volume). Deployed alongside NAVAwareHook when `launchFeeEnabled` is true.

**Inherits**: `BaseHook` (v4-periphery)

**Hook Permissions**: `beforeSwap` (fee override)

**Mechanism**: Starts the LP fee at a configurable maximum (default 80%) immediately after pool creation, then decays parabolically to the base fee over a configurable duration (default 120 seconds). This extracts maximum value from eager early bots while allowing normal trading to resume quickly.

```solidity
contract LaunchFeeHook is BaseHook {
    mapping(PoolId => uint256) public poolCreationTime;
    uint16 public maxFeeBps;         // Default: 8000 (80%)
    uint16 public baseFeeBps;        // Default: 30 (0.30%)
    uint32 public decayDuration;     // Default: 120 seconds

    function beforeSwap(address sender, PoolKey calldata key,
        IPoolManager.SwapParams calldata params, bytes calldata hookData)
        external returns (bytes4, BeforeSwapDelta, uint24)
    {
        uint256 elapsed = block.timestamp - poolCreationTime[key.toId()];
        uint24 fee;
        if (elapsed < decayDuration) {
            // Parabolic decay: fee = maxFee * (1 - elapsed/duration)^2
            uint256 remaining = decayDuration - elapsed;
            fee = uint24((maxFeeBps * remaining * remaining) / (decayDuration * decayDuration));
        } else {
            fee = baseFeeBps;
        }
        return (this.beforeSwap.selector, toBeforeSwapDelta(0, 0), fee | OVERRIDE_FEE_FLAG);
    }
}
```

**Novel twist for agent vaults**: The excess fees collected during the high-fee launch window are not burned or sent to creators (as in ClankerHook) but are deposited back into the vault as initial yield. This means the vault's first depositors immediately see positive returns from MEV captured during the launch, creating a powerful first-mover incentive.

**LVF-Optimal Parameterization** (D-034):

> **Research basis**: Moallemi, Robinson — "Loss-Versus-Fair: Dutch Auctions on Blockchains" (arXiv, Jun 2024; AFT 2024)

The LaunchFeeHook's parabolic decay function is calibrated using the LVF efficient frontier analysis. Key insights from the paper:

* **Never start above estimated value**: The Pareto-optimal parameterization has `z0 <= 0` (starting price at or below estimated value when value is known). For vault share pools where NAV is known, the `launchFeeMaxBps` should be calibrated so the effective launch price does not exceed NAV — the high fee extracts from eager bots, not from fair-value traders.
* **Optimal decay rate**: The LVF formula `LVF >= sigma * sqrt(dt/2)` establishes that on Base (2s blocks), the minimum unavoidable auction loss is \~1.2 bps. The decay function should reach `baseFeeBps` within `launchFeeDecaySeconds` — the default 120 seconds ensures the high-fee window is short enough that most legitimate trading occurs at near-base fees.
* **Bayesian extension for uncertain value**: When launching a vault share pool for a new vault (where NAV may be uncertain in the first blocks), the paper provides a Bayesian extension with lognormal prior that quantifies the additional loss from value uncertainty. The `launchFeeMaxBps` should increase proportionally with NAV uncertainty.

**Configuration**: Agent-configurable via `VaultConfig`:

| Parameter               | Default    | Range     | Description                                                                   |
| ----------------------- | ---------- | --------- | ----------------------------------------------------------------------------- |
| `launchFeeMaxBps`       | 8000 (80%) | 1000–9500 | Maximum fee at pool creation                                                  |
| `launchFeeDecaySeconds` | 120        | 30–600    | Duration of parabolic decay                                                   |
| `launchStartAboveNav`   | false      | —         | Whether launch price can exceed NAV (false = Pareto optimal per LVF analysis) |

**Composability**: The LaunchFeeHook is composable with NAVAwareHook — both can be active on the same pool. In practice, the factory deploys a merged contract that combines NAV-aware pricing with descending launch fees. After the launch window expires, only NAV-aware pricing remains active.

***

### 10.9 RehypothecationAdapter.sol

Optional module that deploys idle out-of-range vault liquidity to lending protocols for dual yield (swap fees + lending APY). Inspired by Bunni V2's rehypothecation hook (100x more volume/TVL than standard V4 pools), but operating at the vault strategy level rather than the pool level.

**Key difference from Bunni**: The vault's managing agent configures rehypothecation parameters — the adapter is not a standalone hook but a strategy module the vault delegates to. This gives agents full control over lending risk.

```solidity
interface IRehypothecationAdapter {
    /// @notice Deploy idle out-of-range tokens to the highest-yielding lending venue
    /// @param token Token address to deploy
    /// @param amount Amount to deploy
    /// @param venue Lending venue identifier (morpho, aave, seamless)
    /// @return shares Lending position shares received
    function deployToLending(address token, uint256 amount, bytes32 venue)
        external returns (uint256 shares);

    /// @notice Withdraw from lending to cover incoming swap range
    /// @param token Token address to withdraw
    /// @param amount Amount to withdraw
    /// @param venue Lending venue to withdraw from
    function withdrawFromLending(address token, uint256 amount, bytes32 venue)
        external returns (uint256 actual);

    /// @notice Emergency withdrawal from all lending venues
    function emergencyWithdrawAll() external;

    /// @notice Total value currently deployed across all lending venues
    function totalLendingValue() external view returns (uint256);

    /// @notice Per-venue breakdown of deployed capital and current APY
    function getLendingPositions() external view returns (LendingPosition[] memory);

    /// @notice Update the registry of approved lending destinations
    function setApprovedVenue(bytes32 venue, address adapter, bool approved)
        external;
}

struct LendingPosition {
    bytes32 venue;          // morpho, aave, seamless
    address adapter;        // Venue-specific adapter contract
    address token;          // Deployed token
    uint256 principal;      // Original amount deployed
    uint256 currentValue;   // Current redeemable value (principal + yield)
    uint256 apyBps;         // Current lending APY in basis points
}
```

**Integration with vault lifecycle**:

* **After rebalance**: When a vault rebalances LP positions and liquidity moves out of range, the adapter automatically detects idle tokens and routes them to the highest-yielding approved venue
* **Before swap (JIT withdrawal)**: When the vault's hook detects an incoming swap will cross into a rehypothecated tick range, it preemptively withdraws from lending to ensure sufficient liquidity. Flash accounting ensures the withdrawal + swap settles atomically.
* **totalAssets()**: Lending positions are included in the vault's NAV calculation via `totalLendingValue()`

**Agent-configurable parameters**:

| Parameter                    | Default                     | Description                                                                                                       |
| ---------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `minIdleThreshold`           | 10,000 USDC                 | Minimum idle amount before deployment                                                                             |
| `maxAllocationPerVenue`      | 40% of idle                 | Max capital in any single lending venue                                                                           |
| `maxTotalLendingExposure`    | 60% of idle                 | Max total capital in lending positions (D-042; lowered from 80% per Baude et al. liquidity risk penalty function) |
| `emergencyWithdrawalTrigger` | 5% vault NAV drop in 1 hour | Auto-withdraw from all venues                                                                                     |

**Approved lending venues on Base**:

| Venue    | Adapter                 | Typical APY | Risk Profile            |
| -------- | ----------------------- | ----------- | ----------------------- |
| Morpho   | `MorphoSupplyAdapter`   | 3–8%        | Low (isolated markets)  |
| Aave V3  | `AaveV3SupplyAdapter`   | 2–5%        | Low (battle-tested)     |
| Seamless | `SeamlessSupplyAdapter` | 4–10%       | Medium (newer protocol) |

**Rehypothecation Cap and Liquidity Risk Penalty (D-042)**:

> **Research basis**: Baude, Challet, Toke — "Optimal Risk-Aware Interest Rates for Decentralized Lending" (arXiv:2502.19862, Feb 2025). Derives optimal interest rates via stochastic control, calibrated to USDT/Aave V3 data. Provides a liquidity risk penalty function for idle capital deployment.

The `maxTotalLendingExposure` is set at **60% of idle capital** (lowered from an initial 80%) based on the liquidity risk penalty function from Baude et al. The penalty function accounts for the probability that a sudden withdrawal demand exhausts the vault's idle reserves while capital is locked in lending. At 60%, the expected penalty cost is below the expected lending yield for most stablecoin lending markets. Vaults with higher withdrawal volatility (measured by the coefficient of variation of daily withdrawals) should use a lower cap; the vault-strategist computes the optimal ratio per vault.

**Flywheel effect**: Dual yield (swap fees + lending) → higher vault APY → more deposits → deeper Uniswap liquidity → better execution → more volume → more swap fees. Bunni V2's rehypothecation pools achieved combined APYs of \~13% on stablecoin pairs.

**Liquidity Density Functions (LDFs)** — opt-in via `ldfEnabled`:

By default, vaults manage liquidity as standard V4 concentrated liquidity positions with discrete tick ranges (`tickLower`, `tickUpper`, `liquidity`). This is the well-understood, battle-tested model that all V4 tooling and analytics support natively.

When `ldfEnabled=true` in VaultConfig, the vault uses Bunni v2-style Liquidity Density Functions instead — continuous mathematical functions that define a smooth, differentiable liquidity distribution `f(price)` which the hook maps to the V4 tick grid. LDFs are an advanced option suited to vaults with sophisticated AI-driven strategy agents.

| Aspect                | Default (discrete ticks)              | LDF mode (`ldfEnabled=true`)                     |
| --------------------- | ------------------------------------- | ------------------------------------------------ |
| Position definition   | `tickLower`, `tickUpper`, `liquidity` | Density function `f(price)` mapped to tick grid  |
| Rebalancing           | Remove + re-add at new ticks          | Single density function update ("shapeshifting") |
| Optimization method   | Combinatorial (waterfilling NE)       | Gradient-based (smooth, differentiable)          |
| Gas per rebalance     | Higher (multiple position changes)    | Lower (single function update)                   |
| Hedging               | Numerical Greeks from tick positions  | Closed-form Greeks from `f(price)`               |
| Tooling compatibility | Full V4 ecosystem support             | Requires Bunni v2-compatible tooling             |
| Complexity            | Lower                                 | Higher                                           |

LDFs offer three advantages for vaults that opt in:

1. **Learnable shape optimization**: The liquidity distribution becomes a smoothly optimizable parameter. AI agents can use gradient-based methods to adjust the density function directly, rather than discretizing into tick ranges and solving a combinatorial problem. The vault-strategist treats `f(price)` as part of its learnable parameter set alongside lambda (activeness) and fee regime thresholds.
2. **Gas-efficient "shapeshifting"**: Redistributing liquidity across the price curve does not require removing and re-adding positions at individual ticks. The density function update propagates to the tick grid in a single operation, reducing the gas cost of frequent rebalancing — critical for L2 vaults that rebalance more frequently due to lower gas costs.
3. **Continuous hedging**: A differentiable density function enables closed-form computation of position Greeks (delta, gamma, theta), which the vault agent uses for delta-neutral hedging strategies. This connects to the Lipton-Lucic-Sepp IL hedging framework (Digital Finance vol. 7, 2025) where IL is perfectly hedgeable with European vanilla options — the density function provides the analytic Greeks needed for hedge ratios.

When rehypothecation is also enabled, the RehypothecationAdapter interacts with LDFs naturally: the density function's out-of-range integral determines how much capital is available for lending deployment, and the integral updates smoothly as the function shifts — eliminating the discrete "in-range / out-of-range" boundary that causes JIT withdrawal spikes at tick boundaries. Vaults using discrete tick mode retain the existing JIT withdrawal trigger based on tick range boundaries.

***

### 10.10 RebalanceParams Extension (TWAMM Support)

The `RebalanceParams` struct is extended to support TWAMM (Time-Weighted Average Market Maker) rebalancing for large vault operations. When `useTWAMM` is true, the vault submits a TWAMM order that splits the rebalance over a configurable time window, minimizing price impact and MEV extraction.

```solidity
struct RebalanceParams {
    // Existing fields
    address[] poolIds;
    int24[] tickLowers;
    int24[] tickUppers;
    uint128[] liquidities;
    bytes strategyData;

    // TWAMM extension
    bool useTWAMM;               // Submit as TWAMM order instead of immediate swap
    uint32 twammDuration;        // Duration in seconds (e.g., 14400 = 4 hours)
    uint256 maxSlippageBps;      // Max acceptable slippage for the TWAMM order
}
```

**When to use TWAMM**: The SDK's `StrategyEngine.recommendTWAMM()` evaluates rebalance size vs pool liquidity and recommends TWAMM for operations exceeding 1% of pool TVL. TWAMM orders execute as the first pool action in each block — effectively frontrunning all other trades, which paradoxically protects the vault from being frontrun by external MEV bots.

**Single-Swap Dominance Rule** (formally verified):

> **Research basis**: Dessalvi et al. — "Formal AMM Fee Mechanisms with Lean 4" (arXiv, Jan 2026; DTU/Cagliari). 3,500 lines of machine-checked Lean 4 proofs including the generalized additivity theorem.

The Lean 4 formal verification library proves that **under fees, a single large swap always dominates split swaps** — the generalized additivity theorem shows that splitting a trade into smaller pieces to "save on fees" is strictly suboptimal. This has a direct implication for vault rebalancing:

* **TWAMM is justified for price impact reduction**, not fee optimization. When a vault uses TWAMM to split a large rebalance over time, the benefit comes solely from reduced market impact, not from fee savings on the split trades.
* **When price impact is negligible** (rebalance < 0.5% of pool TVL), the vault should execute as a **single atomic swap** rather than splitting. The SDK's `StrategyEngine.recommendTWAMM()` enforces this by recommending immediate execution below the TWAMM threshold.
* **Rebalance transaction structure**: When a vault rebalances across multiple pools, the formally-verified result implies each pool-level swap should be executed as a single operation, not broken into sub-swaps within the same pool.

**Conditional TWAMM trigger** (optional `beforeSwap` hook extension): The vault's V4 hook can include logic that checks the vault's current asset allocation against target weights set by the agent. When drift exceeds a configurable threshold, the hook automatically initiates a TWAMM order without requiring the agent to submit an explicit transaction. This enables truly autonomous rebalancing.

**Flywheel effect**: Lower-cost rebalancing → better vault performance → more deposits → larger rebalances → more TWAMM volume through Uniswap → more swap fees for passive LPs → deeper liquidity → even lower rebalancing costs.

***

### 10.10a StrategyAdapter Architecture (D-019)

Pluggable adapter architecture inspired by Morpho V2, enabling vaults to integrate new external protocols by deploying adapters rather than upgrading the vault contract itself. This future-proofs vaults against the rapidly evolving DeFi landscape on Base.

**Design**: The vault maintains a registry of approved `IStrategyAdapter` implementations. Each adapter encapsulates all interaction logic for a specific external protocol (lending, yield farming, restaking, etc.). The Curator (am-AMM winner) manages the adapter registry; the Allocator (AI agent) calls adapters within curator-defined risk caps.

```solidity
interface IStrategyAdapter {
    /// @notice Deploy capital to the target protocol
    function deploy(address token, uint256 amount) external returns (uint256 positionId);

    /// @notice Withdraw capital from the target protocol
    function withdraw(address token, uint256 amount) external returns (uint256 actual);

    /// @notice Current value of all positions managed by this adapter
    function totalValue() external view returns (uint256);

    /// @notice Per-position breakdown
    function getPositions() external view returns (AdapterPosition[] memory);

    /// @notice Protocol-specific health check (utilization, delinquency, etc.)
    function healthCheck() external view returns (bool healthy, string memory reason);
}

struct AdapterPosition {
    bytes32 positionId;
    address token;
    uint256 principal;
    uint256 currentValue;
    uint256 apyBps;
    bool withdrawable;  // false if locked/illiquid
}
```

**In-Kind Redemption via `forceDeallocate()`**: Guarantees non-custodial exits even when the vault is illiquid. A depositor can force-exit by receiving underlying adapter positions directly (proportional to their share), paying a 2% penalty to disincentivize casual use. This is the escape hatch for worst-case scenarios where the vault strategy has locked capital.

```solidity
/// @notice Force-exit by receiving underlying positions in kind
/// @param shares Shares to redeem
/// @param receiver Address to receive the underlying positions
/// @dev 2% penalty applied. Positions transferred in proportion to vault holdings.
///      Caller receives pro-rata adapter positions (lending shares, LP tokens, etc.)
///      rather than the base asset. This guarantees exit even during illiquidity.
function forceDeallocate(uint256 shares, address receiver) external returns (uint256 penaltyBps);
```

**Adapter Registry** (managed by Curator):

| Function                                                      | Access                   | Description                                              |
| ------------------------------------------------------------- | ------------------------ | -------------------------------------------------------- |
| `registerAdapter(address adapter, bytes32 protocolId)`        | Curator only             | Add a new adapter to the registry                        |
| `removeAdapter(bytes32 protocolId)`                           | Curator only, timelocked | Remove an adapter (triggers withdrawal of all positions) |
| `setAdapterCap(bytes32 protocolId, uint256 maxAllocationBps)` | Curator only             | Set max allocation as % of vault TVL                     |
| `getAdapters()`                                               | Public view              | List all registered adapters with allocation caps        |

***

### 10.11 Permit2 Agent Delegation Pattern

The Universal Router batch rebalancing pattern leverages Permit2's signature-based approval system to give agents bounded, time-limited, amount-limited authority to execute vault operations without requiring on-chain approval transactions for each action.

**Full flow**:

1. Vault depositor approves Permit2 once (standard ERC-20 `approve`)
2. Vault contract grants Permit2 sub-approval to the managing agent's address with bounded amount and expiration (e.g., 1 week, up to 10% of vault TVL per rebalance)
3. Agent constructs a Universal Router command batch encoding the complete rebalancing plan:

```solidity
// Universal Router command sequence for atomic multi-step rebalance
bytes memory commands = abi.encodePacked(
    uint8(Commands.V4_SWAP),              // Exit current positions
    uint8(Commands.V4_POSITION_MODIFY),   // Close old LP ranges
    uint8(Commands.V4_SWAP),              // Acquire new target assets (multi-hop)
    uint8(Commands.V4_POSITION_MODIFY),   // Open new LP ranges
    uint8(Commands.SETTLE_ALL)            // Final settlement
);
```

4. Agent signs the batch with their ERC-8004 identity, submits via Permit2's `SignatureTransfer`
5. Flash accounting ensures only net token deltas are settled — a 5-step rebalance with intermediate tokens requires only 2 actual transfers

**Safety rails**: The vault implements Permit2's `AllowanceTransfer` with custom validation logic that checks the agent's proposed rebalancing parameters against on-chain constraints (max slippage, approved assets, position size limits). If the agent attempts an operation outside bounds, the Permit2 transfer reverts. This gives depositors cryptographic guarantees about agent behavior without requiring trust.

**Base-specific advantage**: On Base, sub-penny gas costs ($0.001–$0.01 per transaction) make even complex multi-step rebalancing operations economically viable for smaller vaults. Combined with V4's 99% reduction in pool creation gas costs, agents can efficiently manage hundreds of positions across dozens of pools.

***

## Governance Model (Morpho Four-Role Adaptation)

The vault implements a four-role governance model adapted from Morpho V2's architecture ($6B+ TVL) for AI agent workflows. This model integrates with the am-AMM strategy auction (Section 10.16) and ERC-8004 reputation tiers.

| Role          | Actor                                            | Permissions                                                                                                            | Trust Level                         |
| ------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| **Owner**     | Protocol DAO / multisig                          | Set curator, vault name, timelocks. Highest authority. One address per vault.                                          | Highest -- controls vault lifecycle |
| **Curator**   | am-AMM auction winner / human deployer           | Configure adapters, risk caps (absolute + relative), allocators, fees. All actions timelocked proportional to risk.    | High -- won competitive auction     |
| **Allocator** | AI agent operating within curator-defined bounds | Day-to-day capital allocation: rebalancing, lending venue selection, LP range optimization. Multiple agents permitted. | Medium -- bounded by curator policy |
| **Sentinel**  | Automated circuit breaker + human override       | Emergency derisk: revoke pending timelocked actions, pause vault, force-exit positions. Cannot allocate capital.       | Emergency-only -- veto power        |

**Abdication Mechanism**: Curators (and owners) can call `abdicate()` to permanently lock specific parameters, creating irreversible guarantees. A vault whose curator abdicates fee parameters becomes a credibly neutral, permissionless yield instrument. This is a powerful trust primitive for autonomous AI vaults -- once an agent has proven itself, the vault creator can abdicate, making the vault permanently autonomous.

```solidity
/// @notice Permanently lock a parameter. Cannot be reversed.
/// @param paramHash keccak256 of the parameter name
function abdicate(bytes32 paramHash) external onlyOwner;

/// @notice Check if a parameter has been permanently locked
function isAbdicated(bytes32 paramHash) external view returns (bool);
```

**Fee Waterfall** (three tiers, all configurable):

| Fee Layer          | Recipient             | Default Range                  | Source                                       |
| ------------------ | --------------------- | ------------------------------ | -------------------------------------------- |
| Protocol fee       | Protocol treasury     | 2-5% of performance            | Charged on positive returns above HWM        |
| Curator fee        | am-AMM auction winner | 5-15% (market-driven via rent) | Rent paid per block by winning manager       |
| Agent operator fee | Allocator agent(s)    | Variable                       | Residual yield after protocol + curator fees |

Reputation-weighted discounts: Tier 4-5 agents (Trusted/Sovereign) receive 50% lower protocol fees, incentivizing long-term reputation building.

***

## Onboarding and Growth Contracts

The following contracts implement friction reduction mechanisms identified in `research/flywheel.md`. The am-AMM Strategy Auction Module (Section 10.16) has been promoted to core -- see below.

***

## Vault Interaction Safety (Normative)

The following requirements apply to all vault entry/exit paths across all contracts and the SDK. They address slippage protection (EIP-5143 concepts) and async flow handling (EIP-7540 pattern).

### Slippage-Protected Entry and Exit

* The SDK MUST expose slippage-protected vault entry/exit wrappers consistent with EIP-5143 concepts. All `deposit()` and `mint()` calls MUST accept a `maxAssetsIn` parameter. All `withdraw()` and `redeem()` calls MUST accept a `minAssetsOut` parameter.
* On-chain, `previewDeposit()`, `previewMint()`, `previewWithdraw()`, and `previewRedeem()` MUST be conservative estimates: the actual execution MUST be equal to or more favorable than the preview.
* Vaults using fee-on-transfer tokens or rebasing tokens MUST either account for the token behavior in `totalAssets()` or explicitly disallow such tokens via the factory's `baseAsset` validation (revert with `UnsupportedTokenBehavior`).

### Async Deposit/Withdrawal (EIP-7540 Pattern)

* Vaults that integrate illiquid strategies (adapters where immediate full withdrawal is not guaranteed) MUST support an async request/claim lifecycle (following the EIP-7540 pattern) OR explicitly mark themselves as "synchronous only" in the vault's on-chain `VaultDisclosure.strategyHash` metadata.
* Async vaults MUST emit `RequestDeposit` / `RequestRedeem` events and MUST expose `claimableDepositRequest()` / `claimableRedeemRequest()` view functions for queue position and ETA estimation.
* Async requests (if enabled) MUST produce a transferable receipt (ERC-721 or equivalent) with clear claim rights. Receipt holders can transfer their claim position without losing queue priority.

### Vault Capability Reporting

* Every vault MUST expose a `vaultCapabilities()` view function returning a bitmap of supported features:
  * Bit 0: Synchronous deposits
  * Bit 1: Synchronous withdrawals
  * Bit 2: Async deposits (EIP-7540)
  * Bit 3: Async withdrawals (EIP-7540)
  * Bit 4: Share pool trading (V4)
  * Bit 5: Slippage-protected wrappers (EIP-5143-style)
* The SDK MUST read this bitmap before presenting entry/exit options to the user or agent.

> **References**: EIP-5143 (slippage-protected vault interfaces), EIP-7540 (async deposits/redemptions for ERC-4626), Lido WithdrawalQueueERC721 (receipt NFT pattern for async exits).

***

## Composability Surface (Normative)

Vault shares are designed for day-one composability across the DeFi ecosystem (see [01-overview.md](/docs/gotts-vaults/vault/01-overview.md) "Composability as strategy" design principle). This section defines the normative requirements for composable vault tokens.

### Share Token Standards

* Vault shares MUST be ERC-20 compatible and MUST follow ERC-4626 semantics. Any external protocol (Morpho, Pendle) that supports ERC-4626 MUST be able to integrate vault shares without custom adapters.
* Internally, vault shares use ERC-6909 for gas-efficient multi-token accounting. A standard ERC-20 "claim" wrapper MUST be available for external composability.
* ERC-7802 (Superchain token standard) SHOULD be supported for Superchain portability where applicable.

### Async Request Receipts

* Async deposit/withdrawal requests (if enabled via EIP-7540 pattern) MUST produce a transferable receipt token (ERC-721 or equivalent).
* Receipt holders MUST be able to transfer their claim position to another address without losing queue priority or claim rights.
* Receipts MUST expose `claimableAmount()` and `estimatedClaimTime()` view functions for composability with aggregators and UIs.

### Yield Derivative Integration

* Strategy integrations MAY wrap yield-bearing tokens into standardized yield wrappers (SY-like, following the Pendle SY/PT/YT pattern) when integrating yield derivatives.
* If yield tokenization is supported, the vault MUST correctly account for PT/YT positions in `totalAssets()` and MUST handle maturity events (PT redemption, YT expiry) without manual intervention.
* Yield derivative integration is an expansion-track feature (Part C). The core v1 vault does not implement yield tokenization but MUST NOT prevent future integration.

> **References**: ERC-4626 standard; ERC-6909 multi-token; ERC-7802 Superchain portability; EIP-7540 async vaults; Pendle SY/PT/YT tokenization; Lido WithdrawalQueueERC721.

***

## Part B: Phase 2-3 Contracts (Mainnet-Blocking Safety and Onboarding)

The following contracts support onboarding, identity, strategy auctions, and the proxy safety module. The proxy contracts (10.12-10.14, appearing at the end of this section) are a **Phase 3 blocking gate** for mainnet -- without them, v1 security relies entirely on TEE + policy engine.

***

### 10.15 OnboardRouter.sol

Batches identity registration, Permit2 approval, reputation enrollment, and vault deposit into a single call. Designed for ERC-4337 UserOp or EIP-7702 batched execution with paymaster sponsorship.

**Rationale**: The protocol defines persona-specific canonical onboarding (see [00-quickstart.md](/docs/gotts-vaults/vault/00-quickstart.md) Section "Canonical Onboarding (Normative)"). `OnboardRouter` is the canonical entry point that executes the *Vault Participant 2-step* flow in one atomic transaction (or UserOperation where supported). ERC-4337 smart accounts execute an arbitrary array of calls in a single atomic UserOperation. Combined with counterfactual deployment (the wallet address is computed deterministically but not deployed until the first transaction), the OnboardRouter collapses all steps into one gasless operation.

```solidity
struct OnboardParams {
    // Identity registration
    string handle;                 // ERC-8004 handle (unique, human-readable)
    string metadataURI;            // IPFS/HTTPS URI for agent metadata
    uint8 interfaceType;           // 1 = autonomous agent

    // Vault deposit
    address vault;                 // Target vault address
    uint256 depositAmount;         // Amount of base asset to deposit (smallest unit)
    address baseAsset;             // Base asset address (e.g., USDC)

    // Optional: referrer for on-chain attribution
    uint256 referrerAgentId;       // 0 = no referrer; non-zero = agent ID of referrer
}

interface IOnboardRouter {
    /// @notice Atomic onboarding: register identity + approve Permit2 + enroll reputation + deposit
    /// @param params Onboarding parameters
    /// @return agentId The minted ERC-8004 agent ID
    /// @return shares Vault shares received from deposit
    /// @dev Designed to be called within an ERC-4337 UserOp or EIP-7702 batched transaction.
    ///      Accepts paymaster sponsorship — the entire operation can be gasless.
    ///      Supports counterfactual smart account addresses: agents can receive USDC at
    ///      a deterministic address, then execute this call as their first transaction.
    function onboardAndDeposit(OnboardParams calldata params)
        external returns (uint256 agentId, uint256 shares);

    /// @notice Pre-compute the agent's wallet address before deployment (counterfactual)
    /// @param owner The agent operator's address
    /// @param salt CREATE2 salt for deterministic address computation
    /// @return The future wallet address (can receive funds before onboarding)
    function computeWalletAddress(address owner, bytes32 salt)
        external view returns (address);
}
```

**Internal flow**:

1. Call `identityRegistry.register(handle, metadataURI, interfaceType)` → receive `agentId`
2. Call `baseAsset.approve(PERMIT2, type(uint256).max)` → one-time Permit2 approval
3. Call `reputationEngine.enrollAgent(agentId)` → enable automated milestone tracking
4. Call `vault.deposit(agentId, depositAmount)` → deposit and receive shares
5. If `referrerAgentId > 0`, emit `ReferralRecorded(agentId, referrerAgentId)` for on-chain attribution

All steps execute atomically — if any step fails, the entire UserOp reverts.

**Events**:

```solidity
event AgentOnboarded(
    uint256 indexed agentId,
    address indexed vault,
    uint256 depositAmount,
    uint256 sharesReceived,
    uint256 referrerAgentId
);
event ReferralRecorded(uint256 indexed newAgentId, uint256 indexed referrerAgentId);
```

***

### 10.15a IdentityGuardian.sol

Wrapper around the ERC-8004 Identity Registry that adds transfer friction, guardian-based veto, progressive lockdown, credential freezing, and governance-gated identity reissuance. This contract does **not** fork ERC-8004 -- it composes with the existing Identity Registry by implementing the ERC-6454 `isTransferable` interface and hooking into the ERC-721 transfer lifecycle.

**Rationale**: ERC-8004 identity NFTs are standard ERC-721 tokens with no built-in transfer friction. For Gotts Vaults, where identity gates vault access, an unprotected identity NFT is a single point of failure. The IdentityGuardian adds Lens Protocol's production-tested guardian pattern, ENS's progressive fuse lockdown, and emergency freeze capabilities. See [03-custody.md](/docs/gotts-vaults/vault/03-custody.md) Section 8 for the full security architecture.

**Inherits**: `AccessControl`, `ReentrancyGuard`

**State Variables**:

```solidity
contract IdentityGuardian is AccessControl, ReentrancyGuard, IERC6454 {
    bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
    bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
    bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");

    IIdentityRegistry public immutable identityRegistry;

    uint48 public constant GUARDIAN_COOLDOWN = 7 days;
    uint48 public constant EXECUTION_WINDOW = 48 hours;

    // Per-token guardian state
    mapping(uint256 => bool) public guardianEnabled;          // Default: true
    mapping(uint256 => uint48) public unlockRequestTime;      // 0 = no pending transfer
    mapping(uint256 => address) public pendingRecipient;      // Target of pending transfer
    mapping(uint256 => bool) public transferFuseBurned;       // Irreversible: permanent lock
    mapping(uint256 => bool) public credentialFrozen;         // Emergency freeze
    mapping(uint256 => uint256) public lastTransferTimestamp;  // For reputation decay
    mapping(uint256 => uint8) public transferCount;           // Rolling 90-day transfer count

    // Separated pause/unpause key architecture (Trail of Bits Level 3)
    bool public protocolPaused;
}
```

**Core Functions**:

```solidity
// === Guardian Management ===

/// @notice Enable guardian protection. Instant, no cooldown.
/// @dev Default state for all newly minted identities.
function enableGuardian(uint256 tokenId) external onlyTokenOwner(tokenId);

/// @notice Begin the process of disabling guardian protection.
/// @dev DANGER prefix deliberately chosen to prevent accidental LLM execution.
///      Starts a 48-hour governance-tier delay before guardian is actually disabled.
function DANGER__disableGuardian(uint256 tokenId) external onlyTokenOwner(tokenId);

// === Transfer Lifecycle ===

/// @notice Request a transfer of the identity NFT. Starts 7-day cooldown.
/// @param tokenId The ERC-8004 identity token ID
/// @param recipient The intended new owner address
/// @dev DANGER prefix prevents accidental execution. Emits TransferRequested.
function DANGER__requestTransfer(uint256 tokenId, address recipient)
    external onlyTokenOwner(tokenId);

/// @notice Cancel a pending transfer. Callable by token owner or any guardian.
function cancelTransfer(uint256 tokenId) external;

/// @notice Execute a transfer after cooldown has elapsed and within execution window.
/// @dev Callable by anyone (permissionless after cooldown). Updates lastTransferTimestamp.
function executeTransfer(uint256 tokenId) external nonReentrant;

// === Progressive Lockdown (ENS-Inspired Fuses) ===

/// @notice Permanently burn the transfer fuse. Identity can never be transferred.
/// @dev Irreversible. Recommended for agents with reputation >= 100 (Trusted tier).
///      Requires 48-hour governance-tier delay before taking effect.
function burnTransferFuse(uint256 tokenId) external onlyTokenOwner(tokenId);

// === Emergency Mechanisms ===

/// @notice Freeze all vault operations for this identity. Instant.
/// @param tokenId The identity to freeze
/// @dev Callable by GUARDIAN_ROLE or PAUSE_ROLE. Frozen identities cannot:
///      - Deposit, withdraw, or manage vaults
///      - Transfer the identity NFT
///      Unfreezing requires GOVERNANCE_ROLE with 24-hour delay.
function freezeCredential(uint256 tokenId) external onlyRole(GUARDIAN_ROLE);

/// @notice Unfreeze a credential. Requires governance with 24-hour delay.
function unfreezeCredential(uint256 tokenId) external onlyRole(GOVERNANCE_ROLE);

/// @notice Burn a compromised identity and reissue to a new wallet.
/// @param oldTokenId The compromised token to burn/freeze
/// @param newOwner The legitimate agent's new wallet address
/// @param handle The agent handle to preserve
/// @param metadataURI Updated metadata URI for the new token
/// @dev Only callable by GOVERNANCE_ROLE. Preserves 50% of base reputation.
///      The other 50% must be re-earned. Soulbound SBTs in the old wallet
///      are not recoverable.
function reissueIdentity(
    uint256 oldTokenId,
    address newOwner,
    string calldata handle,
    string calldata metadataURI
) external onlyRole(GOVERNANCE_ROLE) returns (uint256 newTokenId);

// === ERC-6454 Transfer Gate ===

/// @notice Check if a transfer is currently allowed for the given token.
/// @dev Composable with ERC-721 transfer hooks.
function isTransferable(uint256 tokenId, address from, address to)
    external view returns (bool)
{
    if (from == address(0) || to == address(0)) return true;  // Allow mint/burn
    if (protocolPaused) return false;                          // Protocol-wide pause
    if (transferFuseBurned[tokenId]) return false;             // Permanent lock
    if (credentialFrozen[tokenId]) return false;               // Emergency freeze
    return !guardianEnabled[tokenId] ||
           (unlockRequestTime[tokenId] != 0 &&
            block.timestamp >= unlockRequestTime[tokenId] + GUARDIAN_COOLDOWN &&
            block.timestamp <= unlockRequestTime[tokenId] + GUARDIAN_COOLDOWN + EXECUTION_WINDOW &&
            pendingRecipient[tokenId] == to);
}

// === Query Functions ===

/// @notice Get the effective reputation after transfer decay
/// @dev Reputation decays to near-zero on transfer and recovers linearly over 30 days
function getEffectiveReputation(uint256 tokenId) external view returns (uint256) {
    uint256 timeSinceTransfer = block.timestamp - lastTransferTimestamp[tokenId];
    uint256 baseReputation = reputationRegistry.getScore(tokenId);
    uint256 FULL_RECOVERY_PERIOD = 30 days;
    if (lastTransferTimestamp[tokenId] == 0) return baseReputation; // Never transferred
    if (timeSinceTransfer >= FULL_RECOVERY_PERIOD) return baseReputation;
    return baseReputation * timeSinceTransfer / FULL_RECOVERY_PERIOD;
}

/// @notice Check if this identity has been transferred multiple times recently
function isHighTransferVelocity(uint256 tokenId) external view returns (bool) {
    return transferCount[tokenId] >= 2; // 2+ transfers in 90 days
}
```

**Events**:

```solidity
event TransferRequested(
    uint256 indexed tokenId, address indexed owner,
    address indexed recipient, uint48 executeAfter
);
event TransferCancelled(uint256 indexed tokenId, address cancelledBy);
event TransferExecuted(uint256 indexed tokenId, address from, address to);
event GuardianEnabled(uint256 indexed tokenId);
event GuardianDisabled(uint256 indexed tokenId);
event TransferFuseBurned(uint256 indexed tokenId);
event CredentialFrozen(uint256 indexed tokenId, address frozenBy);
event CredentialUnfrozen(uint256 indexed tokenId, address unfrozenBy);
event IdentityReissued(
    uint256 indexed oldTokenId, uint256 indexed newTokenId,
    address indexed newOwner, uint256 preservedReputation
);
event ProtocolPaused(address pausedBy);
event ProtocolUnpaused(address unpausedBy);
```

**Access Control Summary**:

| Role              | Can Do                                                   | Cannot Do                                              |
| ----------------- | -------------------------------------------------------- | ------------------------------------------------------ |
| Token owner       | Request transfer, enable/disable guardian, burn fuse     | Freeze credentials, reissue identity, unpause protocol |
| `GUARDIAN_ROLE`   | Cancel transfers, freeze credentials                     | Request transfers, disable guardian, reissue identity  |
| `GOVERNANCE_ROLE` | Reissue identity, unfreeze credentials, unpause protocol | Directly transfer tokens, bypass guardian cooldown     |
| `PAUSE_ROLE`      | Pause protocol (instant), freeze credentials             | Unpause protocol, reissue identity                     |

**Design Decisions**:

* The `DANGER__` prefix on destructive functions follows Lens Protocol's convention and specifically targets LLM safety -- Claude, GPT, and other models are less likely to call functions with explicit danger warnings in their names
* Guardian is enabled by default on mint. Agents must explicitly opt out, adding friction to the "just transfer it" attack path
* Credential freeze is separate from guardian disable -- even if an attacker disables the guardian, a monitoring bot can independently freeze the credential
* Transfer fuse burn is irreversible and intentional -- no governance can override it. This gives Sovereign-tier agents absolute certainty their identity cannot be stolen
* Reputation reissuance preserves only 50% of base reputation -- this creates a real cost for compromise even in legitimate recovery, incentivizing proactive security measures

**Gas Targets**:

* `enableGuardian()`: < 30k gas
* `DANGER__requestTransfer()`: < 80k gas
* `cancelTransfer()`: < 30k gas
* `executeTransfer()`: < 60k gas + `transferFrom` cost
* `freezeCredential()`: < 30k gas
* `reissueIdentity()`: < 200k gas (includes mint + metadata copy)

***

### 10.16 StrategyAuctionModule.sol (Core -- am-AMM)

> **Status**: Promoted to core (D-012). Previously deferred Track E.

Harberger lease auction for vault management rights, based on the am-AMM (auction-managed AMM) model from Adams, Moallemi, Reynolds & Robinson (2024, Financial Cryptography 2025). The production implementation is Bunni v2, which processes \~59% of all V4 hook volume ($138M of $236M tracked) with an open-source reference implementation (BidDog, K=7200 block delay, 1.1x minimum bid multiplier).

**How it works**: A continuous Harberger lease auction sells the right to act as "vault strategy manager." The winning manager (Curator in the four-role governance model) controls capital allocation (rebalancing, lending venue selection, LP range optimization), sets dynamic fee parameters, receives a share of accrued performance fees, and pays continuous rent to vault depositors. Rent is determined K blocks in advance so depositors can react. If a manager underperforms, a higher-bidding manager can outbid them at any time -- creating natural Darwinian selection among agent strategies.

**Why am-AMM for vaults**: Under proven assumptions, am-AMM attracts higher equilibrium liquidity than any fixed-fee model because manager rent redistributes arbitrage profits to LPs/depositors. The rent payment is the economic mechanism; ERC-8004 reputation scores provide the trust signal; and the auction ensures efficient price discovery for management quality.

**Governance integration**: The am-AMM winner maps to the **Curator** role in the four-role governance model (see Governance Model section above). The Curator appoints Allocator agents to execute day-to-day operations within curator-defined bounds. The Sentinel retains emergency veto power regardless of auction outcome.

```solidity
interface IStrategyAuctionModule {
    /// @notice Bid for the right to manage this vault's strategy
    /// @param rentPerBlock Rent offered per block (in base asset units)
    /// @dev Caller must be ERC-8004 registered with minimum reputation.
    ///      Bid must exceed current manager's rent by minBidIncrementBps.
    ///      Manager deposits K blocks of rent upfront as collateral.
    function bid(uint256 rentPerBlock) external;

    /// @notice Top up rent collateral to extend management tenure
    /// @param amount Additional base asset to deposit as rent collateral
    function topUp(uint256 amount) external;

    /// @notice Withdraw accumulated rent (depositors only)
    /// @return amount Rent withdrawn in base asset units
    function withdrawRent() external returns (uint256 amount);

    /// @notice Get the current active manager
    /// @return manager Address of current manager
    /// @return agentId ERC-8004 ID of current manager
    /// @return rentPerBlock Current rent rate
    /// @return collateralRemaining Blocks of rent remaining before manager is evicted
    function getCurrentManager() external view returns (
        address manager, uint256 agentId, uint256 rentPerBlock, uint256 collateralRemaining
    );

    /// @notice Total rent accumulated and available for depositor withdrawal
    function accumulatedRent() external view returns (uint256);

    /// @notice Evict a manager whose rent collateral has been exhausted
    function evictManager() external;
}
```

**Configuration** (set in VaultConfig when `strategyAuctionEnabled = true`):

| Parameter                 | Default               | Description                            |
| ------------------------- | --------------------- | -------------------------------------- |
| `minBidIncrementBps`      | 500 (5%)              | Minimum bid increase over current rent |
| `rentLookAheadBlocks`     | 7200 (\~24h on Base)  | Rent determined K blocks in advance    |
| `minRentCollateralBlocks` | 21600 (\~72h on Base) | Minimum upfront rent deposit           |
| `minManagerReputation`    | 50 (Verified tier)    | Minimum ERC-8004 score to bid          |

**Integration with ERC-8004**: The auction contract reads the bidder's reputation score from the ERC-8004 Reputation Registry and enforces a minimum threshold. Reputation scores are displayed alongside bids in discovery interfaces, enabling depositors to evaluate both economic commitment (rent) and track record (reputation) when choosing vaults.

**Events**:

```solidity
event ManagerBid(uint256 indexed agentId, address indexed manager, uint256 rentPerBlock);
event ManagerEvicted(uint256 indexed agentId, uint256 remainingCollateral);
event RentWithdrawn(address indexed depositor, uint256 amount);
event RentAccumulated(uint256 blockNumber, uint256 amount);
```

***

## L2 Parameter Calibration (Section 10.18)

> **Research basis**: "Layer-2 Arbitrage: An Empirical Analysis" (arXiv:2406.02172, Jun 2024); "Optimistic MEV in Ethereum Layer 2s" (arXiv:2506.14768, Jun 2025); Milionis et al. square-root decay model; Nezlobin & Tassy constant-block-time correction (arXiv:2505.05113, May 2025); Loesch arbitrage-time formula (arXiv:2502.04097, Feb 2025); Fritsch & Canidio pair-specific empirical data (arXiv:2404.05803, WWW'24).

LVR overestimates actual L2 arbitrage by **5x** due to faster block production and different MEV dynamics. Cyclic arbitrage accounts for >50% of gas on Base in Q1 2025. Parameters throughout the protocol require L2-specific calibration. The Nezlobin & Tassy constant-block-time correction (D-037) additionally shows that deterministic block times (as on Base) uniquely minimize asymptotic LVR, yielding fees 5-15% lower than naive Poisson-derived sqrt scaling.

**L2 Calibration Table** (Base vs Ethereum):

| Parameter              | Contract               | Base (2s blocks)               | Ethereum (12s blocks) | Calibration Factor                                                                                                                                        |
| ---------------------- | ---------------------- | ------------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Block time             | DynamicFeeEngine       | 2s                             | 12s                   | —                                                                                                                                                         |
| LVR decay factor       | DynamicFeeEngine       | √2 ≈ 1.41                      | √12 ≈ 3.46            | √(blockTime); use Nezlobin & Tassy Corollary 3.1 for constant-block-time chains                                                                           |
| Expected annual LVR    | DynamicFeeEngine       | \~5x lower than naive estimate | Baseline              | Empirical (arXiv:2406.02172); multi-block decay correction                                                                                                |
| σ\_low threshold       | DynamicFeeEngine       | 20% ann.                       | 15% ann.              | Calibrated via Loesch τ\_arb = f²/σ² where τ\_arb ≈ 10 × blockTime                                                                                        |
| σ\_high threshold      | DynamicFeeEngine       | 80% ann.                       | 60% ann.              | Calibrated via Loesch τ\_arb < blockTime                                                                                                                  |
| Base fee               | DynamicFeeEngine       | 5 bps                          | 5 bps                 | Lowered from 8 to 5 on L2 per constant-block-time correction + empirical 5x LVR overestimate (D-037); research supports 2-3 bps for aggressive strategies |
| Max fee                | DynamicFeeEngine       | 150 bps                        | 100 bps               | Higher on L2                                                                                                                                              |
| Rent lookahead (K)     | StrategyAuctionModule  | 36,000 blocks (\~20h)          | 7,200 blocks (\~24h)  | Equivalent time horizon                                                                                                                                   |
| Min rent collateral    | StrategyAuctionModule  | 108,000 blocks (\~60h)         | 21,600 blocks (\~72h) | Equivalent time horizon                                                                                                                                   |
| Gas cost per rebalance | RehypothecationAdapter | \~$0.01–$0.10                  | \~$5–$50              | 50–500x cheaper on L2                                                                                                                                     |
| TWAMM min size         | RebalanceParams        | 0.5% of pool TVL               | 1% of pool TVL        | Lower threshold — L2 MEV is less aggressive                                                                                                               |

**Implication for vault strategies**: The 5x lower LVR on Base makes LP-focused vault strategies significantly more viable than on mainnet. Vaults can afford tighter ranges and more active management because the cost of adverse selection (LVR) is structurally lower. The `DynamicFeeEngine` accounts for this via the `blockTime` parameter and uses the constant-block-time correction for deterministic L2s. Additionally, Fritsch & Canidio (arXiv:2404.05803) show the LVR reduction from faster blocks is **not uniform across pairs** — volatile long-tail pairs see 60-70% reductions while stablecoin pairs see only 20-30%. Vault-specific strategies should use pair-specific empirical data when available.

***

## VaultReputationEngine with Endgame Defection Detection (Section 10.19)

> **Research basis**: "Understanding LLM Agent Behaviours via Game Theory" (arXiv:2512.07462, Dec 2025); "Multi-Agent Risks from Advanced AI" (arXiv:2502.14143, Feb 2025); zScore papers (arXiv:2507.20494, arXiv:2503.05718).

The VaultReputationEngine contract auto-attests on-chain milestones to the ERC-8004 Reputation Registry. It extends beyond basic milestone tracking with four endgame defection detection mechanisms, addressing the research finding that LLM agents exhibit "incentive-sensitive cooperation but shift toward defection in endgames" with >90% classifiable strategy signatures.

```solidity
interface IVaultReputationEngine {
    // === Milestone Tracking ===

    /// @notice Enroll an agent for automated milestone tracking
    function enrollAgent(uint256 agentId) external;

    /// @notice Claim a reached milestone (auto-attests to ERC-8004 Reputation Registry)
    function claimMilestone(uint256 agentId, bytes32 milestoneId) external;

    /// @notice Get all claimable milestones for an agent
    function getClaimableMilestones(uint256 agentId) external view returns (Milestone[] memory);

    /// @notice Get the composite reputation summary (milestones + defection signals)
    function getSummary(uint256 agentId) external view returns (ReputationSummary memory);

    // === Endgame Defection Detection ===

    /// @notice Record a withdrawal event for defection analysis
    function recordWithdrawal(uint256 agentId, uint256 amount, address vault) external;

    /// @notice Record a deposit event for defection analysis
    function recordDeposit(uint256 agentId, uint256 amount, address vault) external;

    /// @notice Check if an agent is exhibiting endgame defection patterns
    function isDefecting(uint256 agentId) external view returns (bool defecting, uint8 confidence);

    /// @notice Get the exit bond status for a high-tier agent
    function getExitBond(uint256 agentId) external view returns (uint256 bondAmount, uint256 releaseTime);
}

struct ReputationSummary {
    uint256 baseScore;                // Raw milestone-based score
    uint256 decayWeightedScore;       // Exponentially decay-weighted (recent behavior 3x)
    uint8 behavioralRegime;           // 0=cooperative, 1=tit-for-tat, 2=defective
    uint8 regimeConfidence;           // 0-100 confidence in regime classification
    bool exitBondActive;              // Whether agent has an active exit bond
    uint256 withdrawalAcceleration;   // Second derivative of withdrawal rate (0 = stable)
}
```

**Four Endgame Defection Detection Mechanisms**:

1. **Withdrawal Acceleration Detection**: Track the first and second derivatives of each agent's withdrawal rate over a 7-day rolling window. If the withdrawal rate is accelerating (second derivative positive) while the deposit rate is zero across all vaults, flag as potential endgame defection. Agents flagged for acceleration face a 24-hour withdrawal cooldown and elevated monitoring.
2. **Behavioral Regime Classification**: An off-chain ML classifier categorizes agent behavior into three regimes based on the game theory research. Classification results are **submitted on-chain by any executor** via the Permissionless Executor Framework (Section 10.1b, D-061) — no privileged classifier role is required. On-chain verification: the VaultReputationEngine validates submitted classifications against observable on-chain data (withdrawal rates, deposit patterns, hold durations, cross-vault exit sequences) before accepting a regime update. Invalid classifications revert. Executors receive a fixed tip from the reputation engine's fee allocation for accepted classifications.
   * **Cooperative** (Always Cooperate): Consistent deposits, long holds, fee generation — score bonus
   * **Tit-for-Tat**: Reciprocal behavior, deposits/withdrawals tracking vault performance — neutral
   * **Defective** (Always Defect / endgame shift): Accelerating withdrawals, declining deposits, cross-vault exit pattern — tier downgrade and withdrawal rate limiting when classified with >90% confidence
3. **Exponentially Decay-Weighted Scoring**: Reputation uses **exponential decay** (not linear), following the BTRM finding that exponential decay resists reputation inflation from stale history. Recent behavior (last 30 days) is weighted **3x** relative to older behavior. The decay-weighted score captures agents that built reputation cooperatively over months then pivot to extraction in the final phase.

   ```
   decayWeightedScore = Σ(milestone_score_i × e^(-λ × age_days_i))
   ```

   Where λ = ln(3)/30 ≈ 0.0366 (3x weight at 30 days vs 60 days).

   **Defection detection threshold**: Reputation penalties trigger when an agent's defection rate exceeds **\~30%**, calibrated by Fontana et al. (arXiv:2406.13605, Sep 2024) who find that LLM cooperation emerges sharply when opponent defection drops below 30%. The 30% threshold represents the empirically observed phase transition between cooperative and exploitative behavior in LLM populations. Dramatic behavioral differences between LLM families (Llama3 consistently exploitative, GPT models cooperative) justify tracking **agent-type-specific reputation profiles** when the underlying model family is known.

   **Termination horizon masking**: The evolutionary IPD tournaments (arXiv:2507.02618, Jul 2025) show that Gemini correctly identifies high termination probability and rationally defects, while OpenAI models remain cooperative even when defection is rational. The protocol MUST mask the termination horizon from AI agents — use **probabilistic continuation** rather than fixed horizons for vault management periods. Strategy auction rental periods should have stochastic end dates (e.g., K blocks ± random jitter) to prevent rational endgame defection.

   **Protocol-specific reputation boundaries (D-045)**: Rossetti et al. (Nature Communications 16, Article 1524, 2025) find that when agents play multiple games concurrently, spillover effects between games can **reduce cooperation**. Reputation should be **protocol-specific with controlled cross-protocol signaling** — vault reputation scores should not automatically import or be weighted by reputation from other DeFi protocols. Cross-protocol signals are used as advisory inputs (informing tier placement decisions) but not as direct score components, to prevent negative behavioral spillover.
4. **Exit Bond for High-Tier Agents**: Sovereign and Trusted tier agents post a small exit bond equal to **0.5% of their AUM across all factory vaults**. The bond is:
   * **Slashed** if the agent's withdrawal pattern matches the defection profile (classifier confidence >90%) within 30 days of posting
   * **Returned** after a 30-day clean exit window with no defection signals
   * **Optional for Verified and below** — only required for agents with significant cross-vault influence
5. **TraceRank Payments-as-Endorsements (D-044)**:

> **Research basis**: Shi — "TraceRank: Sybil-Resistant Service Discovery for Agent Economies" (arXiv:2510.27554, Oct 2025). Reputation-weighted ranking where **payment transactions serve as endorsements**. Reputation propagates through payment flows weighted by payer seed reputation, transaction value, and temporal recency/decay. Explicitly integrates ERC-8004 agent registries.

Vault interactions (deposits, fee payments, rent payments in am-AMM, CCA participation) serve as reputation seed scores that propagate through the protocol interaction graph. An agent that deposits into a high-reputation vault receives a reputation signal proportional to `deposit_amount × vault_creator_reputation × temporal_decay`. This creates a natural reputation flow: successful vault creators endorse their depositors by accepting capital, and depositors endorse vaults by committing real funds. The payments-as-endorsements paradigm aligns with the existing exponential decay weighting and provides resistance to Sybil attacks because endorsement propagation requires real capital commitment.

**Sybil-proof impossibility constraint**: Schlegel, Mattauch & Moldovanu (arXiv:2407.14485, Jul 2024, updated May 2025) prove that the **only non-wasteful, symmetric, incentive-compatible, and Sybil-proof mechanism is a second-price auction with symmetric tie-breaking**. Any proportional allocation rule cannot be both Sybil-proof and incentive-compatible under private information. This means reputation-weighted resource allocation (fee discounts, tier caps) should accept Bayesian relaxation rather than assuming strict Sybil-proofness, or use auction-like mechanisms for high-stakes allocation decisions.

**Mandatory probationary period**: Graser et al. (PLOS Computational Biology, Feb 2025) prove that **no equilibrium exists where all players start cooperating in round 1** — defect-and-run mutants exploit initial cooperation. This directly validates starting new agents at low reputation with mandatory testing phases where new agents earn reduced rewards. The Unverified tier's $1,000 deposit cap and limited session scope implement this evolutionary insight.

**Milestones** (auto-attested to ERC-8004 Reputation Registry):

| Milestone                         | Points | Qualifier                    | Anti-Gaming                      |
| --------------------------------- | ------ | ---------------------------- | -------------------------------- |
| First Deposit                     | 70     | null (once ever)             | Self-deposit excluded            |
| Steady Staker (30-day hold)       | 75     | vault address                | Must maintain >$1K position      |
| Diversifier (3+ vaults)           | 80     | null                         | $1K minimum per vault            |
| Profitable Exit                   | 80     | vault address                | Net positive P\&L required       |
| Diamond Hands (90-day hold)       | 85     | vault address                | Locked capital, no partial exits |
| Capital Attractor (5+ depositors) | 90     | vault address (creator only) | Unique depositors, self excluded |
| CCA Lifecycle Complete            | 95     | auction address              | Full bid→claim→LP cycle          |

**Events**:

```solidity
event MilestoneClaimed(uint256 indexed agentId, bytes32 indexed milestoneId, uint256 points);
event DefectionFlagged(uint256 indexed agentId, uint8 regime, uint8 confidence);
event ExitBondPosted(uint256 indexed agentId, uint256 amount);
event ExitBondSlashed(uint256 indexed agentId, uint256 amount, bytes32 reason);
event ExitBondReturned(uint256 indexed agentId, uint256 amount);
```

***

### 10.19a Continuous Yield Feedback Extension (D-082, D-087)

> **Research basis**: `research/8004-enhancements.md` Section 2.3. Complements the milestone system with continuous quantitative feedback using the ERC-8004 Reputation Registry's `giveFeedback()` function.

The milestone system (Section 10.19) handles **binary achievements** that move agents through reputation tiers. This extension adds **continuous performance feedback** — automated yield data and qualitative reviews — that runs alongside milestones as a separate track. In v1, continuous feedback does NOT affect tier progression to avoid gaming.

**Three Feedback Tracks**:

| Track               | Data Type                   | Frequency          | Tags                                              | Purpose                             | Affects Tier? |
| ------------------- | --------------------------- | ------------------ | ------------------------------------------------- | ----------------------------------- | ------------- |
| Yield Performance   | Continuous (signed decimal) | Per epoch (weekly) | `tradingYield` + `week`/`month`/`year`            | Performance evaluation, leaderboard | No (v1)       |
| Qualitative Reviews | Continuous (0-100)          | On interaction     | `starred` + `overall`, `successRate` + `deposits` | Quality signal                      | No (v1)       |
| Infrastructure      | Continuous (percentage)     | Per epoch          | `uptime` + `week`, `responseTime` + `mcp`         | Reliability signal                  | No            |

The VaultReputationEngine extends with yield feedback submission:

```solidity
interface IYieldFeedbackExtension {
    /// @notice Submit automated yield feedback for a vault manager (per-epoch)
    /// @param agentId The vault manager's ERC-8004 agent ID
    /// @param yieldBps Yield in basis points (signed — supports negative yields)
    /// @param tag2 Time period: "week", "month", "year"
    /// @param feedbackURI IPFS URI for rich structured data (Sharpe, drawdown, IL)
    /// @param feedbackHash keccak256(feedbackURIContents) for tamper evidence
    function submitYieldFeedback(
        uint256 agentId,
        int128 yieldBps,
        string calldata tag2,
        string calldata feedbackURI,
        bytes32 feedbackHash
    ) external;

    /// @notice Submit qualitative feedback (investor-submitted)
    /// @param agentId Target agent
    /// @param score Quality score (0-100)
    /// @param tag1 Category: "starred", "successRate", "responseTime"
    /// @param tag2 Subcategory: "overall", "deposits", "a2a"
    function submitQualitativeFeedback(
        uint256 agentId,
        uint8 score,
        string calldata tag1,
        string calldata tag2
    ) external;
}
```

The `feedbackURI` resolves to an IPFS-hosted JSON file with rich structured data:

```json
{
  "type": "agenticvaults/yield-feedback/v1",
  "agentId": "8453:42",
  "epoch": 12,
  "tag1": "tradingYield",
  "tag2": "week",
  "riskMetrics": {
    "sharpeRatio7d": 2.1,
    "sortinoRatio7d": 2.8,
    "maxDrawdown30d": -0.015,
    "impermanentLoss7d": -0.002,
    "realYield": true,
    "yieldSource": "trading_fees + lending_interest"
  },
  "vaultSnapshot": {
    "tvlUsd": 5200000,
    "sharePrice": "1.0107",
    "asset": "USDC",
    "vaultAddress": "0x..."
  },
  "onChainProof": {
    "blockNumber": 28500000,
    "sharePriceAtStart": "1.0082",
    "sharePriceAtEnd": "1.0107"
  }
}
```

**Events**:

```solidity
event YieldFeedbackSubmitted(uint256 indexed agentId, int128 yieldBps, string tag2, bytes32 feedbackHash);
event QualitativeFeedbackSubmitted(uint256 indexed fromAgentId, uint256 indexed toAgentId, uint8 score, string tag1);
```

***

### 10.19b IYieldLeaderboard (D-087)

> **Research basis**: `research/8004-enhancements.md` Section 2.4. On-chain leaderboard aggregating `tradingYield` feedback from the Reputation Registry.

A read-only contract that queries `getSummary()` on the Reputation Registry filtered by `tag1: "tradingYield"` and returns sorted results. Primarily used for on-chain composability (e.g., a vault that only accepts deposits from agents whose own managed vaults are in the top 20).

```solidity
interface IYieldLeaderboard {
    struct LeaderboardEntry {
        uint256 agentId;
        int128 averageYield;       // From Reputation Registry getSummary()
        uint256 feedbackCount;
        uint256 lastFeedbackBlock;
    }

    /// @notice Returns the top N agents by average tradingYield for a given time period
    /// @param tag2 Time period: "week", "month", "year"
    /// @param count Number of entries to return
    function getTopAgents(string calldata tag2, uint256 count)
        external view returns (LeaderboardEntry[] memory);

    /// @notice Returns a specific agent's leaderboard position
    function getAgentRank(uint256 agentId, string calldata tag2)
        external view returns (uint256 rank, LeaderboardEntry memory entry);
}
```

The on-chain leaderboard is gas-intensive for large agent counts. The primary query interface is a **subgraph-indexed leaderboard** (see SDK Section 11.7a) that computes running averages, variance, and rankings off-chain from `NewFeedback` events filtered by `indexedTag1 = "tradingYield"`.

***

***

## Part D: Strategy Marketplace Contracts (Post-v1, Track H)

Contracts supporting the agent learning and strategy economy (see [17-learning-economy.md](/docs/gotts-vaults/vault/17-learning-economy.md)). These contracts are expansion-track (D-089 through D-093) and do not block v1 launch.

***

### 10.D1 StrategyMarketplaceRegistry.sol

On-chain registry for agent strategies. Tracks published strategies, records purchases, enables slashing on underperformance, and manages royalty claims.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import { IIdentityRegistryAdapter } from "./IdentityRegistryAdapter.sol";

/// @title StrategyMarketplaceRegistry
/// @notice Permissionless registry for agent-published DeFi strategies.
///         Sellers stake USDC as collateral; underperformance burns the stake.
contract StrategyMarketplaceRegistry {

    struct Strategy {
        bytes32 id;               // keccak256(agentId + version + timestamp)
        uint256 agentId;          // ERC-8004 token ID of the seller
        uint256 priceUsdc;        // Base price in USDC (6 decimals, before alpha decay)
        uint256 stakedAmount;     // Slashable USDC stake in StrategyEscrow
        bytes32 zkProofHash;      // EZKL Halo2 proof hash (bytes32(0) if not provided)
        bytes32 easAttestationHash; // EAS performance attestation hash
        string metadataURI;       // IPFS URI of full strategy JSON
        uint256 createdAt;        // Unix timestamp of publication
        uint256 subscribersCount; // Number of lifetime purchases
        bool active;              // False if deactivated by seller or slashed 100%
    }

    mapping(bytes32 strategyId => Strategy) public strategies;
    mapping(bytes32 strategyId => address verifierContract) public verifiers;
    mapping(bytes32 strategyId => uint256 accruedRoyalties) public royalties;

    IIdentityRegistryAdapter public identityAdapter;
    address public escrow;   // StrategyEscrow contract address
    address public slasher;  // Authorized slasher (StrategyEscrow after dispute resolution)

    /// @notice Publish a new strategy. Caller must be ERC-8004 Verified+ (score >= 50).
    /// @param id        Strategy ID (keccak256(agentId + version + timestamp))
    /// @param agentId   ERC-8004 token ID of the seller
    /// @param priceUsdc Base USDC price (6 decimals). Callers apply alpha decay externally.
    /// @param metadataURI IPFS URI of full strategy JSON (see VaultStrategy TypeScript type)
    /// @param zkProofHash EZKL Halo2 proof hash. Required if priceUsdc > 1_000_000 ($1.00).
    /// @param easAttestationHash EAS performance attestation hash.
    function publishStrategy(
        bytes32 id,
        uint256 agentId,
        uint256 priceUsdc,
        string calldata metadataURI,
        bytes32 zkProofHash,
        bytes32 easAttestationHash
    ) external;

    /// @notice Record a strategy purchase. Called by StrategyEscrow after payment confirmation.
    /// @param strategyId Strategy being purchased
    /// @param buyerAgentId ERC-8004 token ID of the buyer
    function recordPurchase(bytes32 strategyId, uint256 buyerAgentId) external;

    /// @notice Slash a strategy's stake. Called by StrategyEscrow after dispute resolution.
    ///         Slashed stake is burned (sent to address(0xdead)) — not redistributed.
    /// @param strategyId Strategy to slash
    /// @param slashBps   Slash percentage in basis points (2500=25%, 5000=50%, 10000=100%)
    function slash(bytes32 strategyId, uint256 slashBps) external onlySlasher;

    /// @notice Claim accumulated royalties. Only callable by registered seller after
    ///         dispute windows have closed.
    /// @param strategyId Strategy to claim royalties for
    function claimRoyalties(bytes32 strategyId) external;

    /// @notice Deactivate a strategy (seller-initiated). Stops new purchases.
    ///         Active subscriptions remain valid until expiry.
    function deactivateStrategy(bytes32 strategyId) external;

    // View functions
    function getStrategy(bytes32 strategyId) external view returns (Strategy memory);
    function isVerifiedPurchaser(bytes32 strategyId, uint256 buyerAgentId) external view returns (bool);
    function getClaimableRoyalties(bytes32 strategyId) external view returns (uint256);

    // Events
    event StrategyPublished(bytes32 indexed id, uint256 indexed agentId, uint256 priceUsdc, string metadataURI);
    event StrategyPurchased(bytes32 indexed strategyId, uint256 indexed buyerAgentId, uint256 pricePaid);
    event StrategySlashed(bytes32 indexed strategyId, uint256 slashBps, uint256 amountBurned);
    event RoyaltiesClaimed(bytes32 indexed strategyId, address indexed seller, uint256 amount);
}
```

**Security considerations**:

* `publishStrategy()` reverts if `priceUsdc > 1_000_000` ($1.00) and `zkProofHash == bytes32(0)` — enforces tiered ZK verification (D-091).
* `slash()` is `onlySlasher` — only `StrategyEscrow` can slash after dispute resolution. The factory owner cannot slash directly.
* `claimRoyalties()` reverts if any open dispute window exists for recent purchases (7-day window per purchase).
* `metadataURI` must be an IPFS CID (validated by checking `ipfs://` prefix) — centralized URI hosts are rejected.

***

### 10.D2 StrategyEscrow\.sol

Optimistic verification and escrow for strategy purchases. Implements the UMA-inspired dispute mechanism (D-089): \~98.5% of claims resolve without dispute. Contested claims go to token-weighted commit-reveal voting.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/// @title StrategyEscrow
/// @notice Manages seller stakes, purchase escrow, and dispute resolution.
///         Dispute resolution uses optimistic verification (UMA pattern):
///         7-day window, disputers post 5% bond, commit-reveal voting.
///         Slashed stake is BURNED to address(0xdead) — never redistributed.
contract StrategyEscrow {

    uint256 public constant DISPUTE_WINDOW = 7 days;
    uint256 public constant DISPUTE_BOND_BPS = 500;  // 5% of strategy price
    uint256 public constant SLASH_BURN_DELAY = 30 days; // Undisputed slash burns after 30 days

    struct Purchase {
        bytes32 strategyId;
        uint256 buyerAgentId;
        uint256 pricePaid;        // USDC paid (6 decimals)
        uint256 purchasedAt;      // Unix timestamp
        bool disputed;
        bool resolved;
    }

    struct Dispute {
        bytes32 purchaseId;
        address disputer;
        uint256 bond;             // 5% of strategy price, at risk
        uint256 filedAt;
        DisputeState state;       // Pending / CommitPhase / RevealPhase / Resolved
        bool sellerSlashed;
        bool disputerBondBurned;
    }

    enum DisputeState { Pending, CommitPhase, RevealPhase, Resolved }

    mapping(bytes32 purchaseId => Purchase) public purchases;
    mapping(bytes32 disputeId => Dispute) public disputes;

    // Seller stakes: agentId → strategyId → staked USDC amount
    mapping(uint256 agentId => mapping(bytes32 strategyId => uint256)) public sellerStakes;

    /// @notice Lock seller stake when publishing strategy.
    ///         Called by StrategyMarketplaceRegistry.publishStrategy() flow.
    function lockStake(uint256 agentId, bytes32 strategyId, uint256 amount) external;

    /// @notice Process x402 payment and record purchase.
    ///         Called by x402 facilitator after payment confirmation.
    ///         Starts 7-day dispute window.
    function recordPurchase(
        bytes32 strategyId,
        uint256 buyerAgentId,
        uint256 pricePaid
    ) external returns (bytes32 purchaseId);

    /// @notice File a dispute within DISPUTE_WINDOW. Disputer must post 5% bond.
    ///         Starts commit-reveal voting.
    function fileDispute(bytes32 purchaseId) external payable;

    /// @notice Commit vote (hashed: keccak256(vote + salt)). Voting period: 24 hours.
    function commitVote(bytes32 disputeId, bytes32 commitment) external;

    /// @notice Reveal vote. Reveal period: 24 hours after commit period ends.
    function revealVote(bytes32 disputeId, bool voteSlash, bytes32 salt) external;

    /// @notice Resolve dispute after reveal period.
    ///         If seller slashed: burns stake per slash table, returns bond to disputer.
    ///         If seller not slashed: burns disputer bond.
    ///         Either way, some funds are burned — prevents coordinated dispute farming.
    function resolveDispute(bytes32 disputeId) external;

    /// @notice Claim undisputed royalties after DISPUTE_WINDOW.
    ///         Called by StrategyMarketplaceRegistry.claimRoyalties().
    function releaseRoyalties(bytes32 strategyId, address seller) external;

    // Events
    event StakeLocked(uint256 indexed agentId, bytes32 indexed strategyId, uint256 amount);
    event PurchaseRecorded(bytes32 indexed purchaseId, bytes32 indexed strategyId, uint256 buyerAgentId);
    event DisputeFiled(bytes32 indexed disputeId, bytes32 indexed purchaseId, address disputer);
    event DisputeResolved(bytes32 indexed disputeId, bool sellerSlashed, uint256 amountBurned);
}
```

**Design rationale (D-089)**:

The critical property is that slashed funds are *burned* (sent to `address(0xdead)`), not redistributed. If slash proceeds went to buyers or disputers, a seller could collude with their own buyer accounts: self-dispute, vote-slash themselves, and recover the stake. Burning eliminates this attack entirely — there is no recipient to collude with.

Dispute bonds are similarly burned regardless of outcome (returned to disputer if they win, burned if they lose). This prevents coordinated "dispute-to-earn" schemes.

**Slash percentages** (called by `StrategyMarketplaceRegistry.slash()` after dispute resolution):

| Underperformance vs. Claimed Sharpe | Slash %       |
| ----------------------------------- | ------------- |
| Within 15% of claimed               | 0% (no slash) |
| 15–30% below claimed                | 25%           |
| 30–50% below claimed                | 50%           |
| > 50% below claimed                 | 100%          |

***

## Part C: Expansion-Track Contracts (Post-v1)

The following contracts are all deferred past v1. They are preserved here as valuable IP and future implementation targets, but should NOT block audit or deployment timelines. Promotion of any expansion contract requires core safety and reliability gates to be green.

***

### 10.E1 RecursiveLendingAdapter.sol (D-048)

Implements atomic flash-loan leverage loops as a strategy adapter. The adapter deposits collateral into a lending protocol, borrows against it, swaps to the collateral asset, and redeposits — all within a single atomic transaction via flash loan. This is DeFi's primary leverage mechanism (Morpho reports 64% of volume from looping strategies).

**Target protocols on Base**: Morpho Blue ($4.2B TVL), Aave V3 E-Mode (90% LTV for ETH-correlated assets), Contango (flash-loan looping infrastructure).

```solidity
interface IRecursiveLendingAdapter {
    struct LeverageConfig {
        address lendingVenue;       // Morpho, Aave, etc.
        address collateralAsset;    // e.g., wstETH
        address borrowAsset;        // e.g., WETH
        uint16 maxLeverage;         // Max loop multiplier (default 500 = 5x on Base)
        uint16 healthFactorFloor;   // Min HF before auto-deleverage (default 130 = 1.3)
        uint16 deleverageTrigger;   // HF threshold for pre-emptive unwind (default 150 = 1.5)
        uint16 maxLeveragedAumBps;  // Max % of vault AUM in leveraged positions (default 4000 = 40%)
        uint16 targetSpreadBps;     // Min borrow-vs-yield spread (default 50 = 0.5%)
    }

    /// @notice Execute a recursive lending loop atomically via flash loan
    /// @param config Leverage parameters
    /// @param initialAmount Amount of collateral to lever up
    /// @param targetLeverage Desired leverage multiplier (must be <= maxLeverage)
    /// @return leveragedAmount Total position size after looping
    /// @return healthFactor Resulting health factor
    function leverageUp(
        LeverageConfig calldata config,
        uint256 initialAmount,
        uint16 targetLeverage
    ) external returns (uint256 leveragedAmount, uint256 healthFactor);

    /// @notice Unwind leveraged position (partially or fully)
    /// @param config Leverage parameters (must match active position)
    /// @param deleverageAmount Amount to unwind (0 = full unwind)
    function leverageDown(
        LeverageConfig calldata config,
        uint256 deleverageAmount
    ) external returns (uint256 returnedAmount, uint256 healthFactor);

    /// @notice Emergency deleverage — callable by Sentinel when HF < deleverageTrigger
    function emergencyDeleverage(LeverageConfig calldata config) external;

    /// @notice Current health factor for the vault's leveraged position
    function getHealthFactor(address vault) external view returns (uint256);

    /// @notice Current effective leverage multiplier
    function getEffectiveLeverage(address vault) external view returns (uint256);

    event LeverageIncreased(address indexed vault, uint256 amount, uint16 leverage, uint256 healthFactor);
    event LeverageDecreased(address indexed vault, uint256 amount, uint256 healthFactor);
    event EmergencyDeleverage(address indexed vault, uint256 returnedAmount, string reason);
}
```

**Risk guardrails**:

* Maximum 5-8x leverage on Base (vs 15x on mainnet) due to thinner L2 exit liquidity
* Automatic deleverage at HF < 1.5 (triggered by Sentinel role or off-chain monitoring)
* Maximum 40% of vault AUM in leveraged positions (`maxLeveragedAumBps`)
* Minimum spread requirement prevents unprofitable loops
* AI agent performs continuous HF monitoring with off-chain signals (whale wallet tracking, validator queue depth, sentiment analysis) for pre-emptive deleveraging per Sommelier pattern — Sommelier Real Yield ETH outperformed stETH holders by 2.25x

***

### 10.E2 TrancheModule.sol (D-049)

Optional module that splits vault shares into Senior (AA) and Junior (BB) tokens following the Idle Finance / Pareto Perpetual Yield Tranche (PYT) model. Junior provides first-loss capital and earns boosted yield; Senior receives protected yield. Both are perpetual (no maturity), no-lock, and ERC-20 compatible.

This is distinct from the factory-level hierarchical insurance tranching (D-039). PYT is a depositor-facing risk segmentation module for individual vaults; D-039 is cross-vault loss socialization at the factory level.

```solidity
interface ITrancheModule {
    struct TrancheConfig {
        uint16 juniorMinBps;          // Min Junior as % of vault TVL (default 500 = 5%)
        uint16 yieldSplitCeiling;     // Max yield % to Junior (default 9000 = 90%)
        bool adaptiveSplitEnabled;    // Dynamic yield split based on liquidity ratio
        uint16 seniorFloorYieldBps;   // Min guaranteed Senior yield (default 200 = 2%)
    }

    /// @notice Enable tranching on a vault. Deploys AA and BB ERC-20 tokens.
    /// @param vaultAddress The vault to enable tranching on
    /// @param config Tranche parameters
    /// @return seniorToken Address of the Senior (AA) ERC-20
    /// @return juniorToken Address of the Junior (BB) ERC-20
    function enableTranches(
        address vaultAddress,
        TrancheConfig calldata config
    ) external returns (address seniorToken, address juniorToken);

    /// @notice Deposit into a specific tranche
    /// @param tranche 0 = Senior, 1 = Junior
    /// @param assets Amount of base asset to deposit
    /// @return shares Tranche shares received
    function depositTranche(uint8 tranche, uint256 assets) external returns (uint256 shares);

    /// @notice Withdraw from a specific tranche
    function withdrawTranche(uint8 tranche, uint256 shares) external returns (uint256 assets);

    /// @notice Current yield split: what percentage goes to Junior
    function currentYieldSplit() external view returns (uint16 juniorYieldBps);

    /// @notice Current coverage ratio (Junior TVL / Total TVL)
    function coverageRatio() external view returns (uint256);

    /// @notice Verify vault creator/manager holds minimum Junior position
    function verifyManagerSkinInGame(uint256 agentId) external view returns (bool sufficient);

    event TranchesEnabled(address indexed vault, address seniorToken, address juniorToken);
    event TrancheDeposit(address indexed depositor, uint8 tranche, uint256 assets, uint256 shares);
    event YieldSplitAdjusted(uint16 newJuniorYieldBps, uint256 coverageRatio);
}
```

**Key design properties**:

* **Manager skin-in-the-game**: Vault creators and managers are REQUIRED to hold Junior shares equal to at least `juniorMinBps` (default 5%) of vault TVL. This is enforced at deposit time — the vault rejects participant deposits if the manager's Junior holding is below the minimum.
* **Adaptive yield split**: When `adaptiveSplitEnabled` is true, the yield split dynamically adjusts based on the Senior/Junior liquidity ratio. When Junior coverage is low (more deposits needed), Junior yield increases to attract capital. When Junior is well-funded, Senior yield increases. The formula follows Idle Finance's proven adaptive split mechanism.
* **Composability**: Senior AA tokens are lower-risk, suited as conservative collateral on Morpho. Junior BB tokens are higher-risk/higher-yield, suited as leverage collateral on Morpho or for yield-seeking agents. Both are standard ERC-20 and compatible with any DeFi protocol.
* **Loss waterfall**: On drawdown events, Junior absorbs losses first. Senior is only impacted after Junior is fully depleted. This naturally protects conservative depositors while giving aggressive agents leveraged yield exposure.

***

### 10.E3 PendleAdapter.sol (D-051)

Strategy adapter enabling vault interaction with the Pendle protocol for yield tokenization. Supports two primary strategies: fixed-rate capture via PT-as-collateral leverage loops, and yield stripping via PT/YT splitting of vault share tokens.

```solidity
interface IPendleAdapter {
    struct PendleConfig {
        address pendleRouter;         // Pendle router contract on Base
        address market;               // Target Pendle market
        uint256 maturity;             // PT/YT maturity timestamp
        uint16 maxLeverageBps;        // Max leverage for PT-collateral loops (default 310 = 3.1x)
    }

    /// @notice Buy PT tokens to lock in fixed yield
    /// @param syAmount Amount of SY (Standardized Yield) token to convert to PT
    /// @return ptAmount PT tokens received
    /// @return fixedYieldBps Locked-in fixed yield in basis points
    function buyPrincipalToken(
        PendleConfig calldata config,
        uint256 syAmount
    ) external returns (uint256 ptAmount, uint16 fixedYieldBps);

    /// @notice Execute a PT-collateral leverage loop
    /// @dev Buys PT → deposits as Aave collateral (91% LTV) → borrows → reinvests
    /// @param config Pendle market parameters
    /// @param initialAmount Starting capital
    /// @param targetLeverage Desired leverage (must be <= maxLeverageBps)
    /// @return totalPtPosition Total PT position after looping
    /// @return netYieldBps Estimated net yield after borrow costs
    function ptLeverageLoop(
        PendleConfig calldata config,
        uint256 initialAmount,
        uint16 targetLeverage
    ) external returns (uint256 totalPtPosition, uint16 netYieldBps);

    /// @notice Split vault share tokens into PT and YT on Pendle Prime
    /// @param shareAmount Vault shares to tokenize
    /// @return ptAmount Principal Tokens received
    /// @return ytAmount Yield Tokens received
    function splitShareTokens(
        PendleConfig calldata config,
        uint256 shareAmount
    ) external returns (uint256 ptAmount, uint256 ytAmount);

    /// @notice Register vault share token on Pendle Prime (permissionless listing)
    function listOnPendlePrime(address shareToken) external returns (address market);

    event PTBought(address indexed vault, uint256 ptAmount, uint16 fixedYieldBps, uint256 maturity);
    event PTLeverageExecuted(address indexed vault, uint256 totalPosition, uint16 leverage, uint16 netYieldBps);
    event ShareTokensListed(address indexed shareToken, address market);
}
```

**Strategies**:

* **Fixed-rate capture**: Buy PT-sUSDe (locking 12-13% fixed yield), use as Aave collateral (91% LTV for stablecoins), borrow at 5-7%, reinvest. 3.1x leverage yields 26%+ (validated by Term Structure).
* **Yield stripping**: Split vault share tokens into PT (fixed yield buyers) and YT (variable yield speculators) on Pendle Prime (permissionless listing since March 2025). Creates instant fixed/variable yield markets for every vault.
* **Boros integration (deferred)**: Pendle V3 Boros enables funding rate trading. Vault agents could lock in fixed funding rates for predictable depositor yield (5.98-11.4% fixed APR on cross-exchange funding arbitrage). Deferred until Boros deploys on Base.

***

### 10.E4 CreditDelegationAdapter.sol (D-052)

Enables reputation-gated uncollateralized borrowing via Aave V3 credit delegation. High-reputation agents receive delegated borrowing power from vault depositors without posting their own collateral. Creates a trust progression: insurance backing (D-039) → reputation score (D-007) → credit delegation → leverage capability.

```solidity
interface ICreditDelegationAdapter {
    struct DelegationConfig {
        address lendingPool;          // Aave V3 pool on Base
        uint16 minReputationScore;    // Min ERC-8004 score (default 100 = Trusted tier)
        uint16 maxDelegationBps;      // Max % of depositor supply delegatable (default 5000 = 50%)
        uint16 healthFactorFloor;     // Min HF for delegated borrows (default 150 = 1.5)
        bool autoRevokeEnabled;       // Auto-revoke on trigger conditions
    }

    struct AutoRevokeTrigger {
        uint16 reputationDropThreshold;  // Revoke if score drops below this
        uint16 healthFactorThreshold;    // Revoke if HF drops below this
        bool revokeOnInsuranceLapse;     // Revoke if factory insurance coverage lapses
    }

    /// @notice Approve credit delegation from depositor to agent
    /// @param delegatee Agent address receiving borrowing power
    /// @param amount Maximum borrowable amount
    /// @param asset Borrowable asset address
    function approveDelegation(
        DelegationConfig calldata config,
        address delegatee,
        uint256 amount,
        address asset
    ) external;

    /// @notice Agent borrows using delegated credit
    /// @param delegator Address that approved the delegation
    /// @param amount Amount to borrow
    /// @param asset Asset to borrow
    function borrowDelegated(
        address delegator,
        uint256 amount,
        address asset
    ) external;

    /// @notice Revoke delegation (callable by delegator or Sentinel on trigger)
    function revokeDelegation(address delegatee, address asset) external;

    /// @notice Check current delegation status and health
    function getDelegationStatus(
        address delegator,
        address delegatee,
        address asset
    ) external view returns (uint256 approved, uint256 borrowed, uint256 healthFactor);

    event DelegationApproved(address indexed delegator, address indexed delegatee, address asset, uint256 amount);
    event DelegatedBorrow(address indexed delegatee, address indexed delegator, address asset, uint256 amount);
    event DelegationRevoked(address indexed delegator, address indexed delegatee, address asset, string reason);
}
```

**Reputation-weighted collateral tiers** (complements credit delegation):

| Agent Status            | Required Collateral          | Credit Delegation Eligible |
| ----------------------- | ---------------------------- | -------------------------- |
| New (score < 10)        | 150%                         | No                         |
| Basic (score 10-49)     | 130%                         | No                         |
| Verified (score 50-99)  | 120%                         | Limited (25% max)          |
| Trusted (score 100-199) | 100%                         | Yes (50% max)              |
| Sovereign (score 200+)  | 80% (with insurance backing) | Yes (75% max)              |

***

### 10.E5 LiquidityRouter.sol (D-054)

Factory-level contract that enables inter-vault lending of idle capital. Vaults with excess idle reserves lend to vaults needing short-term liquidity (withdrawal spikes, rebalancing) at utilization-curve-determined rates. Extends D-032 (cross-vault coordination) from arbitrage-free rebalancing to capital sharing.

Design precedents: Morpho Blue ($13B deposits in 2025) shared liquidity model, Fluid Protocol's shared liquidity layer.

```solidity
interface ILiquidityRouter {
    struct RouterConfig {
        uint16 maxBorrowBps;         // Max a vault can borrow from peers (default 2000 = 20% of TVL)
        uint256 minIdleReserve;      // Min idle a lending vault must retain
        uint16 baseRateBps;          // Base interest rate at 0% utilization (default 100 = 1%)
        uint16 optimalUtilBps;       // Optimal utilization point (default 8000 = 80%)
        uint16 slopeOneBps;          // Rate slope below optimal (default 400 = 4%)
        uint16 slopeTwoBps;          // Rate slope above optimal (default 7500 = 75%)
        uint32 maxBorrowDuration;    // Max seconds a vault can borrow (default 86400 = 24h)
    }

    /// @notice Vault requests to borrow idle capital from the factory pool
    /// @param amount Amount to borrow
    /// @param duration Expected borrow duration in seconds
    /// @return borrowed Actual amount borrowed (may be less if insufficient idle)
    /// @return rate Current interest rate in bps
    function borrow(
        uint256 amount,
        uint32 duration
    ) external returns (uint256 borrowed, uint16 rate);

    /// @notice Vault repays borrowed capital plus accrued interest
    function repay(uint256 amount) external;

    /// @notice Total idle capital available for lending across all factory vaults
    function totalAvailableLiquidity() external view returns (uint256);

    /// @notice Current utilization rate of the cross-vault lending pool
    function utilizationRate() external view returns (uint16);

    /// @notice Current interest rate based on utilization curve
    function currentRate() external view returns (uint16);

    /// @notice Outstanding borrows for a specific vault
    function vaultBorrows(address vault) external view returns (uint256 principal, uint256 interest, uint32 maturity);

    event InterVaultBorrow(address indexed borrower, uint256 amount, uint16 rate, uint32 duration);
    event InterVaultRepay(address indexed borrower, uint256 principal, uint256 interest);
    event LiquidityAvailable(uint256 totalIdle, uint16 utilizationRate);
}
```

**Key properties**:

* Opt-in: Vaults must enable `interVaultLendingEnabled` in VaultConfig to participate as lenders or borrowers
* Utilization-curve pricing: Follows the Aave/Compound two-slope interest rate model. Rates increase steeply above optimal utilization to incentivize repayment.
* Lending vaults must retain `minIdleReserve` for their own depositor withdrawals — cannot lend all idle capital
* Maximum 24h borrow duration by default; longer borrows require Curator approval
* Factory-level emergency: Sentinel can force-repay all outstanding borrows if systemic stress is detected (links to D-026 adaptive circuit breakers)

***

### 10.E6 BondMMHook.sol (D-055)

V4 hook implementing BondMM-A (arXiv:2512.16080) for fixed-rate lending across arbitrary maturities in a single liquidity pool. Unlike existing fixed-rate protocols that require separate pools per maturity date, BondMM uses a single liquidity pool supporting multiple time horizons (1 week to 1 year).

```solidity
interface IBondMMHook {
    struct BondConfig {
        uint32 minMaturity;           // Minimum maturity in seconds (default 604800 = 1 week)
        uint32 maxMaturity;           // Maximum maturity in seconds (default 31536000 = 1 year)
        uint16 maxUtilizationBps;     // Max pool utilization (default 9000 = 90%)
        uint16 reserveFactorBps;      // Protocol reserve factor (default 1000 = 10%)
    }

    /// @notice Lend at fixed rate for a specific maturity
    /// @param amount Amount to lend
    /// @param maturity Maturity timestamp
    /// @return bondTokens Bond tokens representing the fixed-rate position
    /// @return fixedRateBps Locked-in annual rate in basis points
    function lendFixed(
        uint256 amount,
        uint32 maturity
    ) external returns (uint256 bondTokens, uint16 fixedRateBps);

    /// @notice Borrow at fixed rate for a specific maturity
    /// @param amount Amount to borrow
    /// @param maturity Maturity timestamp
    /// @param collateral Collateral amount posted
    /// @return fixedRateBps Locked-in annual borrow rate
    function borrowFixed(
        uint256 amount,
        uint32 maturity,
        uint256 collateral
    ) external returns (uint16 fixedRateBps);

    /// @notice Redeem matured bond tokens for principal + interest
    function redeemBond(uint256 bondTokens) external returns (uint256 amount);

    /// @notice Current yield curve across supported maturities
    function getYieldCurve() external view returns (uint32[] memory maturities, uint16[] memory ratesBps);

    event FixedRateLend(address indexed lender, uint256 amount, uint32 maturity, uint16 rateBps);
    event FixedRateBorrow(address indexed borrower, uint256 amount, uint32 maturity, uint16 rateBps);
    event BondRedeemed(address indexed holder, uint256 bondTokens, uint256 principal, uint256 interest);
}
```

**AI agent role**: The vault-strategist agent determines optimal maturity allocation based on yield curve shape and depositor duration preferences. DeFi lending ($40B TVL) has only \~$5B in fixed-income products — this addresses a massive underserved market. The single-pool design provides superior capital efficiency versus fragmented per-maturity approaches.

***

## Risk and Governance Contracts

### 10.E7 RiskEngine.sol (D-056)

The RiskEngine is the single on-chain source of truth for all vault risk parameters. It consolidates scattered risk configuration (vault-level parameters, adapter caps, circuit breaker thresholds, oracle freshness) into one queryable contract implementing the ERC-7265 circuit breaker interface.

**Standalone module**: `packages/vault/contracts/src/RiskEngine.sol`

#### v1 RiskEngine Scope (Normative)

The v1 RiskEngine implements only the core risk infrastructure needed for launch. Expansion features (adapter risk profiles, scoring framework D-063, TraceRank integration D-044) are deferred.

**v1 must-have features:**

| Feature                            | Description                                                                | Decision        |
| ---------------------------------- | -------------------------------------------------------------------------- | --------------- |
| Exposure caps                      | Per-asset and per-adapter maximum allocation as bps of `totalAssets`       | D-056           |
| Drawdown thresholds                | Continuous dampening (D-043) -- no binary halts; regime-aware alpha curves | D-026, D-043    |
| ERC-7265 circuit breaker interface | `checkRateLimit()` and `checkAdapterExposure()` called by vault core       | D-056, ERC-7265 |
| Oracle freshness validation        | `checkOracleFreshness()` for all price feeds; auto-pause on staleness      | D-021           |
| Parameter validation               | Bounds checking on all risk parameters; revert on invalid values           | D-059           |
| External composability views       | `getVaultLimits()`, `getAdapterLimits()`, read-only for Morpho/Pendle      | D-056           |
| Share price rate-of-change bounds  | Max `sharePriceMaxIncreaseBps` per unlock period (connects D-018)          | D-062           |

**v1 deferred features (expansion):**

* Adapter risk profiles and `StrategyRiskProfile`/`StrategyRiskState` structs (D-063)
* Risk-adjusted scoring framework with gamma coefficients (D-063)
* Allocation trace artifacts (D-063)
* TraceRank payments-as-endorsements integration (D-044)
* Cross-protocol reputation signaling (D-045)

```solidity
interface IRiskEngine {
    // --- Structs ---

    struct AdapterLimits {
        uint16 maxExposureBps;     // Per-adapter cap (default 2000 = 20%)
        uint16 maxDrawdownBps;     // Per-adapter drawdown trigger (default 500 = 5%)
        uint32 oracleMaxStaleness; // Seconds before oracle considered stale (1800 on L2, 7200 on L1)
        bool forceExitEnabled;     // Whether forceDeallocate() is available for this adapter
        uint16 forceExitPenaltyBps; // Penalty for in-kind exits (50-200 bps)
    }

    struct VaultLimits {
        uint16 maxDrawdownBps;           // Global drawdown trigger (default 1000 = 10%)
        uint16 sharePriceMaxIncreaseBps; // Per-unlock-period max share price increase (default 500 = 5%)
        uint32 navDropWindow;            // Window for NAV drop detection in seconds (default 3600 = 1h)
        uint16 navDropTriggerBps;        // NAV drop within window that triggers pause (default 500 = 5%)
        uint16 maxTotalAdapters;         // Max adapters per vault (default 8)
        uint16 reserveFloorBps;          // Minimum idle reserve (default 1000 = 10%)
    }

    // --- Views ---

    /// @notice Check if an adapter's current exposure is within limits
    function checkAdapterExposure(address vault, address adapter) external view returns (bool allowed);

    /// @notice Check if an oracle's last update is within staleness bounds
    function checkOracleFreshness(address oracle) external view returns (bool fresh);

    /// @notice Get all risk limits for a vault
    function getVaultLimits(address vault) external view returns (VaultLimits memory);

    /// @notice Get risk limits for a specific adapter within a vault
    function getAdapterLimits(address vault, address adapter) external view returns (AdapterLimits memory);

    /// @notice Get current adapter exposure as bps of vault totalAssets
    function getAdapterExposureBps(address vault, address adapter) external view returns (uint16);

    /// @notice ERC-7265: Check if a withdrawal of `amount` would trigger the circuit breaker
    function checkRateLimit(address vault, uint256 amount) external view returns (bool allowed);

    // --- Mutative (timelocked via ParameterDecisionTable) ---

    /// @notice Update adapter limits (requires Curator role + shortTimelock)
    function setAdapterLimits(address vault, address adapter, AdapterLimits calldata limits) external;

    /// @notice Update vault-level limits (requires Owner role + longTimelock)
    function setVaultLimits(address vault, VaultLimits calldata limits) external;

    // --- Events ---

    event AdapterExposureWarning(address indexed vault, address indexed adapter, uint16 currentBps, uint16 maxBps);
    event AdapterExposureBreach(address indexed vault, address indexed adapter, uint16 currentBps);
    event OracleStale(address indexed oracle, uint32 lastUpdate, uint32 maxStaleness);
    event CircuitBreakerTriggered(address indexed vault, uint16 drawdownBps, uint8 tier);
    event VaultLimitsUpdated(address indexed vault, VaultLimits limits);
    event AdapterLimitsUpdated(address indexed vault, address indexed adapter, AdapterLimits limits);
}
```

**Default parameter values by chain profile**:

| Parameter                  | Base (L2)      | Ethereum (L1) | Rationale                                             |
| -------------------------- | -------------- | ------------- | ----------------------------------------------------- |
| `adapterTimelock`          | 24h            | 48h           | User reaction window; governance safety best practice |
| `maxExposureBps`           | 2000 (20%)     | 2000 (20%)    | Limit monitoring complexity                           |
| `oracleMaxStaleness`       | 1800s (30 min) | 7200s (2h)    | Longer block times + oracle update patterns on L1     |
| `forceExitPenaltyBps`      | 100 (1%)       | 200 (2%)      | Higher penalty on L1 where exit liquidity is thinner  |
| `maxDrawdownBps`           | 1000 (10%)     | 1000 (10%)    | Consistent across chains                              |
| `sharePriceMaxIncreaseBps` | 500 (5%)       | 500 (5%)      | Per D-018 profit unlock buffer                        |

**Integration with vault core**: `AgentVaultCore` calls `riskEngine.checkAdapterExposure()` before every `allocate()` and `riskEngine.checkRateLimit()` before every `withdraw()`. The RiskEngine is set at factory deployment and cannot be changed for existing vaults (immutable reference).

**External queryability**: The RiskEngine exposes read-only views that external protocols (Morpho, Pendle, Credora) can call to assess vault risk before accepting share tokens as collateral. This makes vault risk composable infrastructure rather than opaque metadata.

**Adapter Risk Reporting (D-063)**: Each adapter registered in the adapter registry exposes standardized risk metadata consumed by the RiskEngine:

```solidity
/// @notice Static risk profile set at adapter registration (updated via Curator with timelock)
struct StrategyRiskProfile {
    ExitLatencyClass exitLatencyClass;  // Instant, Hours, Days
    OracleDependency oracleDependency;  // None, Single, Multi
    uint16 maxLeverage;                 // Max supported leverage (10000 = 1x, 50000 = 5x)
    bool forceExitSupported;            // Whether forceDeallocate() is available
    uint40 auditTimestamp;              // Last audit date (unix timestamp)
}

enum ExitLatencyClass { Instant, Hours, Days }
enum OracleDependency { None, Single, Multi }

/// @notice Dynamic risk state updated per report cycle
struct StrategyRiskState {
    uint16 currentUtilization;  // bps of adapter capacity used (0-10000)
    uint16 realizedDrawdown;    // bps, trailing 30d max drawdown
    bool oracleFreshFlag;       // true if all required feeds are fresh
    uint16 currentLeverage;     // actual leverage ratio (10000 = 1x)
}

interface IStrategyAdapter {
    function getRiskProfile() external view returns (StrategyRiskProfile memory);
    function getRiskState() external view returns (StrategyRiskState memory);
}
```

The RiskEngine aggregates adapter risk data for the scoring framework (D-063) and exposes it to external protocols.

**Health Attestations (D-066)**: The RiskEngine emits periodic health snapshots and provides a queryable attestation view:

```solidity
/// @notice Periodic health snapshot emitted every `healthSnapshotCadence` blocks
event VaultHealthSnapshot(
    address indexed vault,
    uint256 totalAssets,
    uint256 sharePrice,
    uint16 idleRatioBps,
    uint8 adapterCount,
    bool oraclesFresh,
    uint256 timestamp
);

/// @notice On-chain health attestation queryable by external protocols
/// @return healthy True if all invariants pass
/// @return attestationBlock Block number of the last attestation
/// @return details Bitmask: bit0=totalAssets_consistent, bit1=sharePrice_within_clamp,
///                 bit2=oracles_fresh, bit3=no_adapter_above_cap, bit4=idle_above_floor,
///                 bit5=no_active_tier2_breaker
function getHealthAttestation(address vault)
    external view returns (bool healthy, uint256 attestationBlock, uint256 details);
```

The `healthSnapshotCadence` defaults to 300 blocks (\~10 min on Base). External protocols call `getHealthAttestation()` before accepting vault shares as collateral — a failed attestation signals degraded vault state.

### 10.E8 ParameterDecisionTable.sol — Expansion Features (D-059)

On-chain registry mapping each vault parameter to its governance constraints: who can change it, how long the timelock is, what the valid bounds are, and a justification hash linking to off-chain documentation.

**Standalone module**: `packages/vault/contracts/src/ParameterDecisionTable.sol`

```solidity
interface IParameterDecisionTable {
    struct ParameterEntry {
        bytes32 paramId;           // keccak256 of parameter name (e.g., keccak256("maxDrawdownBps"))
        address authorityRole;     // Role address that can propose changes
        uint32 timelockDuration;   // Delay before change takes effect (seconds)
        uint256 minValue;          // Lower bound (revert if proposed value below)
        uint256 maxValue;          // Upper bound (revert if proposed value above)
        bytes32 justificationHash; // IPFS hash of risk memo explaining current value
    }

    struct ScheduledChange {
        bytes32 paramId;
        uint256 newValue;
        uint256 executionTime;     // block.timestamp + timelockDuration
        address proposer;
        bytes32 newJustificationHash;
        bool executed;
        bool cancelled;
    }

    /// @notice Propose a parameter change (starts timelock)
    function proposeChange(
        address vault,
        bytes32 paramId,
        uint256 newValue,
        bytes32 justificationHash
    ) external returns (uint256 changeId);

    /// @notice Execute a scheduled change after timelock elapses
    function executeChange(uint256 changeId) external;

    /// @notice Cancel a scheduled change (Guardian/Sentinel role)
    function cancelChange(uint256 changeId) external;

    /// @notice Get the governance entry for a parameter
    function getParameterEntry(address vault, bytes32 paramId) external view returns (ParameterEntry memory);

    /// @notice Get all pending scheduled changes for a vault
    function getPendingChanges(address vault) external view returns (ScheduledChange[] memory);

    event ChangeProposed(address indexed vault, bytes32 indexed paramId, uint256 newValue, uint256 executionTime);
    event ChangeExecuted(address indexed vault, bytes32 indexed paramId, uint256 oldValue, uint256 newValue);
    event ChangeCancelled(address indexed vault, uint256 indexed changeId, address cancelledBy);
}
```

**Default timelock durations**:

| Parameter Category                                 | Timelock                 | Authority                      | Examples                                                      |
| -------------------------------------------------- | ------------------------ | ------------------------------ | ------------------------------------------------------------- |
| Strategy parameters (small caps, fee adjustments)  | 12-24h (`shortTimelock`) | Curator (am-AMM winner)        | `feeBaseBps`, `minIdleBps`, adapter allocation weights        |
| Adapter add/remove, hook changes, custody controls | 3-7d (`longTimelock`)    | Owner (DAO/multisig)           | `approvedAdapters[]`, `hookEnabled`, `rehypothecationEnabled` |
| Emergency pause/unpause                            | 0s (immediate)           | Sentinel                       | `paused` flag                                                 |
| Emergency unpause after pause                      | 24h minimum cooldown     | Owner + Curator (two-man rule) | Unpause after investigation                                   |

**Guardian veto**: The Guardian/Sentinel role can cancel any scheduled change during the timelock window. The veto window equals the timelock duration. This ensures depositors and monitoring agents have full visibility into upcoming changes and the ability to exit before changes take effect.

### 10.E9 Hook Kill-Switch Extension (D-058)

Extension to all hook contracts deployed by the vault factory. Provides runtime emergency bypass of hook logic.

```solidity
/// @notice Added to VaultHook, NAVAwareHook, LaunchFeeHook, and all factory-deployed hooks
abstract contract HookKillSwitch {
    bool public hookDisabled;

    event HookDisabled(address indexed caller, uint256 timestamp);
    event HookEnabled(address indexed caller, uint256 timestamp);

    /// @notice Sentinel-only: immediately disable all hook logic
    /// @dev Pool continues as standard V4 pool without custom callbacks
    modifier onlySentinel() {
        require(msg.sender == sentinel, "HookKillSwitch: not sentinel");
        _;
    }

    function disableHook() external onlySentinel {
        hookDisabled = true;
        emit HookDisabled(msg.sender, block.timestamp);
    }

    /// @notice Re-enable requires Owner + Curator approval via ParameterDecisionTable
    /// @dev Subject to longTimelock (3-7 days) to prevent premature re-activation
    function enableHook(uint256 changeId) external {
        // Validates changeId references an executed ParameterDecisionTable change
        // with paramId = keccak256("hookDisabled") and newValue = 0
        require(!hookDisabled || _validateEnableApproval(changeId), "HookKillSwitch: not approved");
        hookDisabled = false;
        emit HookEnabled(msg.sender, block.timestamp);
    }

    /// @notice Guard applied to all hook callback functions
    modifier whenHookActive() {
        if (hookDisabled) return;
        _;
    }
}
```

**Impact when hook is disabled**:

| Hook                   | Normal Behavior                    | Disabled Behavior                 |
| ---------------------- | ---------------------------------- | --------------------------------- |
| NAVAwareHook           | Prices shares at NAV ± spread      | Standard constant-product pricing |
| LaunchFeeHook          | Descending-fee MEV protection      | Standard base fee                 |
| DynamicFeeEngine       | Three-regime adaptive fees         | Static base fee                   |
| RehypothecationAdapter | Routes idle liquidity to lending   | Liquidity remains idle in pool    |
| VaultHook              | Agent-gated swaps + yield wrapping | Standard ungated swaps            |

Depositors can still trade shares and withdraw in all cases. The disabled state is strictly more conservative (less yield, but also less attack surface).

***

### 10.E10 RebalanceIntentModule.sol (D-064)

Intent-based solver competition for large vault rebalances. Complements TWAMM (time-splitting) and FM-AMM batch clearing (D-035) with a competitive execution quality layer where permissionless solvers bid to execute rebalances with minimal slippage.

**Standalone module**: `packages/vault/contracts/src/RebalanceIntentModule.sol`

```solidity
interface IRebalanceIntentModule {
    struct RebalanceIntent {
        address vault;
        bytes32 targetStateHash;     // keccak256 of target position ranges/weights
        uint16 maxSlippageBps;       // Max acceptable slippage (default 30)
        address[] allowedRoutes;     // Optional whitelist of swap routes (empty = any)
        uint48 deadline;             // Block number after which intent expires
        bool twammFallback;          // If true, fall back to TWAMM if no valid winner
        uint32 twammDuration;        // TWAMM duration in seconds (if fallback enabled)
    }

    struct SolverBid {
        bytes32 commitHash;          // keccak256(calldata, expectedOutputs, fee, salt)
        uint256 bond;                // Anti-grief deposit
        address solver;
    }

    struct SolverReveal {
        bytes executionCalldata;     // Actual swap/position calldata
        uint256[] expectedOutputs;   // Min output amounts
        uint256 solverFee;           // Fee requested by solver
        bytes32 salt;
    }

    // --- Intent lifecycle ---

    /// @notice Manager posts a rebalance intent (requires Curator/Allocator role)
    function postIntent(RebalanceIntent calldata intent) external returns (uint256 intentId);

    /// @notice Solvers submit sealed bids during auction window
    function submitBid(uint256 intentId, bytes32 commitHash) external payable;

    /// @notice Solvers reveal bids after auction window closes
    function revealBid(uint256 intentId, SolverReveal calldata reveal) external;

    /// @notice Execute the winning bid (permissionless after reveal window)
    function executeWinner(uint256 intentId) external;

    /// @notice Cancel intent (Curator only, before execution)
    function cancelIntent(uint256 intentId) external;

    // --- Events ---

    event RebalanceIntentPosted(uint256 indexed intentId, address indexed vault, bytes32 targetStateHash, uint48 deadline);
    event SolverBidSubmitted(uint256 indexed intentId, address indexed solver, bytes32 commitHash, uint256 bond);
    event SolverBidRevealed(uint256 indexed intentId, address indexed solver, uint256 expectedSurplus, uint256 fee);
    event RebalanceExecuted(uint256 indexed intentId, address indexed solver, uint256 surplusBps, uint256 gasUsed);
    event RebalanceFallbackToTWAMM(uint256 indexed intentId, uint256 twammOrderId);
    event IntentCancelled(uint256 indexed intentId, address indexed canceller);
}
```

| Parameter                   | Default         | Range  | Description                                          |
| --------------------------- | --------------- | ------ | ---------------------------------------------------- |
| `auctionDurationBlocks`     | 3 (L2) / 1 (L1) | 1-10   | Blocks for commit phase                              |
| `revealWindowBlocks`        | 2 (L2) / 1 (L1) | 1-5    | Blocks for reveal phase after commit                 |
| `defaultSlippageBps`        | 30              | 10-100 | Default max slippage if not specified in intent      |
| `minSolverBond`             | 0.01 ETH        | —      | Minimum anti-grief bond                              |
| `minRebalanceBpsForAuction` | 100 (1% of TVL) | 50-500 | Smaller rebalances bypass auction (direct execution) |
| `invalidRevealSlashBps`     | 10000 (100%)    | —      | Bond slashed on invalid reveal (wrong hash)          |
| `failedExecutionSlashBps`   | 5000 (50%)      | —      | Bond slashed on reverted execution                   |

**Winner selection**: Best valid bid is the one that maximizes `expectedOutputs - solverFee` (net surplus to vault) subject to: all calldata targets are on the vault's allowlisted contracts, min-out constraints met, no `delegatecall`, and within `maxSlippageBps`.

**Fallback behavior**: If no valid bid after reveal window, and `twammFallback` is true, the module automatically submits a TWAMM order with the specified duration. If `twammFallback` is false, the intent is rescheduled with an extended deadline and increased solver incentive (Dutch auction escalation per D-061 pattern).

**Forward compatibility**: Solvers use the `IExecutable` interface (D-061), making the module compatible with the Permissionless Executor Framework and the future bonded ExecutionMarket (D-057).

***

## Time-Delayed Proxy Contracts (`packages/agent-proxy/`)

The following contracts are part of the standalone `packages/agent-proxy/` module. They are independently deployable and usable with any AI agent wallet -- they do not depend on the vault protocol. The vault composes them as a role-dependent safety layer (required for manager/admin classes, optional for low-value participant flows).

### 10.12 AgentProxy.sol

Adapts Polkadot's proxy pattern for EVM. Combines the best ideas from OpenZeppelin TimelockController (role-based access, hash-based operation IDs), Zodiac Delay Modifier (FIFO queue, expiration), and Polkadot's `pallet_proxy` (proxy types, announcement deposits, cancel-type authority).

**Standalone module**: `packages/agent-proxy/contracts/src/AgentProxy.sol`

```solidity
contract AgentProxy is AccessControl, ReentrancyGuard {
    bytes32 public constant AGENT_ROLE = keccak256("AGENT_ROLE");
    bytes32 public constant CANCEL_ROLE = keccak256("CANCEL_ROLE");

    enum ProxyType { Any, Transfer, DeFiSwap, Governance, Staking }

    struct ProxyConfig {
        ProxyType proxyType;
        uint48 delay;         // seconds
        uint48 expiration;    // seconds after delay (0 = no expiry)
        bool active;
    }

    struct Announcement {
        address agent;
        address target;
        uint256 value;
        bytes data;
        uint48 announceTime;
        uint48 delay;
        uint48 expiration;
        bool executed;
        bool cancelled;
    }
}
```

**Key functions:**

| Function                                                | Caller               | Description                                                  |
| ------------------------------------------------------- | -------------------- | ------------------------------------------------------------ |
| `configureAgent(address, ProxyType, delay, expiration)` | `DEFAULT_ADMIN_ROLE` | Configure agent permissions, proxy type, and delay           |
| `whitelistCall(ProxyType, target, selector)`            | `DEFAULT_ADMIN_ROLE` | Allow a specific (target, selector) pair for a proxy type    |
| `whitelistTarget(ProxyType, target)`                    | `DEFAULT_ADMIN_ROLE` | Allow all selectors for a target contract                    |
| `announce(target, value, data)`                         | `AGENT_ROLE`         | Submit a time-delayed transaction announcement               |
| `execute(txId)`                                         | Anyone               | Execute an announcement after delay has elapsed              |
| `cancel(txId)`                                          | `CANCEL_ROLE`        | Veto a pending announcement                                  |
| `cancelAll(fromId, toId)`                               | `CANCEL_ROLE`        | Batch cancel range of announcements (emergency)              |
| `removeAnnouncement(txId)`                              | Announcing agent     | Agent cancels their own pending announcement                 |
| `isExecutable(txId)`                                    | Anyone (view)        | Check if an announcement is ready for execution              |
| `getPendingCount()`                                     | Anyone (view)        | Count of pending (non-executed, non-cancelled) announcements |

**Operational role mapping:**

| Responsibility | Actor                                                                                           | Required Key Policy                                        |
| -------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Announce       | Agent execution key (`AGENT_ROLE`)                                                              | Can only call `announce()`                                 |
| Execute        | Any executor — permissionless (D-061, Section 10.1b). `vault-executor` agent is canonical role. | Execute-only, no config access. Gas refund + tip rewarded. |
| Cancel         | Cancel authority (`CANCEL_ROLE`)                                                                | Cancel-only (`cancel`, `cancelAll`)                        |
| Configure      | Admin (`DEFAULT_ADMIN_ROLE`)                                                                    | Restricted admin key, ideally multisig                     |

**Call filtering**: The `_isCallAllowed()` internal function enforces proxy type restrictions. `ProxyType.Any` bypasses filtering. All other types check `allowedTargets[proxyType][target]` (wildcard) then `allowedCalls[proxyType][target][selector]` (specific). Plain ETH transfers to unrecognized addresses are rejected.

**Events:**

```solidity
event TransactionAnnounced(
    uint256 indexed txId, address indexed agent, address target,
    uint256 value, bytes data, bytes32 callHash, uint256 executeAfter
);
event TransactionExecuted(uint256 indexed txId, bool success);
event TransactionCancelled(uint256 indexed txId, address cancelledBy);
event AgentConfigured(address indexed agent, ProxyType proxyType, uint48 delay, uint48 expiration);
event CallWhitelisted(ProxyType proxyType, address target, bytes4 selector);
```

**Design decisions:**

* Minimum 60-second delay enforced at the contract level
* `CANCEL_ROLE` is separate from `DEFAULT_ADMIN_ROLE` -- a cancel authority cannot reconfigure the proxy
* Agent can cancel their own announcements via `removeAnnouncement()` (does not require `CANCEL_ROLE`)
* Announcements store full calldata on-chain (not just hash) to enable monitoring bot evaluation
* The contract holds ETH/tokens and executes transactions against its own balance
* Required-proxy operation classes are enforced fail-closed at the policy layer when monitoring/cancel infrastructure is degraded

**Gas targets:**

* `announce()`: < 80k gas
* `execute()`: < 50k gas + underlying call gas
* `cancel()`: < 30k gas

***

### 10.13 AgentProxyFactory.sol

Factory contract for deploying AgentProxy instances with sensible defaults.

**Standalone module**: `packages/agent-proxy/contracts/src/AgentProxyFactory.sol`

```solidity
contract AgentProxyFactory {
    event ProxyDeployed(
        address indexed proxy, address indexed owner,
        address indexed agent, address cancelAuthority
    );

    struct DefaultConfig {
        address[] defiTargets;    // e.g., Uniswap Router, Aave Pool
        bytes4[] defiSelectors;   // e.g., swap(), supply(), borrow()
        uint48 defaultDelay;      // e.g., 3600 (1 hour)
        uint48 defaultExpiration; // e.g., 86400 (24 hours)
    }

    function deployProxy(
        address owner,
        address agent,
        address cancelAuthority,
        AgentProxy.ProxyType proxyType,
        uint48 delay,
        uint48 expiration,
        bool useDefaults
    ) external returns (address);

    function deployProxyDeterministic(
        address owner,
        address agent,
        address cancelAuthority,
        bytes32 salt
    ) external returns (address);

    function computeAddress(
        address owner,
        address cancelAuthority,
        bytes32 salt
    ) external view returns (address);
}
```

**Default DeFi whitelists** are provided for common integrations:

| Preset              | Targets                                                     | Selectors                                         |
| ------------------- | ----------------------------------------------------------- | ------------------------------------------------- |
| `defi-trading`      | Uniswap Universal Router, Uniswap V4 PoolManager            | `execute()`, `swap()`, `modifyLiquidity()`        |
| `vault-participant` | AgentVaultCore, USDC                                        | `deposit()`, `withdraw()`, `approve()`            |
| `vault-manager`     | AgentVaultCore, CCABidAdapter, PoolManager, PositionManager | All vault strategy methods                        |
| `governance`        | Governor, Timelock                                          | `castVote()`, `propose()`, `queue()`, `execute()` |

**CLI deployment** is supported via the `packages/agent-proxy/` npm package:

```bash
npx agent-proxy deploy \
  --owner 0xOwner... \
  --agent 0xAgent... \
  --cancel-authority 0xMonitor... \
  --delay 3600 \
  --chain base \
  --preset defi-trading
```

***

### 10.14 IMonitoringOracle.sol

Optional interface for plugging external monitoring services into the cancel authority decision pipeline. A monitoring oracle evaluates pending announcements and returns a decision. The monitoring bot can query one or more oracles before making its cancel/allow decision.

**Standalone module**: `packages/agent-proxy/contracts/src/interfaces/IMonitoringOracle.sol`

```solidity
interface IMonitoringOracle {
    enum Decision { ALLOW, REVIEW, CANCEL }

    /// @notice Evaluate a pending announcement
    /// @param txId The announcement ID
    /// @param agent The announcing agent address
    /// @param target The target contract of the announced transaction
    /// @param value The ETH value of the announced transaction
    /// @param data The calldata of the announced transaction
    /// @return decision The oracle's recommendation
    function evaluateAnnouncement(
        uint256 txId,
        address agent,
        address target,
        uint256 value,
        bytes calldata data
    ) external view returns (Decision decision);

    /// @notice Batch evaluate multiple announcements
    function evaluateBatch(
        uint256[] calldata txIds,
        address[] calldata agents,
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata datas
    ) external view returns (Decision[] memory decisions);
}
```

**Use cases for monitoring oracles:**

* **Whitelist oracle**: Checks target addresses against an on-chain registry of approved contracts
* **Value oracle**: Evaluates transaction value against configurable thresholds per agent
* **Pattern oracle**: Detects anomalous transaction patterns (frequency, timing, recipient changes)
* **Simulation oracle**: Runs Tenderly simulation and returns CANCEL if unexpected state changes detected

Oracles are composable -- the monitoring bot can query multiple oracles and use a configurable policy (e.g., cancel if any oracle returns CANCEL, or cancel only if majority returns CANCEL).
