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
nileCLI commands acceptnetwork=arbitrumSepolia. CLI is zero-config — Arb Sep addresses, RPC, and subgraph URL are embedded at compile time.
Changed
Contracts
OracleModule.publishRoundcache mode. EmptypythUpdateDataarraymsg.value == 0selects cache mode. The contract re-uses the storedlastPublishedSpotas the anchor instead of parsing fresh Pyth bytes, refreshing each forward’spublishTimestampsogetForward.isValidstays 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 newMAX_SPOT_CACHE_AGE = 96 hoursconstant — multi-day Pyth outages still pause trading viaSpotCacheStale. SamepublishRoundfunction 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 toMAX_SPOT_CACHE_AGE(96h) old. Required for fresh deploys during FX closure. Steady-state calls continue to enforce the standardmaxOracleAgewindow (default 30s). - New
SpotCacheFallbackUsed(pairId, cachedSpot, cachedSpotTimestamp, blockTimestamp)event. Emitted on every cache-modepublishRound. 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()andSpotCacheStale()errors inErrorslibrary.
Subgraph
- New
SpotCacheFallbackEventimmutable entity (pairId,cachedSpot,cachedSpotTimestamp,blockTimestamp,txHash,block) indexed fromOracleModule.SpotCacheFallbackUsed. Queryable for ops monitoring and agent surfaces.
Documentation
pricingUnavailablesemantics 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.comreframed 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 atalpha.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 theModeControllerDEGRADED state is driven by the per-pair watchdog. Consumers that gate on oracle health must readisOracleValid(pairId)rather than a global rolled-up bit. - Per-pair, per-maturity exposure caps.
RiskManagerenforces caps on a(pairId, fixingTimestamp)bucket, queryable via the newBucketStatesubgraph entity. - Fee-first settlement waterfall. At settlement and liquidation: oracle fee is deducted first, then trading fee, then PnL — invariant across PnL sign.
recordFixingPriceFromPythrequiresPUBLISHER_ROLE. Any account with publisher role can record the fixing price; the deployer wallet intentionally does not.- Tightened oracle safeguard bounds.
setOracleConfignow capsspotFixingWindowSeconds ≤ 60,maxOracleMovePerUpdateBps ≤ 1000(10%),maxDeviationVsPriorBps ≤ 500(5%), andmaxAnchorDeviationBps ≤ 1000(10%, default 150 bps). See Oracle Safeguards. - Oracle ABI reshape.
OracleModule.publishForwardRoundandpublishForwardRoundsremoved; replaced bypublishRound(pairId, pythUpdateData[], fixingTimestamps[], forwardPrices[], roundIds[])(payable, parses Pyth bytes onchain).setLastClosePrice/lastClosePrice/LastClosePriceSetremoved; the safeguard readslastPublishedSpot, which is updated exclusively bypublishRoundfrom Pyth-signed bytes.OracleConfig.maxDeviationVsLastCloseBpsrenamed tomaxAnchorDeviationBps(semantics: deviation versuslastPublishedSpot × IRP carry, not the last close).OracleModuleno longer exposessetProtocolAuthorized/ProtocolAuthorizedSet(SettlementEngine no longer writes back into the oracle on close). New:OracleModule.pairForwardRateBps+setPairForwardRateBps(±2_000 bps),SpotPublished/PairForwardRateBpsSetevents,Math.applyForwardCarry. The publisher reads the per-pair carry from chain at startup; the legacy--forward-rate-bpsCLI flag andFORWARD_RATE_BPSenv var are removed. - Dynamic tenor registry. Tenors are managed via
Config.registerTenor / unregisterTenorand queried viaConfig.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.FeesCollectedevents 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 byriskCapacityUtilization()with formulasumAbsBucketExposure × 10000 / maxNetExposure.maxNotionalUtilizationBpsrenamed tomaxRiskCapacityBps; setter renamed tosetMaxRiskCapacityBps. ErrorNotionalUtilizationTooHighrenamed toRiskCapacityTooHigh. Deploy script now wiresPoolVault.setRiskManager(...)after RiskManager deploy. MCP / x402 / CLI response envelopes renamenotionalUtilizationtoriskCapacityUtilization.defaults.tomlkey renamedpool_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/Configwith 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(binarynile),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
BucketStateentity for per-(pair, maturity) net exposure. - Fee aggregates split by
FeeType. addMargin/removeMarginevents 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’sdefaults.toml. @fx-forward/*package namespace (renamed to@nile-markets/*).--private-keyflag from CLI write commands; use--from <address>with the OWS wallet integration instead.