|
| 1 | +--- |
| 2 | +sidebar_position: 2 |
| 3 | +title: Conflux Price Oracle (Pyth-Compatible) |
| 4 | +description: Migrate from Pyth Network to the Conflux-maintained price oracle before the July 31, 2026 sunset |
| 5 | +keywords: |
| 6 | + - Conflux eSpace |
| 7 | + - Oracle |
| 8 | + - Price Feed |
| 9 | + - Pyth Network |
| 10 | + - Pyth sunset |
| 11 | + - Migration |
| 12 | + - Smart Contracts |
| 13 | + - Solidity |
| 14 | + - Price Feed ID |
| 15 | + - CFX Price |
| 16 | + - Mainnet |
| 17 | + - Testnet |
| 18 | + - Contract Addresses |
| 19 | +displayed_sidebar: eSpaceSidebar |
| 20 | +tags: [Oracles, Pyth, Migration] |
| 21 | +--- |
| 22 | + |
| 23 | +# Conflux Price Oracle (Pyth-Compatible) |
| 24 | + |
| 25 | +:::danger Pyth Network sunset — July 31, 2026 |
| 26 | +Pyth Network has announced it is **shutting down Conflux eSpace support on July 31, 2026**. Contracts that read prices from the Pyth contract on eSpace will stop receiving updates after that date. |
| 27 | + |
| 28 | +A **Conflux-maintained, Pyth-compatible price oracle** is deployed as a drop-in read-side replacement. Existing integrators can migrate by pointing at a new contract address — the read functions and price feed IDs are unchanged for the major assets. |
| 29 | +::: |
| 30 | + |
| 31 | +The replacement oracle is an on-chain price feed service maintained for the Conflux community, with source code at [conflux-fans/oracle-contracts](https://github.com/conflux-fans/oracle-contracts). Its spot price read API is compatible with the [Pyth SDK Solidity interface](https://github.com/pyth-network/pyth-sdk-solidity) (`getPriceUnsafe`, `getPriceNoOlderThan`), returning the same `PythStructs.Price` type. The EMA price functions are **not supported** — see [EMA prices are not available](#ema-prices-are-not-available). |
| 32 | + |
| 33 | +## Contract addresses |
| 34 | + |
| 35 | +| Network | Address | |
| 36 | +| --- | --- | |
| 37 | +| Conflux eSpace Mainnet (chain id 1030) | `0x5286BD91e2C79fE066926a15193C7e531bBF6750` | |
| 38 | +| Conflux eSpace Testnet (chain id 71) | `0x838c40B3904FAfBc21b670c97b0dFeE7D8D0a016` | |
| 39 | + |
| 40 | +These are the proxy addresses — the oracle uses a UUPS upgradeable proxy, so the address stays stable across logic upgrades. Always integrate against the proxy. |
| 41 | + |
| 42 | +## Supported price feeds |
| 43 | + |
| 44 | +All feeds use `expo = -8` (consistent with Pyth crypto feeds): real price = `price × 10⁻⁸`. |
| 45 | + |
| 46 | +| Asset | Price Feed ID (bytes32) | Update Frequency | |
| 47 | +| --- | --- | --- | |
| 48 | +| BTC/USD | `0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43` | 1h | |
| 49 | +| ETH/USD | `0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace` | 1h | |
| 50 | +| USDT/USD | `0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b` | 1h | |
| 51 | +| USDC/USD | `0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a` | 1h | |
| 52 | +| CFX/USD | `0x8879170230c9603342f3837cf9a8e76c61791198fb1271bb2552c9af7b33c933` | 1h | |
| 53 | +| BNB/USD | `0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f` | 1h | |
| 54 | +| AxCNH/USD | `0x6412f0e5469e5ab64fccf0eea916ae6db2bcd56568daaaf583b3054d465e8e2d` | 1h | |
| 55 | + |
| 56 | +Notes: |
| 57 | + |
| 58 | +- The BTC, ETH, USDT, USDC, CFX, and BNB feed IDs are **the same IDs Pyth uses**, so consumers of those feeds do not need to change their stored feed IDs. |
| 59 | +- **There is no separate USDT0 feed.** USDT0 is the [LayerZero OFT form of USDT](https://docs.usdt0.to/), backed 1:1 by USDT locked on Ethereum and operated by Everdawn Labs under licence from Tether, so its price depends on that bridging layer in addition to USDT itself. Consumers that want a USDT0 price from this oracle read the USDT feed. |
| 60 | +- AxCNH/USD is a new feed, with its ID derived from `keccak256("ConfluxOracle.AxCNH/USD")`. |
| 61 | + |
| 62 | +## EMA prices are not available |
| 63 | + |
| 64 | +The oracle does not provide EMA (exponentially-weighted moving average) prices. Use the spot price functions — `getPriceUnsafe` and `getPriceNoOlderThan` — for all reads. |
| 65 | + |
| 66 | +:::warning Mainnet and testnet behave differently |
| 67 | +On **mainnet**, `getEmaPriceUnsafe`, `getEmaPriceNoOlderThan`, and the deprecated `getEmaPrice` all revert with `EmaPrice not supported`, for every feed. |
| 68 | + |
| 69 | +On **testnet**, these calls currently do *not* revert — they return the spot price. Do not treat that as EMA data, and do not rely on it: code that reads EMA prices will appear to work on testnet and then revert on mainnet. |
| 70 | +::: |
| 71 | + |
| 72 | +If you are migrating a contract that reads `getEmaPrice*` from Pyth, switch those call sites to the spot price functions before pointing at this oracle. |
| 73 | + |
| 74 | +## Migrating from Pyth |
| 75 | + |
| 76 | +The oracle is push-based: authorized updaters publish prices on a roughly hourly schedule. This changes the integration in two ways compared to Pyth's pull model: |
| 77 | + |
| 78 | +1. **Point your consumer contract at the new oracle address** (table above) instead of the Pyth contract address. |
| 79 | +2. **Remove the Hermes/update flow.** `updatePriceFeeds(bytes[])`, `updatePriceFeedsIfNecessary(...)`, and `getUpdateFee(bytes[])` are **not implemented on this contract at all** — there is no update fee and nothing to submit. Leftover calls to them will not silently no-op; they hit the fallback and revert. Delete any code that fetched update data from Hermes and attached a fee. |
| 80 | + |
| 81 | +Reads take a staleness bound: `getPriceNoOlderThan` reverts with `StalePrice()` when the stored price is older than the bound you pass. Since prices are published about once per hour, a stored price is continuously ageing towards that interval between updates. |
| 82 | + |
| 83 | +```solidity |
| 84 | +// SPDX-License-Identifier: MIT |
| 85 | +pragma solidity ^0.8.19; |
| 86 | +
|
| 87 | +import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; |
| 88 | +import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; |
| 89 | +
|
| 90 | +contract CfxPriceConsumer { |
| 91 | + IPyth public immutable oracle; |
| 92 | + /// @notice Staleness bound in seconds, chosen by the integrator. |
| 93 | + uint public immutable maxPriceAge; |
| 94 | + bytes32 public constant CFX_USD = |
| 95 | + 0x8879170230c9603342f3837cf9a8e76c61791198fb1271bb2552c9af7b33c933; |
| 96 | +
|
| 97 | + constructor(address oracleAddress, uint maxPriceAge_) { |
| 98 | + oracle = IPyth(oracleAddress); |
| 99 | + maxPriceAge = maxPriceAge_; |
| 100 | + } |
| 101 | +
|
| 102 | + /// @notice Returns the CFX/USD price with expo -8 (price × 10⁻⁸ = USD). |
| 103 | + function getCfxPrice() external view returns (PythStructs.Price memory) { |
| 104 | + // Reverts with StalePrice() if the stored price is older than maxPriceAge. |
| 105 | + return oracle.getPriceNoOlderThan(CFX_USD, maxPriceAge); |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +:::caution Do not use the deprecated `getPrice(bytes32)` |
| 111 | +`getPrice(id)` is exactly `getPriceNoOlderThan(id, getValidTimePeriod())` — it is the same code path, with the contract's own validity window supplied as the bound. That window is currently **3600 seconds**, the same ~1 hour cadence on which prices are published, so any delay in an update pushes the stored price past it and the call reverts with `StalePrice()` (`0x19abf40e`). |
| 112 | + |
| 113 | +Because it is the same code path, passing `3600` to `getPriceNoOlderThan` reproduces the identical behaviour. What makes `getPriceNoOlderThan` usable is choosing a bound with headroom over the publish interval, not the function itself. |
| 114 | +::: |
| 115 | + |
| 116 | +You can verify the oracle is live from the command line: |
| 117 | + |
| 118 | +```bash |
| 119 | +cast call 0x5286BD91e2C79fE066926a15193C7e531bBF6750 \ |
| 120 | + "getPriceUnsafe(bytes32)((int64,uint64,int32,uint256))" \ |
| 121 | + 0x8879170230c9603342f3837cf9a8e76c61791198fb1271bb2552c9af7b33c933 \ |
| 122 | + --rpc-url https://evm.confluxrpc.com |
| 123 | +``` |
| 124 | + |
| 125 | +## Trust and limitations |
| 126 | + |
| 127 | +Understand the differences from Pyth before relying on the oracle in production: |
| 128 | + |
| 129 | +- **Update cadence is ~1 hour.** Prices can therefore be up to roughly an hour behind spot at any given moment. |
| 130 | +- **Push-based with role-based access control.** Prices are published by accounts holding `UPDATER_ROLE`, and the contract is upgradeable by its admin — a different trust model from Pyth's decentralized publisher network. Review the [contract source](https://github.com/conflux-fans/oracle-contracts) and its role holders as part of your own due diligence. |
| 131 | +- **Reads revert rather than returning sentinel values.** An unknown feed ID reverts with `PriceFeedNotFound()` (`0x14aebe68`); a price older than the bound passed to `getPriceNoOlderThan` reverts with `StalePrice()` (`0x19abf40e`). Neither returns a zero or a flag, so a failed read propagates as a transaction failure unless the caller explicitly catches it. |
| 132 | +- **`getPriceUnsafe` performs no staleness check.** It returns the stored price at any age, and reverts only when a feed has never been published. If the updater stops — key rotation, infrastructure outage, funding lapse — consumers keep reading the last published price indefinitely, with no revert and no on-chain signal. The returned `publishTime` is the only indication of age. `getPriceNoOlderThan` applies an age bound you supply and reverts with `StalePrice()` when exceeded. |
| 133 | +- **`getValidTimePeriod()` constrains only the deprecated `getPrice` / `getEmaPrice`.** For `getPriceNoOlderThan`, the bound you pass is the only value consulted — the contract's validity window plays no part. |
| 134 | + |
| 135 | +## Further reading |
| 136 | + |
| 137 | +- [conflux-fans/oracle-contracts](https://github.com/conflux-fans/oracle-contracts) — source, deployment scripts, and integration examples |
| 138 | +- [Pyth tutorial](./Pyth/priceFeed.md) — the original Pyth integration tutorial (applicable until the sunset date) |
0 commit comments