// SPDX-License-Identifier: MIT pragma solidity 0.8.26; /// @title HYPE/USD price for Ambrosia mints /// @notice Elysium has no trusted feed yet. Two fixed feeders post the median of several venues. /// The contract bounds how fast and how far they can move it, and refuses stale prices. /// Price has 8 decimals (73.12 USD = 7_312_000_000). contract HypeUsdOracle { uint256 public constant MAX_MOVE_BPS = 500; // 5% per update uint256 public constant MIN_INTERVAL = 60; uint256 public constant MAX_AGE = 15 minutes; uint256 public constant FLOOR = 5e8; // $5 uint256 public constant CEIL = 500e8; // $500 address public immutable feederA; address public immutable feederB; uint128 public price; uint64 public updatedAt; event Price(uint256 price, uint256 at, address feeder); error NotFeeder(); error TooSoon(); error OutOfBand(); error MoveTooLarge(); error StalePrice(); constructor(address a, address b, uint256 initialPrice) { if (initialPrice < FLOOR || initialPrice > CEIL) revert OutOfBand(); feederA = a; feederB = b; price = uint128(initialPrice); updatedAt = uint64(block.timestamp); emit Price(initialPrice, block.timestamp, msg.sender); } function post(uint256 p) external { if (msg.sender != feederA && msg.sender != feederB) revert NotFeeder(); if (block.timestamp < updatedAt + MIN_INTERVAL) revert TooSoon(); if (p < FLOOR || p > CEIL) revert OutOfBand(); uint256 last = price; uint256 diff = p > last ? p - last : last - p; if (diff * 10_000 > last * MAX_MOVE_BPS) revert MoveTooLarge(); price = uint128(p); updatedAt = uint64(block.timestamp); emit Price(p, block.timestamp, msg.sender); } /// @notice Reverts when the last update is older than 15 minutes. function fresh() external view returns (uint256) { if (block.timestamp > updatedAt + MAX_AGE) revert StalePrice(); return price; } }