Skip to main content
The publisher service computes forward prices from Pyth spot data and submits them onchain. It runs continuously to keep forward price curves fresh for all active tenors. This page describes the protocol-level behavior of a publisher — operators building their own publisher can use it as a reference for the onchain interface they must target.
For a product-level overview of how forward pricing works, see Oracle & Forward Pricing.

Architecture

A publisher is any service that:
  1. Sources real-time EUR/USD spot prices from Pyth Hermes
  2. Computes forward prices using the interest rate parity formula
  3. Submits prices onchain via OracleModule.publishRound() (one pair) or OracleModule.publishRoundsBatch() (multiple pairs in one transaction)
  4. Records fixing prices for matured timestamps via OracleModule.recordFixingPriceFromPyth()
                    Pyth Hermes API
                          |
                  spot price (EUR/USD)
                          |
                     [Publisher]
                   (PUBLISHER_ROLE)
                          |
              publishRound() / publishRoundsBatch()
              recordFixingPriceFromPyth()
                          |
                   +--------------+
                   | OracleModule |
                   +--------------+
The publisher wallet must hold the 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

1

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.
2

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))
  • r is the annualized forward rate in basis points (operator-configured)
  • t is seconds to maturity
3

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.
4

Record Fixing Prices

For any fixing timestamps that have matured, submit recordFixingPriceFromPyth() with fresh Pyth update data. This locks in the settlement price onchain.

Safeguard Checks

Every publishRound 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.
CheckFormulaDefaultPurpose
Spacingblock.timestamp - lastPublishTime >= minForwardUpdateSpacing10sPrevent rapid-fire updates
Move Limit|newPrice - lastPrice| / lastPrice <= maxOracleMovePerUpdateBps200 bps (2%)Cap single-update price jumps
Deviation vs Prior|newPrice - lastAccepted| / lastAccepted <= maxDeviationVsPriorBps50 bps (0.5%)Limit cumulative drift
Anchor Deviation|newPrice - lastPublishedSpot × IRP| / expected <= maxAnchorDeviationBps150 bps (1.5%)Anchor to Pyth-verified spot × per-pair carry
If any safeguard check fails, the entire publishRound or publishRoundsBatch call reverts. Publishers should handle reverts gracefully and retry on the next cycle. Safeguard parameters are configured onchain by the ORACLE_ADMIN_ROLE.

Forward Round Clearing

Matured forward rounds can be cleared from storage to reclaim gas:
FunctionAccessBehavior
clearForwardRound(pairId, fixingTimestamp)PublicClears one round if matured and no open positions reference it
clearMaturedForwards(pairId)PublicClears all eligible matured rounds
clearAllForwards(pairId)OwnerClears all rounds (skips timestamps with open positions)
Manual clearing is only needed if operators notice excessive storage from old rounds. Any address can call the permissionless entry points.

Pyth Price Conversion

Pyth delivers prices as int64 with an int32 exponent (typically -5 for FX). The protocol expects 18-decimal precision:
if |expo| <= 18:
    price18 = pythPrice * 10^(18 - |expo|)
if |expo| > 18:
    price18 = pythPrice / 10^(|expo| - 18)
Example: Pyth price 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.

Oracle & Forward Pricing

Product-level pricing overview

Oracle Safeguards

Defense-in-depth oracle protection

Keeper Automation

Settlement and liquidation automation