Skip to main content
The keeper service scans for matured and liquidatable positions and executes batch settlement and liquidation onchain. Because every entry point is permissionless, protocol liveness does not depend on a single operator. This page describes the protocol-level behavior of a keeper — operators building their own keeper can use it as a reference for the onchain interface they must target.
For the publisher service that keeps forward prices fresh onchain, see Publisher Operations.

Architecture

A keeper is any service that:
  1. Reads open position state via PositionManager.getPositionStats(pairId)
  2. Settles matured positions via SettlementEngine.batchSettlePositions()
  3. Liquidates undercollateralized positions via SettlementEngine.batchLiquidatePositions()
  4. Optionally clears matured forward rounds to reclaim gas via OracleModule.clearMaturedForwards()
                  PositionManager
                          |
                getPositionStats() (read)
                          |
                      [Keeper]
                  (permissionless)
                          |
              batchSettlePositions()
              batchLiquidatePositions()
                          |
                +--------------------+
                | SettlementEngine   |
                +--------------------+
The keeper is fully permissionless — any Ethereum address can call the settle and liquidate entry points. No role assignment is required. All other operational choices — cycle interval, retry strategy, telemetry, failover — are implementation decisions left to the operator.
What happens if the keeper goes down? Settlement and liquidation functions are fully permissionless — anyone can call them directly on the SettlementEngine contract. If the official keeper is offline, positions can still be settled and liquidated by any Ethereum address. Protocol correctness does not depend on keeper liveness for safety, only for timeliness. The keeper is a convenience, not a requirement.

Keeper Cycle

The keeper polls for matured and liquidatable positions, then executes batch operations. Settlement and liquidation are independent — one failing does not block the other.
1

Query Position Stats

Call getPositionStats(pairId) (read-only) to get counts of matured and liquidatable positions for the EUR/USD pair.
2

Settle Matured Positions

If matured count is greater than 0, call batchSettlePositions(pairId, maxCount) to settle matured positions in a single transaction. Matured positions that are also liquidatable are skipped by batch settlement and require a separate liquidation call.
3

Liquidate Eligible Positions

If liquidatable count is greater than 0, call batchLiquidatePositions(pairId, maxCount) to liquidate underwater positions in a single transaction.

Permissionless Entry Points

Anyone can call these functions — no special role is required. This ensures the protocol can always make progress even if the official keeper goes offline.
FunctionDescriptionGas Refund
batchSettlePositions(pairId, maxCount)Settle matured positions in batchNo
settlePosition(positionId)Settle a single matured positionNo
batchLiquidatePositions(pairId, maxCount)Liquidate eligible positions in batchNo
liquidatePosition(positionId)Liquidate a single eligible positionNo
recordFixingPriceFromPyth(pairId, fixingTimestamp, pythUpdateData[])Record fixing price from PythNo (requires msg.value for Pyth fee)
clearForwardRound(pairId, fixingTimestamp)Clear matured forward storageYes (SSTORE deletion refund)
clearMaturedForwards(pairId)Clear all matured forward roundsYes
checkOracleAndPause()Check oracle validity, auto-pause if invalidNo
recordFixingPriceFromPyth requires sending ETH as msg.value to cover the Pyth oracle update fee. The exact fee can be queried from the Pyth contract’s getUpdateFee() function.

Gas Cost Responsibility

The current M2 design does not include direct economic incentives for keeper operators. This is intentional for the testnet phase.
  • Settlement and liquidation callers pay gas but receive no direct reward
  • Liquidation penalty goes to fee destinations (treasury + pool), not to the liquidator
  • Forward clearing provides SSTORE refunds — deleting storage slots returns a gas rebate per EIP-3529
  • Oracle fixing requires the Pyth update fee as msg.value in addition to gas costs

Position-Aware Oracle Cleanup

Forward price data for matured timestamps is no longer needed once all positions at that fixing time are settled. The cleanup functions include safety checks:
  • clearForwardRound(pairId, fixingTimestamp) checks positionManager.openPositionCountAtFixingTs(pairId, fixingTimestamp). If any open positions still reference that fixing timestamp, the clear is silently skipped (not reverted). This prevents clearing forward data that is still needed for open position PnL calculations.
  • clearMaturedForwards(pairId) resets safeguard baselines (spacing, move limit tracking) after clearing, ensuring fresh publish cycles start clean. This is useful for batch cleanup after all positions at multiple fixing timestamps have been settled.
Forward clearing is a good candidate for gas-efficient maintenance. The SSTORE deletion refunds can offset a significant portion of the transaction cost, making it economically viable for anyone to call.
In M2, the keeper is operated by the protocol team and there are no direct keeper incentives. M3 plans include economic incentives (keeper rewards, MEV-aware settlement) to attract external keeper operators.

Publisher Operations

Forward price publication and fixing

Oracle & Forward Pricing

Product-level pricing overview

Oracle Safeguards

Defense-in-depth oracle protection