Integrate Oracles
Last updated
Last updated
This guide walks through integrating Mento's on-chain price feeds into your application. The SortedOracles
contract aggregates FX rates from multiple oracle providers (Chainlink, Redstone) to provide reliable exchange rates for stable asset pairs. Mento relayers continuously push these rates on-chain to ensure fresh pricing data.
Use real-time FX rates for cross-currency calculations, collateral valuation, and liquidation thresholds.
Aggregate Mento's FX rates with other sources to provide comprehensive currency data.
Build remittance calculators, multi-currency wallets, or international payment systems using accurate FX rates.
Understand how Mento pools price swaps between stable assets using these oracle rates.
Mento oracle data is available through the SortedOracles
contract:
// Mainnet SortedOracles address
address constant SORTED_ORACLES = 0xefB84935239dAcdecF7c5bA76d8dE40b077B7b33;
// Interface
interface ISortedOracles {
function medianRate(address rateFeedId) external view returns (uint256, uint256);
function numRates(address rateFeedId) external view returns (uint256);
}
See Smart Contracts > Deployments for testnet addresses.
Get FX rates from the sorted oracles contract. Note that rate feed IDs can be either:
Stable token addresses (legacy format, e.g., cUSD address for CELO/USD)
Derived addresses for specific currency pairs (e.g., keccak256("NGNUSD")
for NGN/USD)
Note: For the full list of rate feed IDs check out the Oracles & Price Feeds page
// Example: Get CELO/USD rate using cUSD address as rate feed ID
ISortedOracles oracles = ISortedOracles(SORTED_ORACLES);
address rateFeedId = cUSD_ADDRESS; // Rate feed ID for CELO/USD
(uint256 numerator, uint256 denominator) = oracles.medianRate(rateFeedId);
// Rate represents how much CELO equals 1 USD (with 24 decimal precision)
uint256 celoPerUsd = numerator / denominator;
For cross-currency rates:
// Example: Get NGN/USD rate for cUSD/cNGN pool pricing
address ngnUsdFeedId = 0xC13D42556f1baeab4a8600C735afcd5344048d3C; // keccak256("relayed:NGNUSD")
// This gives USD per 1 NGN
(uint256 ngnUsdRate, uint256 denominator) = oracles.medianRate(ngnUsdFeedId);
Use the FX rates for currency conversions. Note that rates in SortedOracles represent various currency pairs:
// Example: Using NGN/USD rate for conversions
function ngnToUsd(uint256 ngnAmount) public view returns (uint256) {
address ngnUsdFeedId = 0xC13D42556f1baeab4a8600C735afcd5344048d3C;
(uint256 rate, uint256 denominator) = oracles.medianRate(ngnUsdFeedId);
// rate represents USD per 1 NGN (e.g., 0.000654)
return ngnAmount.mul(rate).div(denominator);
}
function usdToNgn(uint256 usdAmount) public view returns (uint256) {
address ngnUsdFeedId = 0xC13D42556f1baeab4a8600C735afcd5344048d3C;
(uint256 rate, uint256 denominator) = oracles.medianRate(ngnUsdFeedId);
// To convert USD to NGN, we need to invert the rate
return usdAmount.mul(denominator).div(rate);
}
These rates are crucial for Mento's pools - they determine the exchange ratios between stable assets (e.g., cUSD/cNGN uses the NGN/USD rate).
Verify that oracle data is fresh and reliable:
// Check report freshness (rates are relayed, so typically only 1 report)
(bool isExpired, ) = oracles.isOldestReportExpired(rateFeedId);
require(!isExpired, "Oracle rate is expired");
// Optional: Check the median timestamp for additional validation
uint256 medianTime = oracles.medianTimestamp(rateFeedId);
require(block.timestamp - medianTime < MAX_AGE, "Rate too old");
Rates use 24 decimal fixed-point precision (1e24)
numerator / denominator
gives the exchange rate
For FX pairs, the rate represents Quote per 1 Base:
NGN/USD: How much USD per 1 NGN (e.g., 0.000654 USD = 1 NGN)
GBP/USD: How much USD per 1 GBP
EUR/XOF: How much XOF per 1 EUR
For CELO pairs (legacy format), the rate represents Base per 1 Quote:
CELO/USD: How much CELO per 1 USD
Oracle reports expire after a configured time period. Expired reports are automatically excluded from median calculations.
Always validate oracle data before use:
Verify rate is within expected bounds
Consider implementing circuit breakers for extreme values
Cache oracle results when multiple operations use the same rate within a transaction.
Handle cases where:
No oracle reports exist for a token
All reports have expired
Rate calculation results in zero denominator
While the contract stores current reports, historical data analysis requires:
Monitoring MedianUpdated events
Maintaining off-chain data stores
Using indexing services like The Graph
import { ethers } from 'ethers';
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const sortedOraclesABI = [...]; // Contract ABI
const sortedOracles = new ethers.Contract(SORTED_ORACLES_ADDRESS, sortedOraclesABI, provider);
// Get exchange rate
const rateFeedId = '0xC13D42556f1baeab4a8600C735afcd5344048d3C'; // NGN/USD
const result = await sortedOracles.medianRate(rateFeedId);
const rate = result.numerator.div(result.denominator);
import { createPublicClient, http } from 'viem';
import { celo } from 'viem/chains';
const client = createPublicClient({
chain: celo,
transport: http(RPC_URL),
});
const sortedOraclesABI = [...]; // Contract ABI
// Get exchange rate
const rateFeedId = '0xC13D42556f1baeab4a8600C735afcd5344048d3C'; // NGN/USD
const result = await client.readContract({
address: SORTED_ORACLES_ADDRESS,
abi: sortedOraclesABI,
functionName: 'medianRate',
args: [rateFeedId],
});
const rate = Number(result.numerator) / Number(result.denominator);
Discord: #general for integration questions
Contract Reference: SortedOracles.sol
For swap functionality using oracle rates, see Integrate the Broker
For contract addresses and ABIs, see Smart Contracts