Skip to main content
This guide provides detailed instructions for integrating Chaos price oracles with EVM-compatible blockchains.

How to Use the Push Oracle

This section explains the main methods and patterns for integrating the Chaos push oracle into your EVM smart contracts, including fetching prices, accessing historical data, and subscribing to updates.

1. Set up your environment.

  • Import the Chaos push oracle contract Application Binary Interface (ABI).
  • Identify the deployed contract address for your desired network, such as the Ethereum mainnet and testnets.
Example:
EdgePushOracle oracle = EdgePushOracle(ORACLE_CONTRACT_ADDRESS);

2. Fetch the latest price

Use the latestAnswer() function to obtain the most recent price. The result is an integer representing the price, formatted with the specified number of decimals.
int256 latestPrice = oracle.latestAnswer();

3. Access historical data

Retrieve historical price data for specific rounds using the getRoundData function. Each round provides a comprehensive dataset, including the price, timestamp, and block number.
(int256 price, uint256 reportRoundId, uint256 timestamp, uint256 blockNumber) = oracle.getRoundData(ROUND_ID);

4. Monitor updates in real-time

Subscribe to the NewPriceUpdate event to react to new price reports. This is particularly useful for applications requiring immediate responses to market changes. Example:
event NewPriceUpdate(uint80 indexed roundId, int256 price, uint256 reportRoundId, uint256 timestamp, address transmitter, uint256 numSignatures);

Push Contract Integration

Here’s a basic example demonstrating how to integrate the Chaos push oracle into your smart contract:
The following code is for illustrative purposes only, has not been audited, and should not be used in production without thorough testing.
pragma solidity ^0.8.25;

interface IEdgePushOracle {
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
    function decimals() external view returns (uint8);
    function description() external view returns (string memory);
}

contract OracleConsumer {
    IEdgePushOracle public oracle;

    constructor(address oracleAddress) {
        oracle = IEdgePushOracle(oracleAddress);
    }

    /**
     * @notice Fetch the latest price data and metadata from the oracle.
     * @return roundId The round ID of the latest price update.
     * @return answer The latest price reported by the oracle.
     * @return updatedAt The timestamp when the latest price was updated.
     */
    function getLatestPriceData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 updatedAt
        )
    {
        (roundId, answer, , updatedAt, ) = oracle.latestRoundData();
        return (roundId, answer, updatedAt);
    }
}

Important Considerations

  • Decimals handling The latestAnswer() and getRoundData() functions return prices as integers. Use the decimals property of the oracle to format the values for display or further calculations.
  • Timestamp validation If the precise timing is crucial, then use the getRoundData() function to validate the timestamp of the data retrieved.
  • Real-time updates Utilize the NewPriceUpdate event to build reactive systems that trigger actions based on price changes.

How to Use the Pull Oracle

To integrate with the pull oracle, you will need to fetch signed price data from the Chaos API and then use it in your smart contract.

Step 1: Obtain API Keys and Signer Address

Before you can fetch data or verify signatures, you must know the oracle’s trusted signer address. Contact the Chaos team to obtain your API keys and the signer address required for verification.

Step 2: Fetch Signed Price Data

Using your API key, make a GET request to the /prices/evm endpoint to retrieve the latest EVM-signed price data. You can find more details about the endpoint in the API reference. The API response will be a JSON object containing an array of prices. Here is an example of a single price object in the response:
{
  "prices": [
    {
      "feedId": "BTCUSD",
      "price": 11938032500000,
      "ts": 1753084191,
      "expo": -8,
      "signature": "314940ee402ec666daf1ade015066cf5d4ee3010b04a9710c32b48a2c916ca690f621d7bbdd230e32559ee7ac6dafbbc7ef9efb8780ff06efdd5ca5faa2cce8c1c",
      "recoveryId": 0,
      "roundId": 74136435,
      "bid": 11938032000000,
      "ask": 11938032500000
    }
  ]
}

Step 3: Verify and Consume the Price On-Chain

The data from the API response must be passed to your smart contract for verification. The contract should hash the price data and verify the signature against the trusted signer address you obtained in Step 1. You can use the following smart contract to verify the signature on-chain. This contract provides functions to hash the price data and verify the ECDSA signature.
The following code is for illustrative purposes only, has not been audited, and should not be used in production without thorough testing.
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

/**
 * @title SignatureVerifier
 * @dev Contract to verify signatures generated by the ChaosLabs Oracle API
 * using OpenZeppelin's ECDSA library
 */
contract SignatureVerifier {
    using ECDSA for bytes32;

    /**
     * @dev Verifies if a signature was signed by the expected signer
     * @param messageHash The hash of the message that was signed
     * @param signature The signature bytes (65 bytes - r, s, v)
     * @param signer The expected signer address
     * @return bool True if the signature is valid, false otherwise
     */
    function verifySignature(
        bytes32 messageHash,
        bytes calldata signature,
        address signer
    ) external pure returns (bool) {
        address recoveredSigner = messageHash.recover(signature);
        return recoveredSigner == signer;
    }

    /**
     * @dev Hashes an oracle price feed data using the same method as in chain_signatures.go
     * @param feedId The feed ID (should already be adjusted, e.g., "BTCUSD")
     * @param price The price value
     * @param expo The exponent value (int8)
     * @param roundId The round ID
     * @param timestamp The timestamp
     * @param bid Optional bid value (set to 0 if not used)
     * @param ask Optional ask value (set to 0 if not used)
     * @return bytes32 The keccak256 hash of the encoded data
     */
    function hashPriceFeedData(
        string memory feedId,
        uint256 price,
        int8 expo,
        uint256 roundId,
        uint256 timestamp,
        uint256 bid,
        uint256 ask
    ) public pure returns (bytes32) {
        bytes memory message;

        // Go's big.NewInt(int64(expo)).Bytes() returns absolute value as minimal bytes
        // For expo = -8, big.NewInt(-8).Bytes() returns [8] (absolute value)
        // For expo = 0, big.NewInt(0).Bytes() returns [] (empty slice)
        bytes memory expoBytes;
        if (expo == 0) {
            expoBytes = new bytes(0); // Empty slice for zero
        } else {
            // Use absolute value, same as Go's big.Int.Bytes() behavior
            uint8 absExpo = expo < 0 ? uint8(-expo) : uint8(expo);
            expoBytes = abi.encodePacked(absExpo);
        }

        // Round ID is passed as bytes in Go, convert uint256 to bytes here
        bytes memory roundIdBytes = abi.encodePacked(roundId);

        message = abi.encodePacked(
            keccak256(bytes(feedId)),
            leftPadBytes(abi.encodePacked(price), 32),
            leftPadBytes(expoBytes, 32),
            leftPadBytes(roundIdBytes, 32),
            leftPadBytes(abi.encodePacked(timestamp), 32),
            leftPadBytes(abi.encodePacked(bid), 32),
            leftPadBytes(abi.encodePacked(ask), 32)
        );

        return keccak256(message);
    }

    /**
     * @dev Left pads a byte array to the specified length, matching Go's common.LeftPadBytes
     * @param data The input bytes
     * @param length The desired output length
     * @return bytes The left-padded bytes
     */
    function leftPadBytes(bytes memory data, uint256 length)
        public
        pure
        returns (bytes memory)
    {
        if (data.length >= length) {
            return data;
        }

        bytes memory result = new bytes(length);
        uint256 paddingLength = length - data.length;

        for (uint256 i = 0; i < data.length; i++) {
            result[paddingLength + i] = data[i];
        }

        return result;
    }
}
To use the SignatureVerifier, you need to:
  1. Deploy the contract: Deploy the SignatureVerifier contract to your target blockchain.
  2. Hash the data: In your consumer contract, call the hashPriceFeedData function with the parameters from the API response (feedId, price, expo, roundId, timestamp, bid, ask).
  3. Verify the signature: Call the verifySignature function with the hash from the previous step, the signature from the API response, and the oracle’s trusted signer address.

Support

For assistance with EVM integration, contact our support team at [email protected].
I