> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publisher Operations

> Forward price publisher interface: Pyth Hermes spot, F = S*(1 + r*t/365) interest rate parity, onchain safeguards (spacing 10s, move limit 200 bps, deviation vs prior 50 bps).

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.

<Note>
  For a product-level overview of how forward pricing works, see [Oracle & Forward Pricing](/protocol/oracle-pricing).
</Note>

## 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

<Steps>
  <Step title="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.
  </Step>

  <Step title="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
  </Step>

  <Step title="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.
  </Step>

  <Step title="Record Fixing Prices">
    For any fixing timestamps that have matured, submit `recordFixingPriceFromPyth()` with fresh Pyth update data. This locks in the settlement price onchain.
  </Step>
</Steps>

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

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

<Warning>
  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`.
</Warning>

## 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)       |

<Tip>
  Manual clearing is only needed if operators notice excessive storage from old rounds. Any address can call the permissionless entry points.
</Tip>

## 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).

<Info>
  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.
</Info>

## Related Pages

<Columns cols={3}>
  <Card title="Oracle & Forward Pricing" icon="satellite-dish" href="/protocol/oracle-pricing">
    Product-level pricing overview
  </Card>

  <Card title="Oracle Safeguards" icon="shield-halved" href="/protocol/oracle-safeguards">
    Defense-in-depth oracle protection
  </Card>

  <Card title="Keeper Automation" icon="robot" href="/deploy/keeper-automation">
    Settlement and liquidation automation
  </Card>
</Columns>
