> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Release Notes

> Public-facing changelog for Nile Markets — highlights, breaking changes, security findings, and product milestones for v0.3.2 (Arbitrum Sepolia + weekend trading) and prior releases

The protocol API is **unstable** during the M2 milestone — tool schemas,
endpoint paths, and response formats may change without notice. This
page surfaces the public highlights of each release; the canonical
internal changelog tracks every contract, ABI, and config change.

## v0.3.2 — Weekend Trading + Arbitrum Sepolia

This release pairs the second testnet rollout with the weekend-trading
fix. Trading now continues at frozen prices through FX market closure
(Fri 5pm ET → Sun 5pm ET, plus holidays) on both L1 Sepolia and L2
Arbitrum Sepolia.

### Highlights

<Columns cols={2}>
  <Card title="Arbitrum Sepolia testnet" icon="layer-group">
    L2 deployment ships as a co-equal testnet alongside Ethereum Sepolia.
    All read and write surfaces accept `network=arbitrumSepolia` —
    frontend wallet picker, MCP tools, x402 routes, and the `nile` CLI
    all work out of the box with no operator configuration.
  </Card>

  <Card title="Weekend trading restored" icon="calendar-week">
    `OracleModule.publishRound` gains a cache-mode branch (empty
    `pythUpdateData`, `msg.value == 0`) that keeps onchain forwards valid
    through FX market closure by re-using the stored `lastPublishedSpot`
    as the anchor. Trading remains open at frozen prices instead of
    pausing for the weekend.
  </Card>

  <Card title="Weekend deploys work" icon="rocket">
    `_verifyAndStoreSpot` widens its accepted Pyth publish-time window to
    96h on the first publish per pair, letting a fresh deploy seed the
    spot anchor from the most recent historical Pyth attestation even
    when upstream attestations are paused. Subsequent calls re-engage
    the strict 30s window.
  </Card>

  <Card title="Mobile responsive alpha" icon="mobile-screen">
    `alpha.nilemarkets.com` renders cleanly down to a 375 px floor.
    Trade, dashboard, and liquidity pages no longer overflow on phones;
    mobile nav drawer crowding fixed; connect-wallet picker no longer
    surfaces dead-link mobile entries.
  </Card>
</Columns>

### New surfaces

#### Networks

* **Arbitrum Sepolia (chainId 421614)** ships as a co-equal testnet to
  Ethereum Sepolia (chainId 11155111). Per-chain contracts, per-chain
  subgraph, per-chain publisher + keeper workers. See
  [Choosing a network for agent flows](/ai-agents/overview#choosing-a-network-for-agent-flows)
  for L1 vs L2 trade-offs.

#### Agent surfaces

* All MCP tools, x402 routes, and `nile` CLI commands accept
  `network=arbitrumSepolia`. CLI is zero-config — Arb Sep addresses,
  RPC, and subgraph URL are embedded at compile time.

### Changed

#### Contracts

* **`OracleModule.publishRound` cache mode.** Empty `pythUpdateData` array
  * `msg.value == 0` selects cache mode. The contract re-uses the stored
    `lastPublishedSpot` as the anchor instead of parsing fresh Pyth bytes,
    refreshing each forward's `publishTimestamp` so `getForward.isValid`
    stays true. The per-tenor safeguard (`maxAnchorDeviationBps`, move-limit,
    spacing) still gates publisher-supplied prices against the cached anchor,
    preserving the anti-manipulation property from 0.3.1. Bounded by a new
    `MAX_SPOT_CACHE_AGE = 96 hours` constant — multi-day Pyth outages still
    pause trading via `SpotCacheStale`. Same `publishRound` function selector
    — no ABI break.
* **Bootstrap-window relaxation in `_verifyAndStoreSpot`.** When no spot
  has ever been published for a pair (`lastPublishedSpotTimestamp == 0`),
  the function accepts Pyth attestations up to `MAX_SPOT_CACHE_AGE` (96h)
  old. Required for fresh deploys during FX closure. Steady-state calls
  continue to enforce the standard `maxOracleAge` window (default 30s).
* **New `SpotCacheFallbackUsed(pairId, cachedSpot, cachedSpotTimestamp, blockTimestamp)`
  event.** Emitted on every cache-mode `publishRound`. Subgraph indexers
  pick this up so external monitors can alert when cache-mode fires outside
  expected FX closure windows (signals a transient Pyth outage).
* **New `SpotNotInitialized()` and `SpotCacheStale()` errors** in `Errors`
  library.

### Subgraph

* New `SpotCacheFallbackEvent` immutable entity (`pairId`, `cachedSpot`,
  `cachedSpotTimestamp`, `blockTimestamp`, `txHash`, `block`) indexed from
  `OracleModule.SpotCacheFallbackUsed`. Queryable for ops monitoring and
  agent surfaces.

### Documentation

* `pricingUnavailable` semantics clarified: trading remains open at frozen
  prices during FX closure, not paused. UI surfaces this as
  "Pricing paused" (never "Market closed") to convey the frozen-quote
  condition.
* `docs.nilemarkets.com` reframed for two-testnet coverage. New
  [m2-scope](/nile-markets/m2-scope) anchor page with L1 vs L2 trade-offs;
  contract addresses and deployment artifacts switched to two-tab tables.

## v0.3.1 — M2 (External Testnet)

The first public alpha. Runs on Ethereum Sepolia at
[`alpha.nilemarkets.com`](https://alpha.nilemarkets.com).

<Note>
  Nothing on Sepolia carries monetary value. Everything is testnet —
  forward prices, USDC, positions, and liquidity. Do not pipe production
  capital into it.
</Note>

### Highlights

<Columns cols={2}>
  <Card title="Two trading pairs" icon="arrows-left-right">
    USD/JPY ships alongside EUR/USD. Pair-aware hooks, registry-driven UI,
    and Pyth-native pricing across both.
  </Card>

  <Card title="Public alpha frontend" icon="browser">
    `alpha.nilemarkets.com` for traders and LPs. Internal dev preview at
    `preview.nilemarkets.com`.
  </Card>

  <Card title="Agent integrations" icon="robot">
    MCP server, x402 paid API gateway, `nile` CLI on crates.io, Claude
    Code plugin, OpenClaw skill.
  </Card>

  <Card title="Audit hardening" icon="shield-check">
    Every High, Medium, Low, and Informational finding from two internal
    audit cycles remediated.
  </Card>

  <Card title="GMX-style pool utilization" icon="gauge-high">
    Pool utilization now reads `sumAbsBucketExposure / maxNetExposure` —
    the same denominator the RiskManager already uses to gate position
    opens. Hedged longs and shorts in the same `(pair, maturity)` bucket
    correctly offset each other, so balanced books no longer falsely
    trip the 80% LP-withdrawal cap.
  </Card>

  <Card title="Pyth-verified forward safeguard" icon="shield-keyhole">
    The forward publish path now lands Pyth-signed update bytes onchain
    in a single per-pair `publishRound(pairId, pythUpdateData[], fixings[],
            prices[], roundIds[])` call. The Pyth bytes are parsed in the same
    transaction, so the safeguard anchors on `lastPublishedSpot × IRP
            carry` rather than the most-recently closed forward — false-positive
    `DeviationExceedsLimit` reverts on legitimate cross-tenor publishes
    are gone.
  </Card>
</Columns>

### Breaking changes

> Treat the entire 0.3.1 surface as a breaking release versus 0.3.0. ABIs,
> response envelopes, configuration layouts, and error names have all
> moved.

#### Contracts

* **Per-pair oracle health.** `OracleModule.isOracleValid(pairId)` returns
  per-pair validity, and the `ModeController` DEGRADED state is driven by
  the per-pair watchdog. Consumers that gate on oracle health must read
  `isOracleValid(pairId)` rather than a global rolled-up bit.
* **Per-pair, per-maturity exposure caps.** `RiskManager` enforces caps
  on a `(pairId, fixingTimestamp)` bucket, queryable via the new
  `BucketState` subgraph entity.
* **Fee-first settlement waterfall.** At settlement and liquidation: oracle
  fee is deducted first, then trading fee, then PnL — invariant across
  PnL sign.
* **`recordFixingPriceFromPyth` requires `PUBLISHER_ROLE`.** Any account
  with publisher role can record the fixing price; the deployer wallet
  intentionally does not.
* **Tightened oracle safeguard bounds.** `setOracleConfig` now caps
  `spotFixingWindowSeconds ≤ 60`, `maxOracleMovePerUpdateBps ≤ 1000`
  (10%), `maxDeviationVsPriorBps ≤ 500` (5%), and
  `maxAnchorDeviationBps ≤ 1000` (10%, default 150 bps).
  See [Oracle Safeguards](/protocol/oracle-safeguards).
* **Oracle ABI reshape.** `OracleModule.publishForwardRound` and
  `publishForwardRounds` removed; replaced by `publishRound(pairId,
  pythUpdateData[], fixingTimestamps[], forwardPrices[], roundIds[])`
  (`payable`, parses Pyth bytes onchain). `setLastClosePrice` /
  `lastClosePrice` / `LastClosePriceSet` removed; the safeguard reads
  `lastPublishedSpot`, which is updated exclusively by `publishRound`
  from Pyth-signed bytes. `OracleConfig.maxDeviationVsLastCloseBps`
  renamed to `maxAnchorDeviationBps` (semantics: deviation versus
  `lastPublishedSpot × IRP carry`, not the last close). `OracleModule`
  no longer exposes `setProtocolAuthorized` / `ProtocolAuthorizedSet`
  (SettlementEngine no longer writes back into the oracle on close).
  New: `OracleModule.pairForwardRateBps` + `setPairForwardRateBps`
  (±2\_000 bps), `SpotPublished` / `PairForwardRateBpsSet` events,
  `Math.applyForwardCarry`. The publisher reads the per-pair carry from
  chain at startup; the legacy `--forward-rate-bps` CLI flag and
  `FORWARD_RATE_BPS` env var are removed.
* **Dynamic tenor registry.** Tenors are managed via
  `Config.registerTenor / unregisterTenor` and queried via
  `Config.getEnabledTenors()`. Default Sepolia set:
  `[1D, 1W, 1M, 3M, 6M, 1Y]`. See [Tenors](/nile-markets/tenors).
* **Pair registry on-chain.** `OracleModule.getAllPairs()` is the
  authoritative pair list at runtime; the SDK ships a bundled cache that
  consumers can fall back to.
* **Fee buckets split by `FeeType`.** `FeesCollected` events carry a
  discriminator (`TRADING`, `LIQUIDATION_PENALTY`, `EARLY_TERMINATION`,
  `MATURITY`); the subgraph splits them into per-bucket aggregates.
* **Pool-utilization metric reshaped.** `IPoolVault.notionalUtilization()`
  removed; replaced by `riskCapacityUtilization()` with formula
  `sumAbsBucketExposure × 10000 / maxNetExposure`. `maxNotionalUtilizationBps`
  renamed to `maxRiskCapacityBps`; setter renamed to `setMaxRiskCapacityBps`.
  Error `NotionalUtilizationTooHigh` renamed to `RiskCapacityTooHigh`.
  Deploy script now wires `PoolVault.setRiskManager(...)` after RiskManager
  deploy. MCP / x402 / CLI response envelopes rename `notionalUtilization` to
  `riskCapacityUtilization`. `defaults.toml` key renamed
  `pool_max_utilization_bps` → `pool_max_risk_capacity_bps`. Cap value
  unchanged at 8,000 bps (80%).

#### Off-chain services

* **Cross-pair batching** in publisher and keeper for forward / fixing /
  stats updates. On-chain tx batching reduces total tx count when
  multiple pairs publish in the same window.
* **Permissionless watchdog hooks.** Anyone can recover the protocol from
  oracle staleness via the new permissionless DEGRADED-mode entry points;
  no named admin required.

#### Tooling

* Solidity 0.8.35, Foundry 1.7.0, forge-std 1.16.1.
* Rust toolchain 1.95.x.
* Migration to `forge-std/Config` with per-network TOML layout — every
  parameter default flows from a per-network TOML file via the deploy
  script, with a hand-maintained JSON mirror for TypeScript consumers.
* TypeScript packages renamed from `@fx-forward/*` to `@nile-markets/*`.
* Crates renamed for crates.io publishing — `nile-markets-common`,
  `nile-markets-contracts`, `nile-markets-cli` (binary `nile`),
  `nile-markets-subgraph`.

### New surfaces

<Columns cols={2}>
  <Card title="MCP server" href="/ai-agents/mcp-server" icon="plug">
    `mcp.nilemarkets.com` — 23 tools over HTTP-stream transport,
    including `get_bucket_states` for per-(pair, maturity) cohorts.
  </Card>

  <Card title="x402 gateway" href="/ai-agents/x402-api" icon="key">
    `x402.nilemarkets.com` — 23 REST endpoints (15 GET, 8 POST) with
    x402 paywall on writes.
  </Card>

  <Card title="`nile` CLI" href="/build/cli-tool" icon="terminal">
    `cargo install nile-markets-cli` — read/write commands with OWS
    wallet integration for signing. New `nile buckets list` for cohort
    queries.
  </Card>

  <Card title="Claude Code plugin" href="/ai-agents/claude-code-plugin" icon="code">
    Four scoped skills (`explain`, `query`, `integrate`, `execute`) plus
    MCP config bundled.
  </Card>

  <Card title="OpenClaw skill" href="/ai-agents/openclaw-skill" icon="cube">
    Cross-agent onboarding via `mcp.nilemarkets.com/skill.md`.
  </Card>

  <Card title="Mintlify docs" icon="book">
    `docs.nilemarkets.com` — protocol, build, and AI-agent reference.
  </Card>
</Columns>

### Subgraph

* New `BucketState` entity for per-(pair, maturity) net exposure.
* Fee aggregates split by `FeeType`.
* `addMargin` / `removeMargin` events surfaced in the activity feed.
* Immutable-entity annotations on event records to halve write
  amplification vs mutable entities.

### Security

* Two internal audit cycles fully remediated: 7 High + 10 Medium + 8 Low
  * 9 Informational from cycle 1, 13 Medium + 4 Low + 7 Informational + 9
    Informational from cycle 2, plus a final pass of 6 Medium + 4 Low + 3
    Informational. No findings carry forward into 0.3.1.
* 100% Foundry coverage on protocol contracts with invariant and
  gap-fill suites.

### Removed

* Block / call handlers in the subgraph — event-driven only.
* Inline parameter defaults in `Config.sol`, `OracleModule.sol`,
  `ModeController.sol`, `RiskManager.sol`. Parameters now flow from the
  network's `defaults.toml`.
* `@fx-forward/*` package namespace (renamed to `@nile-markets/*`).
* `--private-key` flag from CLI write commands; use `--from <address>`
  with the OWS wallet integration instead.

***

## v0.3.0 — M1 (Internal Demo)

Sepolia-deployed contracts with Pyth oracle, publisher, and subgraph.
Internal-only frontend; no public-facing surface.
