# ORBIT DESK — Build Spec

> A non-custodial index-fund rebalancing desk. Holders follow a basket, prices drift,
> a permissionless keeper rebalances toward target and earns a bounty — guarded by
> **three walls**. Not a vault, no custody, no yield-farm.

---

## 1. Product boundary

| Locked | Meaning |
|---|---|
| **No custody** | No manager/owner can withdraw the basket. Only share holders can `cross` (redeem pro-rata). No upgradeable proxy, no admin drain. |
| **Three walls** | Every `rebalance` is checked against three guardrails (see §3). |
| **Bounty keeper** | Anyone may rebalance and earn a bounty; no trusted operator required. |
| **Not a vault** | No yield, no lockup, no ponzi floor, no deposit strategies. A pure rebalancing desk. |

**Deferred to later (explicitly out of v1):** mainnet deployment, real stock/RWA tokens,
third-party audit.

## 2. Repository structure

```
orbit-desk/
├── contracts/          # Solidity + Hardhat + TS (0.8.24, OZ v5)
│   ├── src/            # OrbitFund, OrbitDesk, OrbitPriceOracle, mock/{MockERC20,MockRouter}
│   ├── test/           # the four walls' must-pass tests
│   └── scripts/        # deploy.ts, demo.ts (closed loop)
├── apps/web/           # Vite + React + TS — / desk funds floor mandate
└── services/keeper/    # Node keeper that watches drift and auto-rebalances
```

## 3. Math & contract interfaces

### 3.1 Units
- Weights and drift are in **basis points** (1 bp = 0.01 %, `BPS = 10_000`).
- Prices and values are in **WAD** (1e18, "USD per whole token").
- Token balances are raw wei (18 decimals).

### 3.2 Drift formula (L1 distance, bps)

```
value_i       = balance_i * price_i / 1e18
totalValue    = Σ value_i
actual_i      = value_i * 10000 / totalValue          // bps
drift         = Σ | actual_i − target_i |             // bps, ∈ [0, 20000]
```

### 3.3 Interfaces (on `OrbitFund`)

| Function | Who | Behaviour |
|---|---|---|
| `setPolicy(assets[], weights[])` | manager | Set target weights (sum must equal 10000). Non-manager reverts. |
| `follow(amounts[])` | anyone | Deposit a mix of basket assets; mint shares at reference value-per-share. |
| `cross(shares)` | holder | Burn shares; redeem the basket pro-rata. No manager redemption. |
| `rebalance(prices[])` | keeper | Realign basket toward target; enforce the three walls; mint bounty shares. |

### 3.4 The three walls

**Wall 1 — drift wall.** `rebalance` must strictly reduce drift:
`postDrift < preDrift` **and** `preDrift − postDrift ≥ minDriftReduction`, else revert
`DriftNotReduced`.

**Wall 2 — price wall.** The keeper's feed must track the reference oracle:
`|price_i − oracle_i| / oracle_i ≤ maxPriceDeviation` for every asset, else revert
`PriceDeviation`.

**Wall 3 — bounty wall.** Bounty is minted as shares, purely proportional to
drift-reduction, gated by a floor and a cooldown:

```
bountyValue = totalValue × (preDrift − postDrift) / 10000 × bountyRate / 10000
bountyShares = bountyValue × totalSupply / totalValue
```

Guards: `preDrift ≥ rebalanceFloor` (else `BelowFloor`), and `block.timestamp ≥
lastRebalance + cooldown` (else `CooldownActive`). Linearity means splitting one
rebalance into N yields the same total bounty — no fixed base fee to farm.

## 4. Must-pass tests

| Test | Guards | File |
|---|---|---|
| Drift not decreasing reverts | Wall 1 | `DriftWall.test.ts` |
| Feed price deviation reverts | Wall 2 | `PriceWall.test.ts` |
| Split-order bounty farming blocked | Wall 3 (floor + linearity + cooldown) | `BountyWall.test.ts` |
| follow/cross/setPolicy semantics | product | `FollowCross.test.ts` |

## 5. Frontend pages

- `/` — home, three walls, live drift
- `/desk` — interactive desk: allocation, drift meter, rebalance button
- `/funds` — fund registry from `OrbitDesk`
- `/floor` — the drift floor and why it blocks split-order farming
- `/mandate` — the fund's policy + guardrail parameters

Visual: dark space theme (deep navy `#0a0e1a`, gold `#f4b942`, teal `#35d0ba`),
Space Grotesk + IBM Plex Mono, avatar as the logo.

## 6. Local closed loop

```
Anvil (or npx hardhat node)  →  deploy  →  change feed price to create drift
                             →  keeper auto-rebalances  →  drift ↓, bounty paid
```

The oracle is the single source of truth. Changing it is "the market moving"; the
keeper reads it, hands the same prices to `rebalance`, and the basket realigns.
