For a product-level overview of how forward pricing works, see Oracle & Forward Pricing.
Architecture
The publisher is a single-binary Rust service that:- Connects to the Pyth Hermes API for real-time EUR/USD spot prices
- Computes forward prices using the interest rate parity formula
- Submits prices onchain via
OracleModule.publishForwardRounds() - Records fixing prices for matured timestamps via
OracleModule.recordFixingPriceFromPyth()
Publisher Cycle
Fetch Spot Price
The publisher queries the Pyth Hermes REST API for the latest EUR/USD price. The raw Pyth price (int64 with exponent) is converted to 18-decimal precision.
Compute Forward Prices
For each enabled tenor (1D, 1W, 1M), the publisher:
- Determines all active fixing timestamps (positions open for that tenor)
- Computes the forward price:
F = S * (1 + r * t / (365 days * 10,000)) - Where
ris the configuredFORWARD_RATE_BPS(default: 0) andtis seconds to maturity
Submit Forward Rounds
All computed prices are batched into a single
publishForwardRounds() transaction. Each price is keyed by (pairId, fixingTimestamp) and receives an incrementing round ID.Record Fixing Prices
For any fixing timestamps that have passed (matured positions), the publisher submits
recordFixingPriceFromPyth() with fresh Pyth update data. This records the settlement price.Safeguard Checks
EverypublishForwardRound call passes through four onchain safeguard checks. All must pass for the price to be accepted.
| 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 |
| Deviation vs Close | |newPrice - lastClose| / lastClose <= maxDeviationVsLastCloseBps | 150 bps (1.5%) | Anchor to last settlement |
Configuration
| Environment Variable | Default | Description |
|---|---|---|
RPC_URL | — | Ethereum RPC endpoint |
PRIVATE_KEY | — | Publisher wallet private key (must have PUBLISHER_ROLE) |
FORWARD_RATE_BPS | 0 | Annualized forward rate in basis points |
PUBLISH_INTERVAL_SECS | 30 | Seconds between publish cycles |
ORACLE_MODULE_ADDRESS | — | OracleModule contract address |
PYTH_ADDRESS | — | Pyth contract address |
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 converts to 18-decimal precision:108000 with exponent -5 becomes 1,080,000,000,000,000,000 (1.08 in 18 decimals).
Monitoring
The publisher logs structured output via thetracing crate:
- INFO: Successful publish cycles, fixing price recordings
- WARN: Safeguard check failures, stale spot prices
- ERROR: RPC failures, transaction reverts
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