On-chain price oracle · real-world asset price feeds

Price oracle for smart contracts: forex, gold, indices and stocks on-chain

Fetch a signed price report from our API and pass it to your contract. Our verifier contract checks the signature on-chain, so your contract can trust the price without trusting the messenger.

How it works

  1. Fetch a signed report: GET /oracle/report with your API key.
  2. Submit it in your transaction: our verifier contract checks the signature with ecrecover.
  3. Use the verified price, reject it if it is too old for your use case.

Reports are EIP-712 typed data PriceReport(bytes32 feedId,int256 price,uint64 timestamp,uint64 validUntil,uint8 marketStatus,uint32 spreadBps), bound to one chain and one verifier contract (no replay elsewhere) and accepted only until validUntil, about a minute after signing (no hoarding of favourable reports). Prices are the mid of bid and ask with 8 decimals; spreadBps is the bid-ask spread. When you submit a report in your own transaction, you pay that gas. Feeds in demand are also stored on-chain by our relayer from time to time (free to read), but always check a stored price's age.

Market hours

Every report states the market status: OPEN (a live price, at most 60 seconds old), CLOSED (the official last price before the close, the same for everyone, signed at any time while the market is closed) or HALTED (trading stopped). getPriceNoOlderThan only returns OPEN prices, so a closing price can never pass as live. Asset-backed tokens should settle orders placed while the market is closed at the next OPEN price (forward pricing, as funds and ETFs do).

Circuit breaker

Each feed can have a maximum allowed jump. A larger move is not stored: the feed pauses until we have checked it and re-opened it, so a wrong or manipulated price never reaches your contract.

Feeds

SymbolfeedId
AUDUSD0x5577fa17b1be8ae0f1517fe1864ea534d6992f3721000cdb6585b11e7046d5ec
EURGBP0xeaff3420d70d388655273db9b4f3000962445a6f0ea23ec1f04d843e4dbae4b0
EURUSD0x0730076c3c0f5da594eb635c5d5a22116233bdb2a9c2977279cc348b8b8ce917
GBPUSD0xd30842147b60fe333c2700bcae5eee6815b8529847955d19522858806bca8217
IEF0x309dab183458dd939568dca0d68bea45d34aa25d31fa47fc5f12030e8f227f4e
OILCash#0xe21b90a237aa0663c02092eca221a0098c6c477054a61d05ecd3692f6631f6ff
SHY0x78f1083af8e72ddefc2e66fad95680c38a953b3136f413c61ab40f99b72991bc
SPY0xcea23e8f56b5ac447ac08d57e0808e35f468a65f29bb659b3d48934481e7c67a
TLT0x4abd434857e07567d49487ccf888348e55d5cfc15498c09a9dbcba2820e40ee4
USDCAD0xa36f6cf3195abb4e93bf276660acee423f78878e6cfdbad0eab36e455afca97e
USDCHF0xd85af9ebd6fec6b317a7ac07f6d6455c4fc915cc8e1cfe19471faca002582907
USDJPY0x3d6c40e9d821c10e442cb3fc6cb1c589ffd8b906e734a1ab3b3f7d213ac395d6
USDSGD0x8a1375bd16e33aff9be7037f83e076defe9df6efea6d39a51d0b08f17ac9bc6d
XAGUSD0x67107df7f0e95594d9bf8ad8840bbb2ff95617123c227e10aab4d161dd6959b6
XAUUSD0xe21c86d8b6a127bfef214d88fdb0c279e55d27dd8c443733e46c8d3de3c98cd6

Networks

NetworkChain IDVerifier contract
Optimism100xaeF7f701acFB42267015D3F593ce17CF536Afd6B
Optimism Sepolia testnet111554200xBBf36E7Cd75494f28E02C5698EaD35fF4960a943

Signer address: 0x83fa2d8836525251FAd214A78A72038D0864f877. Machine-readable list: /oracle/feeds.

Integrate

1. Fetch a signed report

curl -H "Authorization: Bearer $API_KEY" \
  "https://api.marketapi.app/v1/oracle/report?symbols=EURUSD&chain_id=10"

Returns report (the ABI-encoded tuple) and signature (65 bytes) per symbol. Your key needs the read:oracle scope.

2. Verify it in your contract

interface IMarketApiOracle {
    struct PriceReport { bytes32 feedId; int256 price; uint64 timestamp; uint64 validUntil;
                         uint8 marketStatus; uint32 spreadBps; }
    function submit(PriceReport calldata r, bytes calldata sig) external returns (bool);  // verifies, stores if newer
    function getPriceNoOlderThan(bytes32 feedId, uint256 maxAge)
        external view returns (int256 price, uint64 timestamp);   // OPEN and fresh only, else reverts
    function getPrice(bytes32 feedId) external view
        returns (int256 price, uint64 timestamp, uint8 marketStatus, uint32 spreadBps);   // incl. CLOSED
}

contract MyVault {
    IMarketApiOracle immutable oracle;
    bytes32 constant EURUSD = keccak256("EURUSD");
    constructor(IMarketApiOracle o) { oracle = o; }

    function act(IMarketApiOracle.PriceReport calldata r, bytes calldata sig) external {
        oracle.submit(r, sig);                                       // reverts on a bad signature
        (int256 price, ) = oracle.getPriceNoOlderThan(EURUSD, 60);   // 8 decimals
        // ... use price ...
    }
}

Chainlink-style consumers can read a feed through the MarketApiFeedAdapter (latestRoundData(), AggregatorV3Interface).

Pricing

5 credits per signed report, paid from your normal API balance. Reading prices already stored on-chain is free.

Use cases for our on-chain price feeds

Why a signed pull oracle?

Push oracles update on a schedule and someone pays gas for every update. With a pull oracle your contract fetches a fresh, cryptographically signed price exactly when it needs it: lower cost, fresher data and the same on-chain verification. Market status, bid-ask spread and expiry are part of every signed report.

Price oracle FAQ

What is a blockchain price oracle?

A price oracle brings off-chain market prices to smart contracts. Our oracle signs each price with an EIP-712 signature; your contract verifies it on-chain with ecrecover before using it, so the price cannot be forged in transit.

Which assets do the price feeds cover?

Forex pairs such as EUR/USD, precious metals such as gold (XAU/USD) and silver, stock indices and individual stocks, as published in the feed list on this page. Every feed has a fixed feedId (keccak256 of the symbol).

Is it suitable for RWA and tokenized assets?

Yes. Reports carry the market status (open, closed, halted), so tokenized stocks, commodities and other real-world assets can apply forward pricing outside market hours instead of trading on a stale price.

Is the oracle Chainlink-compatible?

Each feed can be read through an adapter that implements the Chainlink AggregatorV3Interface (latestRoundData), so existing DeFi code that reads Chainlink-style price feeds works with minimal changes.

How is manipulation prevented?

Reports expire within about a minute and must be newer than the stored price, a per-feed circuit breaker pauses a feed after an abnormal jump, and with several brokers the price is the median of independent sources with outliers removed.

Who pays the gas fees?

It is a pull oracle: your application submits the signed report in its own transaction and pays that gas. Our relayer also stores feeds that are in demand on-chain from time to time; reading stored prices is free, but check their age before use.

Which blockchains are supported?

Any EVM chain where the verifier contract is deployed, for example Ethereum, Base, Arbitrum, Optimism, BNB Smart Chain and Polygon and their testnets; the current list is shown above.

Recommended: the pull pattern (always-fresh prices)

Do not rely on the price already stored on-chain. Fetch a freshly signed report and submit it in the same transaction that uses it: your contract always sees the current price, and you pay only for your own updates.

// 1) off-chain: GET https://api.marketapi.app/v1/oracle/report?symbols=EURUSD&chain_id=CHAIN_ID  (header: Authorization: Bearer KEY)
// 2) on-chain: verify, store and use the report atomically
interface IMarketApiOracle {
    struct PriceReport { bytes32 feedId; int256 price; uint64 timestamp; uint64 validUntil; uint8 marketStatus; uint32 spreadBps; }
    function submit(PriceReport calldata r, bytes calldata sig) external returns (bool);
    function getPriceNoOlderThan(bytes32 feedId, uint256 maxAge) external view returns (int256 price, uint64 timestamp);
}

contract MyVault {
    IMarketApiOracle immutable oracle;
    bytes32 constant EURUSD = keccak256("EURUSD");
    constructor(IMarketApiOracle o) { oracle = o; }

    function settle(IMarketApiOracle.PriceReport calldata r, bytes calldata sig) external {
        oracle.submit(r, sig);                                         // ignored if an equal/newer price is stored
        (int256 price, ) = oracle.getPriceNoOlderThan(EURUSD, 60);    // reverts unless OPEN and fresh
        // ... use price (8 decimals)
    }
}

The report's report and signature fields from the API are exactly the two arguments of submit. Reports are valid for about a minute, so fetch them right before sending the transaction.

Risk notice. Prices are indicative market data from our source, not an execution venue. Smart contracts that move funds based on any oracle must check freshness, handle missing updates and cap the impact of a wrong price. Read the terms before relying on the oracle in production.