# Vault contracts

The ERC-7540 vault interface, fees, roles and events, plus the three contracts that hide its two step flow.

## Why ERC-7540

The vaults implement **ERC-7540**, an extension of ERC-4626 that adds asynchronous deposits and redeems. ERC-4626 vaults settle deposits and withdrawals in one synchronous call, which limits their ability to:

- Execute crosschain strategies
- Integrate real-world assets (RWA) that need settlement time
- Unwind positions as part of risk management
- Support institutional compliance requirements

ERC-7540 replaces the single call with two steps:

```
Deposit:  requestDeposit() → [wait for fulfillment] → claimDeposit()
Redeem:   requestRedeem()  → [wait for fulfillment] → claimRedeem()
```

## Deployed vaults

The user facing vaults are **six per-tier EIP-2535 Diamonds** (ERC-7540 async) on Base Sepolia (chainId 84532).

- Denominated in **tUSDC**, a mintable test USDC available from a faucet.
- Each tier is its own single-vault Diamond.
- Every vault has **two buckets**: a principal protected **Floor** bucket and an **Active Trading** bucket.
- The addresses below are the dApp configuration set.
- The vaults hold seeded test funds. No capital has been deployed to strategies yet.

| Vault | Address | Risk | Strategy Profile |
|-------|---------|------|------------------|
| Conservative | `0x49f747BBac988Cd628a5D79AAdF865EBaDe6a34f` | Low | Capital preservation |
| Moderate | `0x4B183AfF072C7DFf09bc8eB4B7f59eB3D030EEdc` | Medium | Balanced growth |
| Aggressive | `0x1444Fb96961fD9b8Fc14Aa4D9A7198465b7d8CA3` | High | Maximum growth |
| Hedged | `0xDDa7655AD3c53f0F7EC5CCbc346c18baB53C5a92` | Low | Downside-protected |
| Inverse | `0xE2E4d181f2eAd5B380026Ab71F49FEBCb93f641c` | High | Inverse/short exposure |
| Configurable | `0x53A2EfFaDE21055b8f81e1dE96dC5cfc29B75a83` | Medium | DAO-configurable |

Each vault has its own target APY band, listed in [Core Concepts](../../introduction/core-concepts.md#vaults). The bands are targets, not guaranteed, and no capital is deployed on testnet.

The **8–28%** range is the *combined* vault plus node target, available when a depositor also stakes NODR and runs a node. Node rewards are a revenue share, not a fixed APY. It is never a vault only figure. Higher APYs cited for individual underlying protocols reflect **external protocol potential**, not Noderr vault depositor yield.

## ERC-7540 interface

### Request deposit

```solidity
function requestDeposit(
    uint256 assets,
    address controller,
    address owner
) external returns (uint256 requestId);
```

Starts an async deposit request. Assets move from the owner to the vault.

**Parameters:**

- `assets`: Amount of underlying tokens to deposit
- `controller`: Address that can claim the deposit (usually same as owner)
- `owner`: Address that owns the assets being deposited

**Returns:** `requestId` - Unique identifier for tracking the request

### Claim deposit

```solidity
function claimDeposit(
    uint256 assets,
    address receiver,
    address controller
) external returns (uint256 shares);
```

Claims shares after a deposit request has been fulfilled.

**Parameters:**

- `assets`: Amount of assets to claim shares for
- `receiver`: Address to receive the vault shares
- `controller`: Address that controls the request

**Returns:** `shares` - Number of vault shares minted

### Request redeem

```solidity
function requestRedeem(
    uint256 shares,
    address controller,
    address owner
) external returns (uint256 requestId);
```

Starts an async redeem request. Shares move from the owner to the vault.

### Claim redeem

```solidity
function claimRedeem(
    uint256 shares,
    address receiver,
    address controller
) external returns (uint256 assets);
```

Claims underlying assets after a redeem request has been fulfilled.

### Status functions

```solidity
// Check pending deposit amount
function pendingDepositRequest(uint256 requestId, address controller) 
    external view returns (uint256);

// Check claimable deposit amount
function claimableDepositRequest(uint256 requestId, address controller) 
    external view returns (uint256);

// Check pending redeem amount
function pendingRedeemRequest(uint256 requestId, address controller) 
    external view returns (uint256);

// Check claimable redeem amount
function claimableRedeemRequest(uint256 requestId, address controller) 
    external view returns (uint256);
```

### Operator management

```solidity
// Set an operator who can claim on your behalf
function setOperator(address operator, bool approved) external returns (bool);

// Check if an address is an operator for a controller
function isOperator(address controller, address operator) external view returns (bool);
```

## Vault configuration

Values are read onchain from all six vaults:

| Parameter | Description | Value |
|-----------|-------------|----------------|
| `fulfillmentDelay` | Minimum time before requests can be fulfilled | 0 (no minimum wait) |
| `minDeposit` | Minimum deposit amount | 0 (no minimum) |
| `managementFee` | Annual management fee | 0% (every vault) |
| `performanceFee` | High-water-mark fee on profits, charged only from the Trading bucket | 10% to 15% on the six deployed vaults (see Fee Structure) |
| `hurdleRate` | Per-vault soft hurdle return that must be exceeded before the performance fee applies | 4% to 8% on the six deployed vaults (see Fee Structure) |

## Async flow

```
User                    Vault                   Keeper/Fulfiller
  |                       |                           |
  |-- requestDeposit() -->|                           |
  |<-- requestId ---------|                           |
  |                       |                           |
  |        [assets held in vault, request pending]    |
  |                       |                           |
  |                       |<-- fulfillDeposit() ------|
  |                       |   (after delay elapsed)   |
  |                       |                           |
  |-- claimDeposit() ---->|                           |
  |<-- shares ------------|                           |
  |                       |                           |
```

## Integration examples

### JavaScript/TypeScript with viem

```typescript
import { parseUnits } from 'viem';

// Request a deposit
const requestId = await vaultContract.write.requestDeposit([
  parseUnits('1000', 18),  // 1000 tUSDC (underlying asset, 18 decimals)
  userAddress,              // controller
  userAddress               // owner
]);

// Check if claimable (after fulfillment)
const claimable = await vaultContract.read.claimableDepositRequest([
  requestId,
  userAddress
]);

// Claim shares
if (claimable > 0n) {
  const shares = await vaultContract.write.claimDeposit([
    claimable,
    userAddress,  // receiver
    userAddress   // controller
  ]);
}
```

### Solidity

```solidity
import "./interfaces/IERC7540.sol";

contract VaultIntegration {
    IERC7540 public vault;
    
    // This contract holds the tUSDC, so it is the owner and controller of the request
    function depositToVault(uint256 amount) external {
        IERC20(vault.asset()).transferFrom(msg.sender, address(this), amount);
        IERC20(vault.asset()).approve(address(vault), amount);
        vault.requestDeposit(amount, address(this), address(this));
    }
    
    function claimFromVault() external {
        uint256 claimable = vault.claimableDepositRequest(0, address(this));
        require(claimable > 0, "Nothing to claim");
        vault.claimDeposit(claimable, address(this), address(this));
    }
}
```

## Fees

Every vault charges **0% management fee**. The only fee is a **performance fee**:

- **High-water mark based.** It is charged only when NAV per share rises above its prior peak. A depositor is never charged twice for the same gains.
- **Taken strictly from the Trading bucket.** The principal protected Floor bucket is never touched.
- **Subject to a per-vault soft hurdle.** It applies only on returns above the vault's hurdle rate.

| Vault | Performance Fee | Soft Hurdle |
|-------|-----------------|-------------|
| Conservative | **10%** | 4% |
| Moderate | **10%** | 6% |
| Aggressive | **15%** | 8% |
| Hedged | **12%** | 4% |
| Inverse | **15%** | 8% |
| Configurable | **10%** | 6% |
| PredictionMarkets | **25%** | 15% |

**PredictionMarkets** is planned and **not yet deployed**. Its parameters are shown for reference.

Performance fees go to the protocol treasury wallet. Every vault's `feeCollector()` returns that wallet.

| Fee Type | Rate | Description |
|----------|------|-------------|
| Management Fee | 0% | No annual management fee, on every vault |
| Deposit Fee | 0% | No fee to deposit |
| Withdrawal Fee | 0% | No fee to withdraw |

**Example (Moderate vault), illustrative:**

- User deposits: 10,000 tUSDC
- The Trading bucket gains enough to lift NAV/share to a new high-water mark. Net of the 6% soft hurdle, fee eligible profit is 1,000 tUSDC
- Performance fee (10% of the fee eligible profit): 100 tUSDC
- Net return to user: 10,900 tUSDC

## Security

### Access control

| Role | Permissions |
|------|-------------|
| `DEFAULT_ADMIN_ROLE` | Grant/revoke roles, emergency functions |
| `FULFILLER_ROLE` | Call fulfillDeposit/fulfillRedeem |
| `STRATEGY_MANAGER_ROLE` | Update strategy allocations |
| `PAUSER_ROLE` | Pause/unpause vault operations |

`DEFAULT_ADMIN_ROLE` and upgrade authority resolve to a **MultiSigTimelock (7-day delay)** driven by a **2-of-3 Gnosis Safe**. There is no offchain (e.g. Snapshot) governance path. See [Governance](../governance/README.md).

### Emergency functions

```solidity
// Pause all vault operations
function pause() external onlyRole(PAUSER_ROLE);

// Unpause vault operations
function unpause() external onlyRole(PAUSER_ROLE);
```

The vaults have no `emergencyWithdraw` function.

### Audit status

The ERC-7540 vault contracts pass 24/24 internal tests. They are internally audited. An independent third-party audit is planned for Q1 2027.

## Events

```solidity
// Emitted when a deposit is requested
event DepositRequest(
    address indexed controller,
    address indexed owner,
    uint256 indexed requestId,
    address sender,
    uint256 assets
);

// Emitted when a deposit is claimed
event DepositClaim(
    address indexed controller,
    address indexed receiver,
    uint256 indexed requestId,
    uint256 assets,
    uint256 shares
);

// Emitted when a redeem is requested
event RedeemRequest(
    address indexed controller,
    address indexed owner,
    uint256 indexed requestId,
    address sender,
    uint256 shares
);

// Emitted when a redeem is claimed
event RedeemClaim(
    address indexed controller,
    address indexed receiver,
    uint256 indexed requestId,
    uint256 shares,
    uint256 assets
);

// Emitted when an operator is set
event OperatorSet(
    address indexed controller,
    address indexed operator,
    bool approved
);
```

## Vault UX contracts

Three contracts hide ERC-7540's two step flow: VaultRouter, AutoClaimKeeper and InstantLiquidityPool. Their addresses are in [Contract Addresses](./contract-addresses.md#vault-ux-contracts). The fee, premium and gas figures here are testnet parameters and can change.

| Contract | Purpose | Key Benefit |
|----------|---------|-------------|
| **VaultRouter** | Bundled UX with auto-claim | Single-transaction experience |
| **AutoClaimKeeper** | Keeper auto-claim service | Hands-free claim execution |
| **InstantLiquidityPool** | Instant liquidity AMM | Skip the wait entirely |

## VaultRouter

The main entry point for users. It handles request submission, fulfillment monitoring and claim execution, so the user acts once.

- **Auto-claim:** claims run automatically when a request is fulfilled
- **Batch operations:** deposit or redeem across several vaults in one transaction
- **Gasless transactions:** EIP-712 signatures
- **Slippage protection:** configurable minimum output
- **Emergency controls:** pause for security incidents

```
User → VaultRouter → ERC-7540 Vault
           ↓
    AutoClaimKeeper
           ↓
    Automatic Claim
```

### Request deposit with auto-claim

```solidity
function requestDeposit(
    address vault,
    uint256 assets,
    uint256 minShares,
    address receiver,
    bool autoClaim
) external returns (uint256 requestId);
```

**Parameters:**

- `vault`: The ERC-7540 vault address
- `assets`: Amount of underlying tokens to deposit
- `minShares`: Minimum shares to receive (slippage protection)
- `receiver`: Address to receive the vault shares
- `autoClaim`: Enable automatic claim when fulfilled (0.1% fee)

```typescript
// Deposit 1000 tUSDC with auto-claim enabled
await vaultRouter.requestDeposit(
  conservativeVaultAddress,
  parseUnits('1000', 18),
  parseUnits('990', 18),  // Accept up to 1% slippage
  userAddress,
  true  // Enable auto-claim
);
```

### Batch deposit

```solidity
function batchDeposit(
    BatchDepositParams[] calldata params,
    address receiver,
    bool autoClaim
) external;

struct BatchDepositParams {
    address vault;
    uint256 assets;
    uint256 minShares;
}
```

Deposits to several vaults in one transaction, saving 50-70% on gas costs.

```typescript
// Deposit to 3 vaults at once
await vaultRouter.batchDeposit([
  { vault: conservativeVault, assets: parseUnits('500', 18), minShares: parseUnits('495', 18) },
  { vault: moderateVault, assets: parseUnits('300', 18), minShares: parseUnits('297', 18) },
  { vault: aggressiveVault, assets: parseUnits('200', 18), minShares: parseUnits('196', 18) }
], userAddress, true);
```

### Gasless deposit (EIP-712)

```solidity
function depositWithPermit(
    address vault,
    uint256 assets,
    uint256 minShares,
    address receiver,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external returns (uint256 requestId);
```

Users sign a message offchain. Relayers submit the transaction and pay gas.

### Router fees

| Service | Fee | Description |
|---------|-----|-------------|
| Standard Request | 0% | No fee for manual claim |
| Auto-Claim | 0.1% | Deducted from claimed amount |
| Gasless | 2%+ | Covers gas + relayer profit |

### Router configuration

| Parameter | Default | Description |
|-----------|---------|-------------|
| `defaultTimeout` | 1 hour | Request expiration time |
| `autoClaimFeeBps` | 10 (0.1%) | Fee for auto-claim service |

## AutoClaimKeeper

Keeper nodes earn rewards by executing auto-claims for users. Rewards scale with the keeper's TrustFingerprint.

- **Registration:** stake NODR to become a keeper
- **Batch execution:** process several claims at once
- **Performance tracking:** success rate, gas usage, rewards
- **Slashing:** penalties for failed executions

### Become a keeper

```solidity
function registerKeeper(uint256 stake) external payable;
```

**Requirements:**

- Minimum TrustFingerprint: `MIN_TRUST_SCORE = 700` on a 0 to 10,000 scale
- Stake: any amount sent as `msg.value` (no enforced minimum)

### Execute claims

```solidity
// Execute single deposit claim
function executeDepositClaim(
    address user,
    address vault,
    address receiver
) external;

// Execute single redeem claim
function executeRedeemClaim(
    address user,
    address vault,
    address receiver
) external;

// Batch execute multiple claims
function batchExecuteClaims(
    ClaimParams[] calldata deposits,
    ClaimParams[] calldata redeems
) external;
```

### Reward calculation

```
Base Reward = claimValue × baseRewardBps / 10000
Trust Bonus = Base Reward × (trustScore / 100) × trustBonusBps / 10000
Total Reward = Base Reward + Trust Bonus
```

**Example:**

- Claim value: 10,000 tUSDC
- Base reward rate: 0.1%
- TrustFingerprint: 0.85
- Trust bonus rate: 0.05%

```
Base Reward = 10,000 × 0.001 = 10 tUSDC
Trust Bonus = 10 × 0.85 × 0.0005 = 0.00425 tUSDC
Total Reward = 10.00425 tUSDC
```

### Keeper metrics

```solidity
struct KeeperMetrics {
    uint256 totalClaims;      // Total claims executed
    uint256 successfulClaims; // Successful executions
    uint256 failedClaims;     // Failed executions
    uint256 totalGasUsed;     // Cumulative gas usage
    uint256 totalRewards;     // Cumulative rewards earned
    uint256 lastClaimTime;    // Timestamp of last claim
}
```

### Slashing conditions

| Condition | Penalty |
|-----------|---------|
| Failed execution | 0.1% of stake |
| >10% failure rate | Temporary suspension |
| Malicious behavior | Full stake slash |

## InstantLiquidityPool

Deposits and withdrawals that execute at once, for users who prefer speed over cost. LPs earn the fees.

- **Instant deposits:** skip the keeper fulfilment step
- **Instant redemptions:** immediate asset return
- **Dynamic pricing:** 2-5% fee based on utilization
- **LP rewards:** earn fees by providing liquidity
- **Automated rebalancing:** the pool maintains optimal levels

```
Standard Flow (until a keeper fulfils):
User → requestDeposit → [wait] → claimDeposit → Shares

Instant Flow (immediate):
User → instantDeposit → Shares (pay 2-5% premium)
```

### Instant deposit

```solidity
function instantDeposit(
    address vault,
    uint256 assets,
    uint256 minShares,
    address receiver
) external returns (uint256 shares);
```

```typescript
// Instant deposit with 3% fee
const shares = await instantLiquidityPool.instantDeposit(
  conservativeVaultAddress,
  parseUnits('1000', 18),
  parseUnits('950', 18),  // Accept up to 5% slippage (includes fee)
  userAddress
);
```

### Instant redeem

```solidity
function instantRedeem(
    address vault,
    uint256 shares,
    uint256 minAssets,
    address receiver
) external returns (uint256 assets);
```

### Add liquidity

```solidity
function addLiquidity(
    address vault,
    uint256 assets,
    uint256 shares
) external;
```

LPs can provide:

- **Asset liquidity**: tUSDC (the vault underlying asset) for instant deposits
- **Share liquidity**: Vault shares for instant redemptions

### Remove liquidity

```solidity
function removeLiquidity(
    address vault,
    uint256 assets,
    uint256 shares
) external;
```

### Claim fees

```solidity
function claimFees(address vault) external;
```

### Instant fee

The fee is a **linear formula** of pool utilization:

```
feeBps = MIN_FEE_BPS + ((MAX_FEE_BPS - MIN_FEE_BPS) × utilization) / 10000
       = 200 + (300 × utilization / 10000)
```

| Utilization | Fee (approx.) |
|-------------|---------------|
| 0% | 2.0% |
| 25% | 2.75% |
| 50% | 3.5% |
| 75% | 4.25% |
| 100% | 5.0% |

The fee scales continuously and linearly, not in discrete steps. 90% goes to LPs in proportion to stake and 10% to the protocol treasury.

### LP economics

> **Important:** Returns to instant-liquidity LPs are fee income driven entirely by instant-flow volume, fee tier, pool size, and competition among LPs. They are **not** a protocol yield and are **separate from** Noderr's [per-vault target bands](../../introduction/core-concepts.md#vaults) and the combined vault + node target of **8–28%**. Node rewards are a revenue-share, not a fixed APY. The figures below are illustrative of the fee-share mechanic only and are not projected, expected, or guaranteed returns.

**Illustrative mechanic (not a yield projection):**

- LP provides 10,000 tUSDC liquidity
- Suppose the pool processes 50,000 tUSDC/day of instant flow at a 3% average fee
- Gross daily fees: 1,500 tUSDC (3% of 50,000)
- LP share at 90% fee distribution, holding 10% of the pool: 135 tUSDC/day

Realized returns depend on actual instant-flow volume, the prevailing utilization-based fee, the LP's share of the pool, and competition. Sustained high volume at these assumptions would be exceptional. In practice instant-flow demand is variable and may be low, so LPs should not assume the illustrative throughput above.

## Choosing a flow

```
1. Check instant liquidity availability
2. If available and user prefers speed → Use InstantLiquidityPool
3. If user prefers cost savings → Use VaultRouter with auto-claim
4. If user wants full control → Use VaultRouter without auto-claim
```

### TypeScript

```typescript
import { parseUnits } from 'viem';

// Option 1: Instant deposit (immediate, 2-5% fee)
const instantShares = await instantLiquidityPool.instantDeposit(
  vaultAddress,
  parseUnits('1000', 18),
  parseUnits('950', 18),
  userAddress
);

// Option 2: Auto-claim deposit (after keeper fulfilment, 0.1% fee)
await vaultRouter.requestDeposit(
  vaultAddress,
  parseUnits('1000', 18),
  parseUnits('990', 18),
  userAddress,
  true  // auto-claim
);

// Option 3: Manual deposit (after keeper fulfilment, 0% fee)
await vaultRouter.requestDeposit(
  vaultAddress,
  parseUnits('1000', 18),
  parseUnits('990', 18),
  userAddress,
  false  // manual claim
);

// Later: manually claim
const shares = await vaultRouter.claimDeposit(vaultAddress, userAddress);
```

### Solidity

```solidity
import "./interfaces/IVaultRouter.sol";
import "./interfaces/IInstantLiquidityPool.sol";

contract MyProtocol {
    IVaultRouter public vaultRouter;
    IInstantLiquidityPool public instantPool;
    
    function smartDeposit(
        address vault,
        uint256 amount,
        bool preferSpeed
    ) external {
        if (preferSpeed && instantPool.hasLiquidity(vault, amount)) {
            // Instant deposit
            instantPool.instantDeposit(vault, amount, 0, msg.sender);
        } else {
            // Auto-claim deposit
            vaultRouter.requestDeposit(vault, amount, 0, msg.sender, true);
        }
    }
}
```

## UX contract security

| Contract | Role | Permissions |
|----------|------|-------------|
| VaultRouter | `OPERATOR_ROLE` | Execute auto-claims |
| VaultRouter | `PAUSER_ROLE` | Emergency pause |
| AutoClaimKeeper | `KEEPER_ROLE` | Execute claims |
| InstantLiquidityPool | `REBALANCER_ROLE` | Trigger rebalancing |

All three contracts implement:

- `pause()` / `unpause()` - Halt operations
- `emergencyWithdraw()` - Admin asset recovery
- UUPS upgradeable pattern for security patches

These contracts have not had a third-party security audit. Use them on testnet only until audits are complete.

## Next

- [Contract Addresses](contract-addresses.md)
- [ERC-7540 Specification](https://eips.ethereum.org/EIPS/eip-7540)
- [Deposit Guide](../../getting-started/for-users/deposit-guide.md) and [Withdraw Guide](../../getting-started/for-users/withdraw-guide.md)
- [Node Operators](../../node-operators/README.md), for running keeper nodes

*Last updated: December 28, 2025. Version 1.0.*
