- Which currency pairs are tradable — e.g. adding GBP/USD after launch
- Which maturities are tradable — e.g. adding a 2-week tenor on top of the defaults
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-describing —
getAllPairs()/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: theOracleModule 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
Tenor Registry
Where it lives: theConfig 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
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.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.
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.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.
Related Pages
- Supported Pairs — current pair registry
- Tenors — current tenor registry
- Protocol Architecture — how
OracleModuleandConfigfit in - Forwards vs Perpetuals — why the maturity matters at all