For the complete documentation index, see llms.txt. This page is also available as Markdown.

Oracle Gateway

Pyth and Switchboard price feeds behind Chainlink's AggregatorV3Interface.

The Oracle Gateway exposes Solana-native price feeds (Pyth, Switchboard V3) through Chainlink's AggregatorV3Interface, so Ethereum protocols porting to Rome keep their existing oracle integration code. Oracle Gateway V2 is live on every public chain, with a keeper guaranteeing feed freshness and a customer portal for registration. The adapters, factory, and keeper model are covered in the rome-oracle-gateway repo.

The problem

Ethereum DeFi expects Chainlink's AggregatorV3Interface:

(, int256 price,,,) = priceFeed.latestRoundData();

Solana's oracle providers (Pyth, Switchboard) have different data formats. Without adaptation, every ported protocol would need custom oracle code.

The solution

Oracle Gateway V2 deploys lightweight adapter contracts that:

  1. Read price data directly from Pyth or Switchboard accounts on Solana (cross-state reads, not CPI)

  2. Parse the on-chain data

  3. Normalize prices to 8 decimals

  4. Expose the standard Chainlink AggregatorV3Interface

import {IAggregatorV3Interface} from "@rome-protocol/rome-solidity/contracts/oracle/IAggregatorV3Interface.sol";

// Same interface as Chainlink on Ethereum
(, int256 price,,,) = IAggregatorV3Interface(ADAPTER).latestRoundData();
// price = SOL/USD at 8 decimals (e.g. 15000000000 = $150.00)

Consume a feed in three steps

  1. Resolve the adapter address from the registry — never hardcode it:

  1. Read it with the interface your protocol already uses — IAggregatorV3Interface(solUsd).latestRoundData(). A stale or uninitialized feed reverts rather than serving a frozen price.

  2. Several feeds at once? BatchReader.getLatestPrices(adapters[]) (Hadrian: 0x306d670dff7f51ae33f263f5122bd2b18d98adc7) — and getFeedHealth(adapters[]) before you depend on them. Cached adapters make each consumer read a plain SLOAD, so multi-feed transactions (a multi-collateral borrow, a portfolio view) stay cheap.

cardo consumes these feeds in production today. Because the surface is exactly AggregatorV3Interface, Compound- and Aave-class protocols are drop-in — point their price-feed configuration at the adapter addresses; their oracle code needs zero changes.

Adapter types

  • PythPullAdapter — reads Pyth price accounts (price, confidence, EMA, publish time).

  • SwitchboardV3Adapter — reads Switchboard aggregator accounts (price, timestamp; no EMA).

  • CachedPyth / CachedFeed adapters — cached-track variants for CU-efficient reads.

Adapters are deployed as EIP-1167 minimal-proxy clones by the OracleAdapterFactory and expose an extended interface alongside AggregatorV3Interface (latestPriceData, maxStaleness, oracleType, and a metadata() surface used for health checks).

Freshness — the oracle-keeper

Adapters enforce a maxStaleness window: if block.timestamp - publishTime > maxStaleness, reads revert. On Solana mainnet, Pyth runs production keepers. On Solana devnet (where the public chains live), Pyth's pushers are best-effort, so Rome runs its own oracle-keeper sidecar to keep the underlying Pyth accounts fresh. Freshness is monitored and alerted on.

Check feed health with BatchReader.getFeedHealth (which reads each adapter's metadata()), not just latestRoundData — a feed can be reachable but stale.

Customer portal

Register feeds and configure consumers per chain through the Oracle Gateway portal. See the portal page for what it covers.

Deployed addresses

Canonical per-chain addresses (factories, adapter implementations, and the live feeds) live in the registry. Headline factories:

Contract
Hadrian
Martius

OracleAdapterFactory

0xe68e7bc697010c73f1798b356f8ae2f0ba1319db

0xbc06fe9603a02ff4aead265c253f5c6303ee5fbb

BatchReader

0x306d670dff7f51ae33f263f5122bd2b18d98adc7

0xc5e6d932bfd2b4da27643848063905f46861fae8

Constraints

  • No historical round datagetRoundData(roundId) reverts; only latestRoundData() is supported.

  • Switchboard EMA not supported — EMA data is Pyth-only.

  • Per-chain stalenessmaxStaleness is set per environment at bring-up: direct (Pyth-read) adapters default to ~60 seconds; cached adapters default to 3600 seconds (the keeper refreshes far more often — ~25s on devnet/testnet today).

  • Parser offsets are validated against current Pyth/Switchboard layouts; layout changes require re-validation.

What's Next

Last updated

Was this helpful?