Skip to main content
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

Arbitrum Sepolia testnet

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.

Weekend trading restored

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.

Weekend deploys work

_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.

Mobile responsive alpha

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.

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 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 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.
Nothing on Sepolia carries monetary value. Everything is testnet — forward prices, USDC, positions, and liquidity. Do not pipe production capital into it.

Highlights

Two trading pairs

USD/JPY ships alongside EUR/USD. Pair-aware hooks, registry-driven UI, and Pyth-native pricing across both.

Public alpha frontend

alpha.nilemarkets.com for traders and LPs. Internal dev preview at preview.nilemarkets.com.

Agent integrations

MCP server, x402 paid API gateway, nile CLI on crates.io, Claude Code plugin, OpenClaw skill.

Audit hardening

Every High, Medium, Low, and Informational finding from two internal audit cycles remediated.

GMX-style pool utilization

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.

Pyth-verified forward safeguard

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.

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.
  • 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.
  • 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_bpspool_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

MCP server

mcp.nilemarkets.com — 23 tools over HTTP-stream transport, including get_bucket_states for per-(pair, maturity) cohorts.

x402 gateway

x402.nilemarkets.com — 23 REST endpoints (15 GET, 8 POST) with x402 paywall on writes.

`nile` CLI

cargo install nile-markets-cli — read/write commands with OWS wallet integration for signing. New nile buckets list for cohort queries.

Claude Code plugin

Four scoped skills (explain, query, integrate, execute) plus MCP config bundled.

OpenClaw skill

Cross-agent onboarding via mcp.nilemarkets.com/skill.md.

Mintlify docs

docs.nilemarkets.com — protocol, build, and AI-agent reference.

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.