For a product-level overview of how forward pricing works, see Oracle & Forward Pricing.
Architecture
A publisher is any service that:- Sources real-time EUR/USD spot prices from Pyth Hermes
- Computes forward prices using the interest rate parity formula
- Submits prices onchain via
OracleModule.publishRound()(one pair) orOracleModule.publishRoundsBatch()(multiple pairs in one transaction) - Records fixing prices for matured timestamps via
OracleModule.recordFixingPriceFromPyth()
PUBLISHER_ROLE granted by the protocol admin. All other operational choices — cycle interval, retry strategy, telemetry, failover — are implementation decisions left to the operator.
Publisher Cycle
Fetch Spot Price
Query Pyth Hermes for the latest EUR/USD price. The raw Pyth price (int64 with exponent) is converted to 18-decimal precision before use.
Compute Forward Prices
For each enabled tenor (1D, 1W, 1M):
- Determine all active fixing timestamps (from positions currently open at that tenor)
- Compute the forward price:
F = S * (1 + r * t / (365 days * 10,000)) ris the annualized forward rate in basis points (operator-configured)tis seconds to maturity
Submit Forward Rounds
A single pair’s computed prices go onchain in one
publishRound() transaction, keyed by (pairId, fixingTimestamp) with an incrementing round ID. A publisher serving multiple pairs can instead call OracleModule.publishRoundsBatch() to publish spot + tenor forwards for several pairs in one transaction — in fresh mode the whole batch shares a single Pyth update (one Wormhole verification and one update fee for the call). The batch is atomic: if any pair’s safeguard check fails, the entire call reverts.Safeguard Checks
EverypublishRound and publishRoundsBatch call passes through four onchain safeguard checks. All must pass for every forward in the call to be accepted — these are enforced by the OracleModule contract and apply to any publisher regardless of implementation. The Pyth-verified spot anchor (lastPublishedSpot) lands BEFORE any per-tenor check runs in the same call.
| Check | Formula | Default | Purpose |
|---|---|---|---|
| Spacing | block.timestamp - lastPublishTime >= minForwardUpdateSpacing | 10s | Prevent rapid-fire updates |
| Move Limit | |newPrice - lastPrice| / lastPrice <= maxOracleMovePerUpdateBps | 200 bps (2%) | Cap single-update price jumps |
| Deviation vs Prior | |newPrice - lastAccepted| / lastAccepted <= maxDeviationVsPriorBps | 50 bps (0.5%) | Limit cumulative drift |
| Anchor Deviation | |newPrice - lastPublishedSpot × IRP| / expected <= maxAnchorDeviationBps | 150 bps (1.5%) | Anchor to Pyth-verified spot × per-pair carry |
Forward Round Clearing
Matured forward rounds can be cleared from storage to reclaim gas:| Function | Access | Behavior |
|---|---|---|
clearForwardRound(pairId, fixingTimestamp) | Public | Clears one round if matured and no open positions reference it |
clearMaturedForwards(pairId) | Public | Clears all eligible matured rounds |
clearAllForwards(pairId) | Owner | Clears all rounds (skips timestamps with open positions) |
Pyth Price Conversion
Pyth delivers prices as int64 with an int32 exponent (typically -5 for FX). The protocol expects 18-decimal precision:108000 with exponent -5 becomes 1,080,000,000,000,000,000 (1.08 in 18 decimals).
In M2, the publisher is operated by the protocol team. M3 plans include a multi-publisher model where multiple authorized publishers can submit prices, with the
OracleModule selecting the median.Related Pages
Oracle & Forward Pricing
Product-level pricing overview
Oracle Safeguards
Defense-in-depth oracle protection
Keeper Automation
Settlement and liquidation automation