Skip to main content
Nile Markets has two dimensions an operator may want to extend without a protocol upgrade:
  1. Which currency pairs are tradable — e.g. adding GBP/USD after launch
  2. Which maturities are tradable — e.g. adding a 2-week tenor on top of the defaults
Both are onchain registries. Admins add new entries with a single admin transaction; every consumer (app, CLI, agent tooling, analytics, indexer) reads the live registry and surfaces new entries automatically.

Why Dynamic

Hardcoding pairs or tenors into the contract as enums would force a redeploy every time the product adds a market or maturity. That is operationally painful and makes experimentation slow. A registry in contract state flips the model:
  • Cheap extension — one admin transaction, no code upgrade
  • Zero-downtime — existing markets and maturities keep trading while the new one is added
  • Self-describinggetAllPairs() / getEnabledTenors() are the source of truth; no out-of-band announcement needed
  • Immutable per-position snapshot — each open position locks in its maturity as uint32 seconds at open time, so changes to the registry never retroactively affect existing trades

The Two Registries

Pair Registry

Where it lives: the OracleModule contract maintains the canonical set via registerPair(pairId, pythFeedId, pythExponent), with getAllPairs() returning the current set. What gets wired up when a pair is registered:
  • Pyth price feed (spot source)
  • Per-pair fixing time (16:00 UTC by default for every pair — the WM/Reuters London fix — with per-pair admin overrides available)
  • Per-pair forward publisher schedule
  • Position-open gate in PositionManager
See Supported Pairs for the currently-registered set.

Tenor Registry

Where it lives: the Config contract maintains the set via registerTenor(uint32 seconds), with getEnabledTenors() returning the live list. The identifier is the seconds value. There is no enum layer — 86400 is the 1-day tenor, 7776000 is the 3-month tenor. This lets admins register any duration they want (2 weeks, 9 months, whatever) without touching a schema. See Tenors for the default set.

How the Flow Works

1

Admin adds an entry to the shared config JSON

The protocol’s defaults.json is the single source of truth for the bundled (“known at build time”) set. Adding a new entry here ensures the SDK, CLI, and analytics pick up the new label on their next rebuild.
2

Admin runs a registration script

A Foundry script (for pairs or tenors) calls the onchain registry from the admin key. This is the only transaction required — no new contract deploy, no migration.
3

Every consumer picks up the new entry automatically

Apps, the CLI, the MCP/x402 endpoints, the subgraph, and the oracle all read the live registry and merge it with their bundled cache. New entries appear immediately with a fallback label (e.g. "<seconds>s" for tenors) until the SDK is rebuilt.
4

Forward publisher restart

The forward-price publisher reads the enabled tenor set once at startup and iterates the frozen list. Admins adding a new tenor must restart the publisher so it begins publishing forwards for the new maturity. Until then, the UI shows the new tenor but disables it with a “pending publisher restart” message. Keeper has no equivalent gate — it processes whatever positions exist regardless of maturity.

What Gets Cached vs Checked Onchain

Bundled (first-paint)

What: the pair/tenor list and labels from defaults.json, embedded into the SDK and CLI at build time.Why: no RPC hop to render the first screen of the app; offline-friendly for agent tooling; stable labels for analytics.Caveat: bundled data lags onchain by one SDK/CLI release.

Onchain (source of truth)

What: OracleModule.getAllPairs() and Config.getEnabledTenors().Why: the live registry. Any pair or tenor an admin has registered will be here, even if it post-dates the local SDK build.How consumers use it: merge with the bundled cache; fall back to a "<seconds>s" label for unknown tenors or the raw pairId hex for unknown pairs.

What Is NOT Dynamic (Yet)

  • Per-(pair, tenor) fixing-time overrides — today the fixing time is per-pair (not per-(pair, tenor)). All tenors on EUR/USD settle at the EUR/USD fixing time; all tenors on USD/JPY settle at the USD/JPY fixing time. Per-tenor variation is planned but not required at current scale.
  • Per-tenor risk parameters — initial margin, maintenance margin, trading fee, and liquidation penalty are global (protocol-wide). Per-pair or per-tenor variation may come in a later milestone if risk data warrants it.
  • Dynamic publisher reload — the forward publisher reads the tenor set at startup and does not refresh on a per-tick basis. This is a deliberate choice: the restart-to-activate model is simpler to reason about, and adding a new tenor is rare enough that the operational overhead is negligible.