ETH+
Custom price feed for Reserve Protocol's ETH+ RToken on Ethereum.
Custom price feed for Reserve Protocol's ETH+ RToken. ETH+ is derived from the value of the collateral basket that backs each token, using Reserve Protocol's on-chain basket accounting.
Source: ETHPlus_CustomPriceFeed.sol
This feed is built and tested but deployed on no chain, and ETH+ is not priced by it. ETH+ is registered against a direct Chainlink "Calculated ETH+/USD" feed with an 86400 heartbeat, so it follows the ordinary direct-feed path — not the basket logic described below.
The two cannot co-exist: this Custom feed reports 8 decimals while the Chainlink feed reports 18, and an asset's feeds must agree on decimals (PriceFeedDecimalsMismatch). The contract is kept deliberately, as the record of why a custom feed was not used for ETH+. Everything below documents the contract's behaviour, not how ETH+ is valued today.
Approach
(basketsNeeded / totalSupply) × basket low price — the conservative (low-bound) USD value of the basket backing one ETH+.
Price calculation
basketsNeeded
IRToken(ETHPLUS).basketsNeeded()
Basket units the RToken must be backed by (18 dec)
totalSupply
IRToken(ETHPLUS).totalSupply()
ETH+ total supply
basketLow
IBasketHandler.price() (low bound)
Conservative USD value of one basket unit (18 dec)
Computation is carried at 18-decimal precision and divided by 1e10 to yield the 8-decimal USD price. The low price bound from basketHandler.price() is used deliberately for conservative NAV valuation. If basketsNeeded, totalSupply, the per-token basket units, or the basket price is zero, the feed returns stale.
Constructor
ethPlus_
The ETH+ RToken address
reserveProtocolHeartbeat_
Max age (seconds) of the worst-case collateral refresh before the price is stale
Staleness
getLatestPrice() enforces two internal checks; no external Chainlink heartbeat is read directly:
Basket status —
IBasketHandler.status()must beSOUND. AnIFFYorDISABLEDbasket returns stale, even though the basket may still report a non-zero price.Refresh age —
updatedAt = min(IAsset.lastSave())across all components of the active reference basket, read viaIBasketHandler.quote(1e18, RoundingMode.CEIL)— the same basketprice()/basketsNeeded()are computed over, rather than the governance-configured prime basket.lastSave()is set when a keeper callsrefresh()on a collateral plugin (which reads the underlying Chainlink oracle). Ifblock.timestamp − updatedAt ≥ reserveProtocolHeartbeat, the price is stale. Taking the minimum gives the worst-case freshness of the whole basket. Using the active set matters after a default swaps in backup collateral: the active backups differ from the prime config, and checking the prime set would skip the backups' oracle freshness (stale accepted as fresh). The deployedBasketHandlerhas nobasketTokens()getter, soquoteis used to read the live basket.
latestRoundData() exposes updatedAt = min(lastSave()) for observability only; staleness is handled entirely in getLatestPrice().
Chains
Deployed on Ethereum mainnet.
Last updated