Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.generalmarket.io/llms.txt

Use this file to discover all available pages before exploring further.

Curator Service

The curator watches everything. It intervenes only when necessary. The best curators are the ones you forget exist. A Rust service that bridges BLS consensus and Morpho lending. It collects signed NAV prices from oracles, pushes them to on-chain oracles, rebalances vault capital, monitors health factors, and serves lending quotes. Four jobs. One process. Silent until something goes wrong.
    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                            CURATOR SERVICE                                 │
    │                                                                            │
    │   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
    │   │    Oracle     │  │  Allocation  │  │    Health    │  │   Quote API  │  │
    │   │  Collector    │  │     Bot      │  │   Monitor    │  │   (HTTP)     │  │
    │   │              │  │              │  │              │  │              │  │
    │   │ Collect NAV  │  │  Rebalance   │  │  Scan HF,   │  │ POST /quote  │  │
    │   │ from oracles │  │  vault USDC  │  │  oracles,   │  │ SERM rates   │  │
    │   │ BLS aggregate│  │  across mkts │  │  positions  │  │ Bundler data │  │
    │   └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  │
    │          │                 │                  │                 │          │
    └──────────┼─────────────────┼──────────────────┼─────────────────┼──────────┘
               │                 │                  │                 │
               ▼                 ▼                  ▼                 ▼
          ITPNAVOracle     MetaMorpho Vault    Telegram Bot     CuratorRateIRM
          (Settlement)      (Settlement)        (Alerts)        (Settlement)

Operational Modes

The Curator supports four mutually exclusive launch modes selected by CLI flags:
ModeFlagDescription
Oracle Collector(default)Collects BLS-signed NAV from oracles, pushes to ITPNAVOracle
Allocation Bot--allocation-modeRebalances vault supply across Morpho markets
Health Monitor--health-monitor-modeScans positions, oracles, mirrors, vault metrics
Unified--unified-modeRuns all three + Quote API concurrently in one process
In production, the Curator runs in unified mode. Tasks that lack required config are automatically skipped. Each task runs as an independent tokio::spawn with a shared AtomicBool shutdown signal for graceful Ctrl+C handling.

Oracle Collector

The primary function. Poll the oracles. Validate consensus. Aggregate signatures. Push to chain. Repeat. The oracle is only as fresh as the last push.
    Oracle 1            Oracle 2            Oracle 3
    ┌─────────┐         ┌─────────┐         ┌─────────┐
    │ Compute │         │ Compute │         │ Compute │
    │   NAV   │         │   NAV   │         │   NAV   │
    │         │         │         │         │         │
    │ BLS-sign│         │ BLS-sign│         │ BLS-sign│
    │ (price, │         │ (price, │         │ (price, │
    │  cycle,  │         │  cycle,  │         │  cycle,  │
    │  time)  │         │  time)  │         │  time)  │
    └────┬────┘         └────┬────┘         └────┬────┘
         │                   │                   │
         │    GET /api/nav-sign?itp=0x...        │
         ▼                   ▼                   ▼
    ┌────────────────────────────────────────────────┐
    │              NavCollector                      │
    │                                                │
    │  1. HTTP GET to each oracle (parallel)         │
    │  2. Parse NavSignResponse (price, sig, id)     │
    │  3. Validate consensus:                        │
    │     - All prices must agree                    │
    │     - All cycle numbers must agree             │
    │     - Threshold: ceil(2n/3) responses needed   │
    │  4. Aggregate BLS signatures into one          │
    │  5. Build signer bitmask                       │
    └────────────────────┬───────────────────────────┘

                         │  aggregated signature
                         │  + price + cycle + bitmask

    ┌────────────────────────────────────────────────┐
    │              OraclePusher                      │
    │                                                │
    │  1. Read lastCycleNumber from chain            │
    │     (via data-node cache or direct RPC)        │
    │  2. Skip if cycle <= on-chain cycle            │
    │  3. pushPrice(price, timestamp, cycle,         │
    │               aggregatedSig, bitmask)          │
    │  4. Wait for tx confirmation                   │
    └────────────────────┬───────────────────────────┘


    ┌────────────────────────────────────────────────┐
    │           ITPNAVOracle.sol                     │
    │           (Settlement Chain)                   │
    │                                                │
    │  - Verifies BLS signature on-chain             │
    │  - Stores latest price + timestamp             │
    │  - Morpho reads price via oracle interface     │
    └────────────────────────────────────────────────┘

Consensus Validation

No price reaches the oracle without unanimous agreement from a quorum. Disagreement is rejection.
CheckRequirementError
Threshold>= ceil(2n/3) responses from n oraclesThresholdNotMet
Price agreementAll responding oracles report identical pricePriceDisagreement
Cycle agreementAll responding oracles report identical cycle numberCycleNumberDisagreement
FreshnessNew cycle number must exceed on-chain lastCycleNumberSkip (no error)
The collector uses raw TCP with manual HTTP/1.1 request construction (no reqwest) to minimize dependencies. Response size is capped at 1 MB (MAX_RESPONSE_SIZE) to prevent OOM from malicious oracles. Chunked transfer encoding is rejected.

Collection Loop Timing

    ┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐
    │  Collect  │────►│ Validate  │────►│   Push    │────►│   Sleep   │
    │  from all │     │ consensus │     │  to chain │     │ interval  │
    │  oracles  │     │  + agg    │     │  (if new) │     │  (config) │
    └───────────┘     └───────────┘     └───────────┘     └─────┬─────┘
         ▲                                                      │
         └──────────────────────────────────────────────────────┘
                           repeat until shutdown

Allocation Bot

Capital must go where it is needed. The allocation bot watches utilization across all markets and moves USDC accordingly. It respects concentration limits because unchecked optimism is how vaults fail.

Utilization Targets

    0%              50%         70%    85%    90%         100%
    ├────────────────┼───────────┼──────┼──────┼───────────┤
    │                │           │      │      │           │
    │  CRITICAL LOW  │   BELOW   │      │      │ CRITICAL  │
    │  Strong pull   │  TARGET   │TARGET│      │   HIGH    │
    │  candidate     │           │ ZONE │      │ Priority  │
    │                │           │      │      │  supply   │
    └────────────────┴───────────┴──────┴──────┴───────────┘
ConstantValueMeaning
TARGET_UTILIZATION_MIN70%Below this, market is a withdrawal candidate
TARGET_UTILIZATION_MAX85%Above this, market needs more supply
UTILIZATION_CRITICAL_HIGH90%Urgent supply needed
UTILIZATION_CRITICAL_LOW50%Strong candidate for capital withdrawal

Risk Tiers

Every DTF market is assigned a risk tier that controls maximum concentration (percentage of vault assets allowed in that market):
    ┌─────────────────────────────────────────────────────────┐
    │                    RISK TIERS                           │
    │                                                         │
    │   Tier A ████████████████████████████████  30% max cap  │
    │   Blue-chip, diversified ITPs                           │
    │                                                         │
    │   Tier B ████████████████████            20% max cap    │
    │   Medium risk ITPs                                      │
    │                                                         │
    │   Tier C ██████████                     10% max cap     │
    │   Higher risk ITPs                                      │
    │                                                         │
    │   Tier D █████                           5% max cap     │
    │   Watch list, new ITPs (default)                        │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
New markets default to Tier D (5% cap) — the most restrictive tier. This prevents a freshly created DTF from absorbing excessive vault capital before it has been reviewed.

Allocation Cycle

Each cycle follows this sequence:
    ┌──────────────────┐
    │  Detect new      │  Read vault.supplyQueue()
    │  markets from    │  Compare against known set
    │  supply queue    │  Add any new market IDs
    └────────┬─────────┘


    ┌──────────────────┐
    │  Read market     │  For each market:
    │  state from      │  - morpho.market(id) -> totals
    │  Morpho Blue     │  - morpho.position(id, vault) -> vault supply
    │                  │  - Compute utilization %
    └────────┬─────────┘


    ┌──────────────────┐
    │  Compute         │  Identify over/under-utilized markets
    │  reallocation    │  Respect tier concentration caps
    │  decisions       │  Min allocation = 1 USDC (avoid dust)
    └────────┬─────────┘


        ┌────┴────┐
        │ Needed? │
        └────┬────┘
         No  │  Yes
         │   │
         ▼   ▼
       Skip  ┌──────────────────┐
             │  vault.reallocate │  Submit MarketAllocation[]
             │  (on-chain tx)    │  to MetaMorpho vault
             └──────────────────┘

Health Monitor

The health monitor scans everything, constantly, looking for the thing that is about to go wrong. It generates structured reports. It classifies alerts by severity. It sends them to Telegram. Vigilance is the only strategy against entropy.

Monitoring Loop

    ┌──────────────────────────────────────────────────────────────────┐
    │                    HEALTH MONITOR SCAN CYCLE                    │
    │                                                                  │
    │   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
    │   │  Position   │  │   Oracle    │  │   Mirror    │            │
    │   │  Health     │  │  Freshness  │  │  Registry   │            │
    │   │  Factors    │  │  Check      │  │  Sync       │            │
    │   │             │  │             │  │             │            │
    │   │ For each    │  │ For each    │  │ L3 nonce vs │            │
    │   │ borrower:   │  │ oracle:     │  │ Settlement  │            │
    │   │ collateral  │  │ lastUpdated │  │ activeCount │            │
    │   │ / debt      │  │ vs now      │  │             │            │
    │   │ = HF        │  │ > cadence?  │  │ Synced?     │            │
    │   └──────┬──────┘  └──────┬──────┘  └──────┬──────┘            │
    │          │                │                 │                    │
    │          ▼                ▼                 ▼                    │
    │   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
    │   │   Vault     │  │   Crisis    │  │    SERM     │            │
    │   │  Metrics    │  │   Levels    │  │   Stress    │            │
    │   │             │  │             │  │   Scoring   │            │
    │   │ totalAssets │  │ Per-market: │  │             │            │
    │   │ totalSupply │  │ Normal      │  │ Per-asset   │            │
    │   │ idle        │  │ Elevated    │  │ volatility  │            │
    │   │ utilization │  │ Stress      │  │ scoring     │            │
    │   │             │  │ Emergency   │  │             │            │
    │   └──────┬──────┘  └──────┬──────┘  └──────┬──────┘            │
    │          │                │                 │                    │
    │          └────────────────┼─────────────────┘                   │
    │                          ▼                                      │
    │                  ┌──────────────┐                                │
    │                  │ HealthReport │                                │
    │                  │              │                                │
    │                  │ .positions[] │                                │
    │                  │ .oracles[]   │                                │
    │                  │ .alerts[]    │                                │
    │                  │ .vault_metrics│                               │
    │                  │ .mirror_sync │                                │
    │                  │ .crisis_levels│                               │
    │                  └──────┬───────┘                                │
    │                         │                                       │
    └─────────────────────────┼───────────────────────────────────────┘

              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
        ┌──────────┐   ┌──────────┐   ┌──────────────┐
        │ Telegram │   │ Log File │   │ SharedState   │
        │ Alerts   │   │ (JSON)   │   │ (unified mode)│
        │          │   │          │   │               │
        │ Crisis   │   │ Full     │   │ crisis_levels │
        │ changes  │   │ report   │   │ asset_stress  │
        │ Critical │   │ dump     │   │ -> Quote API  │
        │ warnings │   │          │   │ -> Alloc Bot  │
        └──────────┘   └──────────┘   └──────────────┘

Alert System

Alerts are classified by severity and category:
SeverityMeaningExample
InfoRoutine status”All oracles within cadence”
WarningNeeds attentionHF between 1.05 and 1.2
CriticalImmediate actionHF below 1.05, oracle stale >24h
CategoryWhat it monitors
PositionBorrower health factors approaching liquidation
OracleNAV oracle staleness per risk-tier cadence
MirrorL3 registry vs settlement mirror sync status
VaultMetaMorpho vault utilization, idle capital

Health Factor Thresholds

    1.0           1.05          1.2               ∞
     ├──────────────┼──────────────┼────────────────►
     │              │              │
     │ LIQUIDATABLE │   CRITICAL   │    HEALTHY
     │   HF < 1.0   │  1.0 <= HF  │   HF >= 1.2
     │              │   < 1.2      │
     │  ██████████  │  ▓▓▓▓▓▓▓▓   │
     │  Immediate   │  Alert sent  │  No action
     │  liquidation │  to Telegram │

Oracle Freshness Cadences

A stale oracle is a lying oracle. Each risk tier defines the maximum acceptable age:
TierMax StalenessDescription
Tier A4 hoursBlue-chip DTFs, tightest freshness
Tier B6 hoursMedium risk
Tier C12 hoursHigher risk
Tier D24 hoursWatch list (also hard maximum)

Crisis Levels

Four states, each worse than the last. The crisis level feeds into the Quote API and Allocation Bot. A market in EMERGENCY does not receive new capital.
    NORMAL ──────► ELEVATED ──────► STRESS ──────► EMERGENCY
      │               │               │               │
      │  Utilization   │  HF warnings  │  Critical HF  │
      │  in range,     │  or oracle    │  or stale     │
      │  oracles       │  approaching  │  oracle past  │
      │  fresh         │  staleness    │  max cadence  │
      │               │               │               │
      │  Quote API:    │  Quote API:   │  Quote API:   │
      │  Normal HF     │  Higher min   │  Much higher  │
      │  requirements  │  HF enforced  │  min HF or    │
      │               │               │  reject quote │
Crisis level transitions trigger Telegram alerts. The alert dispatcher tracks previous levels and only fires on transitions (Normal -> Elevated, Stress -> Emergency, etc.), not on every scan cycle.

Quote API

A lending quote is a promise with conditions. The Quote API computes the conditions — rate, health factor requirement, calldata — and returns them. The borrower decides whether to accept.

Quote Flow

    Frontend                   Curator Quote API              Settlement Chain
    ┌────────┐                ┌──────────────────┐           ┌────────────────┐
    │        │   POST /quote  │                  │           │                │
    │ Borrow │───────────────►│ 1. Authenticate  │           │                │
    │  Form  │                │    (X-API-Key)   │           │                │
    │        │                │                  │           │                │
    │        │                │ 2. Look up market│           │                │
    │        │                │    config for ITP│           │                │
    │        │                │                  │           │                │
    │        │                │ 3. Get BLS data  │           │                │
    │        │                │    (from shared  │           │                │
    │        │                │     state cache) │           │                │
    │        │                │                  │  read     │                │
    │        │                │ 4. Read on-chain ├──────────►│ Oracle price   │
    │        │                │    state         │◄──────────┤ Market state   │
    │        │                │                  │           │                │
    │        │                │ 5. SERM: compute │           │                │
    │        │                │    borrow rate   │  push     │                │
    │        │                │                  ├──────────►│ CuratorRateIRM │
    │        │                │ 6. Check crisis  │           │ .setRate()     │
    │        │                │    level, enforce│           │                │
    │        │                │    min HF        │           │                │
    │        │                │                  │           │                │
    │        │  QuoteResponse │ 7. Build Morpho  │           │                │
    │        │◄───────────────┤    Bundler       │           │                │
    │        │  terms, rate,  │    calldata      │           │                │
    │        │  calldata      │                  │           │                │
    └────────┘                └──────────────────┘           └────────────────┘

SERM (Shared Exposure Rate Model)

Risk has a price. The SERM algorithm computes it by weighing utilization, volatility, concentration, liquidity, and tier — then producing a single number: the borrow rate.
    rate = kink(global_util) * (1 + stress * 0.5) * (1 + concentration) * liquidity_mult + tier_premium
ParameterSourceDescription
global_utilOn-chain market stateAggregate utilization across all markets
stressHealth monitor (shared state)Per-asset volatility score, capped at 5x
concentrationDTF composition weightsHow concentrated the collateral basket is
liquidity_multAsset configPenalty for illiquid underlying assets
tier_premiumRisk tier (A/B/C/D)Fixed premium per tier
Rate parameters:
PointRate
0% utilization2% APR
80% utilization (kink)5% APR
100% utilization100% APR
Rates are pushed to CuratorRateIRM.sol on-demand — not on a fixed cadence. A push happens before returning a quote, before liquidation, and on crisis detection. This ensures the on-chain rate always reflects the latest risk assessment.

Shared State (Unified Mode)

Four tasks in one process need to share what they know. SharedCuratorState — an Arc<RwLock<>> — is the shared memory. Writers produce. Readers consume. The lock mediates.
    ┌──────────────┐          ┌──────────────────────────┐          ┌──────────────┐
    │    Health    │  write   │    SharedCuratorState    │  read    │  Quote API   │
    │   Monitor   ├─────────►│                          ├─────────►│              │
    │              │          │  crisis_levels:          │          │ Enforce min  │
    │ run_scan()   │          │    { mkt_id: Level }     │          │ HF per level │
    │              │          │                          │          │              │
    └──────────────┘          │  asset_stress:           │          └──────────────┘
                              │    { addr: score }       │
    ┌──────────────┐          │                          │          ┌──────────────┐
    │    Oracle    │  write   │  cached_bls:             │  read    │  Allocation  │
    │  Collector   ├─────────►│    { price, sig,         ├─────────►│     Bot      │
    │              │          │      cycle, bitmask,     │          │              │
    │ collect_all()│          │      cached_at }         │          │ Stress-aware │
    │              │          │                          │          │ rebalancing  │
    └──────────────┘          └──────────────────────────┘          └──────────────┘
FieldWriterReader(s)Purpose
crisis_levelsHealth MonitorQuote API, Allocation BotPer-market crisis classification
asset_stressHealth MonitorQuote API (SERM)Per-asset volatility scores
cached_blsOracle CollectorQuote APILatest BLS data for oracle updates in quotes

Key Contracts

ContractChainRole
ITPNAVOracle.solSettlementReceives BLS-signed NAV prices, provides Morpho oracle interface
CuratorRateIRM.solSettlementDynamic interest rate model, receives SERM-computed rates from Curator
MetaMorpho (vault)SettlementVault whose allocations the bot manages across markets
Morpho BlueSettlementCore lending protocol — markets, positions, liquidations
MirrorRegistry.solSettlementMirror of L3 registry, health monitor checks sync status

Source Modules

ModuleFileResponsibility
collectorcollector.rsHTTP collection from oracles, BLS aggregation, oracle push
allocatorallocator.rsMarket utilization monitoring, tier-aware rebalancing
health_monitorhealth_monitor.rsPosition HF, oracle freshness, mirror sync, vault metrics
quote_serverquote_server.rsAxum HTTP server for POST /api/lending/quote
quotequote.rsQuote computation, crisis levels, rate limiting
sermserm.rsShared Exposure Rate Model — global risk-based rate computation
rate_pusherrate_pusher.rsPush SERM rates to CuratorRateIRM on-chain
alertingalerting.rsTelegram + log-based alert dispatching
shared_stateshared_state.rsArc<RwLock<>> state for cross-task communication
tier_configtier_config.rsRisk tier definitions and concentration cap loading
market_configmarket_config.rsMarket registry: DTF -> Morpho market mapping
liquidatorliquidator.rsLiquidation bot (triggers when HF < 1.0)
data_node_clientdata_node_client.rsClient for data-node RPC cache (avoids direct chain reads)

Alerting

Alerts fire on transitions, not on every scan. A market that has been in crisis for an hour does not need to announce it every five minutes. The dispatcher remembers what it already said.
    ┌──────────────────┐
    │  HealthReport    │
    │                  │
    │  alerts[]        │
    │  crisis_levels   │
    └────────┬─────────┘


    ┌──────────────────┐     ┌─────────────────────────────────────┐
    │ AlertDispatcher  │     │  Alerter trait                      │
    │                  │     │                                     │
    │ Tracks previous  ├────►│  TelegramAlerter                   │
    │ crisis levels    │     │    POST to api.telegram.org         │
    │                  │     │    bot_token + chat_id              │
    │ Fires only on    │     │                                     │
    │ transitions      │     │  LogAlerter (fallback)              │
    │ (not every scan) │     │    tracing::warn / tracing::error   │
    └──────────────────┘     └─────────────────────────────────────┘
If no Telegram credentials are configured (--telegram-bot-token, --telegram-chat-id), the Curator falls back to LogAlerter which outputs alerts via the structured logging system. No alerts are silently dropped.

End-to-End Data Flow

From oracle NAV computation to a lending quote in the user’s browser. Every arrow is a dependency. Every dependency is a risk.
    ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
    │  Oracle 1   │     │  Oracle 2   │     │  Oracle 3   │
    │  Compute NAV│     │  Compute NAV│     │  Compute NAV│
    │  BLS sign   │     │  BLS sign   │     │  BLS sign   │
    └──────┬──────┘     └──────┬──────┘     └──────┬──────┘
           │                   │                   │
           └───────────────────┼───────────────────┘


                   ┌───────────────────────┐
                   │   CURATOR (unified)   │
                   │                       │
                   │   NavCollector        │
                   │     │                 │
                   │     ▼                 │
                   │   OraclePusher ───────┼──────► ITPNAVOracle.sol
                   │     │                 │          │
                   │     ▼                 │          │ Morpho reads
                   │   SharedState ◄───────┼──────────┘ oracle price
                   │     │                 │
                   │     ├──► QuoteAPI     │
                   │     │     │           │
                   │     │     ▼           │
                   │     │   SERM ─────────┼──────► CuratorRateIRM.sol
                   │     │     │           │          │
                   │     │     ▼           │          │ Morpho uses
                   │     │   QuoteResponse │          │ for interest
                   │     │                 │          │
                   │     ├──► HealthMonitor│
                   │     │     │           │
                   │     │     ▼           │
                   │     │   Alerts ───────┼──────► Telegram
                   │     │                 │
                   │     └──► AllocBot     │
                   │           │           │
                   │           ▼           │
                   │         reallocate ───┼──────► MetaMorpho Vault
                   │                       │
                   └───────────────────────┘