Integrating Pull Oracle V2

**Content of this document changes rapidly, it is advised to contact supra team in case you need support.

Similar to V1, Pull Oracle V2 also uses a combination of Web2 and Web3 methods to achieve ultra-low latency when sending data from Supra to destination chains. First, Web2 methods are used to retrieve data from Supra, while Web3 smart contracts are utilized for cryptographically verifying and writing it on-chain where it lives on immutable ledgers.

Please refer to the following resources for a better understanding of DORA price feeds.

  • Data Feeds - This explains how Supra calculates the S-Value for data feeds.

  • Data Feeds Index - This provides a list of data feeds currently offered by Supra.

  • Available Networks - This lists available networks and Supra contract addresses.

The first step of the process would be to set up your web2 code to interact with DORA's pull oracle and sync with Supra's consumer smart contract address.

Javascript Implementation

The code library being discussed in the section can be downloaded here. This library is designed to interact with a gRPC server for fetching proof data. It then uses that data to call a smart contract on a blockchain network. This document provides instructions on how to use the library and how to customize certain components for your specific use case.

Installation

To use the PullServiceClient library, follow these steps:

  1. Clone the repository or download the library's source code.

  2. Install the necessary dependencies by running the following command in your project directory:

    npm install

Usage

The library provides the main function, which fetches proof data from the gRPC server using the specified parameters and then calls a contract function on a blockchain network.

Configuration

Before using the library, make sure to set up the configuration in the main.js file:

  • Set the gRPC server address:

NetworkgRPC Server Address

Mainnets

TBU

Testnets

const address = 'testnet-dora-2.supra.com';
  • Set the pair indexes as an array (below given indexes are examples):

    const pairIndexes = [0, 21, 61, 49];
  • Set the chain type to EVM:

    const chainType = 'evm';
  • Configure the RPC URL for the desired blockchain network:

    const web3 = new Web3(new Web3.providers.HttpProvider('<RPC URL>'));

Customization

Users can customize the smart contract interaction under the callContract function. Specifically, you can modify the following components:

  1. Smart Contract ABI: Update the path to your smart contract's ABI JSON file:

    const contractAbi = require("../resources/abi.json");
  2. Smart Contract Address: Set the address of your smart contract:

    const contractAddress = '<CONTRACT ADDRESS>';
  3. Function Call: Modify the function call according to your smart contract's methods. For example, if your smart contract has a method named GetPairPrice:

    const txData = contract.methods.GetPairPrice(hex, 0).encodeABI();
  4. Gas Estimate: Adjust the gas estimation by calling the desired contract method:

    const gasEstimate = await contract.methods.GetPairPrice(hex, 0).estimateGas({ from: "<WALLET ADDRESS>" });
  5. Transaction Object: Customize the transaction object as needed:

    const transactionObject = {
     from: "<WALLET ADDRESS>",
     to: contractAddress,
     data: txData,
     gas: gasEstimate,
     gasPrice: await web3.eth.getGasPrice() // Set your desired gas price here, e.g: web3.utils.toWei('1000', 'gwei')
    };
  6. Private Key Signing: Sign the transaction with the appropriate private key:

    const signedTransaction = await web3.eth.accounts.signTransaction(transactionObject, "<PRIVATE KEY>");

Running the Application

To run the application, execute the following command:

node main.js

This will initiate the fetching of proof data and allow for interaction with the smart contract based on the provided configuration and customization. -----------------------------------------------------------

Rust Implementation

The code library being discussed in the section can be downloaded here. The Rust PullServiceClient is designed to interact with a gRPC server for fetching proof data and using that data to call a smart contract on a blockchain network. This readme provides instructions on how to use the library and customize certain components for your specific use case.

Prerequisites

Installation

To use the Rust library for Sui, Aptos and EVM, and follow these steps:

  1. Clone the repository or download the library's source code.

  2. Navigate to the project directory in your terminal

Usage

The Rust library for Sui, Aptos and EVM provides a complete example that fetches proof data from a gRPC server and then calls a contract function on a blockchain network.

Configuration

Before using the library, configure the file in example folder:

  • Set the gRPC server address:

    NetworkgRPC Server Address

    Mainnets

    Testnets

    let address = "grpcs:://testnet-dora-2.supra.com".to_string();

  • Set the pair indexes as an array: ( below mentioned indexes are for an example)

    let pair_indexes = vec![0, 21, 61, 49];
  • Set the chain type to EVM:

    let chain_type = "evm".to_string();
  • Set the RPC URL for the desired blockchain network:

    let rpc_url = "<RPC URL>";

Customization

Users can customize the smart contract interaction under the call_contract function. Specifically, you can modify the following components:

  1. Private Key: Set your private key:

    let secret_key = "<PRIVATE KEY>";
  2. Contract Address: Set the address of your smart contract:

    let contract_address = "<CONTRACT ADDRESS>";
  3. Contract Function Call: Customize the function call based on your contract methods:

    let call = sc.get_pair_price(Bytes::from(input.proof_bytes), U256::from(0));
  4. Smart Contract ABI: Update the path to your smart contract's ABI JSON file and contract name (EVM only) in pull_contract.rs:

     abigen!(
       MockOracleClient,
       "../../resources/abi.json"
     );

Running the Application

Open your terminal and navigate to the project directory.

Run the example using the following command:

Evm

cargo run --example evm_client

Now, let's move on to set up your Web3 components:

Step 1: Create The S-Value Pull Interface to verify the price data received.

Add the following code to the solidity smart contract that you wish to retrieve an S-Value.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface ISupraOraclePull {

    /// @notice Verified price data
    struct PriceData {
        // List of pairs
        uint256[] pairs;
        // List of prices
        // prices[i] is the price of pairs[i]
        uint256[] prices;
        // List of decimals
        // decimals[i] is the decimals of pairs[i]
        uint256[] decimals;
        // List of round
        // round[i] is the round of pairs[i]
        uint256[] round;
    }

    function verifyOracleProof(bytes calldata _bytesproof) 
    external 
    returns (PriceData memory);
}

This creates the interface that you will later apply in order to verify and fetch S-Values from Supra's Pull Contract.

Step 2: Configure The S-Value Feed Address

To verify and fetch the S-Value from a Supra Pull smart contract, first find the S-Value Pull Contract address for the preferred chain. When you have the address, create an instance of the ISupraOraclePull using the interface we previously defined: Supra contracts for each network can be found in our network addresses list.

// Mock contract which can consume oracle pull data
contract MockOracleClient {
    /// @notice The oracle contract
    ISupraOraclePull internal oracle;

    /// @notice Event emitted when a pair price is received
    event PairPrice(uint256 pair, uint256 price, uint256 decimals);

    constructor(address oracle_) {
        oracle = ISupraOraclePull(oracle_);
    }
}

Step 3: Receive and Verify the S-Value

You can refer to: GitHub Example to get the Proof<_bytesProof> of S-Values in a Web2 environment.

Next, copy the following code to the smart contract to verify the price data received:

// Get the price of a pair from oracle data received from supra pull model
  
    function GetPairPrice(bytes calldata _bytesProof, uint256 pair) external                 
    returns(uint256){
        ISupraOraclePull.PriceData memory prices = 
        oracle.verifyOracleProof(_bytesProof);
        uint256 price = 0;
        uint256 decimals = 0;
        for (uint256 i = 0; i < prices.pairs.length; i++) {
            if (prices.pairs[i] == pair) {
                price = prices.prices[i];
                decimals = prices.decimals[i];
                break;
            }
        }
        require(price != 0, "Pair not found");
        return price;
    }

Thats it. Done!

Now you are ready to consume fast, low latency, and highly accurate data from Supra's Pull oracle.

Create a function with access control that updates the oracle using the function: updatePullAddress()

This will allow you to update the address of the Supra Pull contract after deployment, allowing you to future proof your contract. Access control is mandatory to prevent the undesired modification of the address.

 function updatePullAddress(SupraOraclePull oracle_) 
    external 
    onlyOwner {
        oracle = oracle_;
    }

Example Implementation

Here's an example of what your implementation should look like:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";

interface ISupraOraclePull {
    
    //Verified price data
    struct PriceData {
        // List of pairs
        uint256[] pairs;
        // List of prices
        // prices[i] is the price of pairs[i]
        uint256[] prices;
        // List of decimals
        // decimals[i] is the decimals of pairs[i]
        uint256[] decimals;
    }

    function verifyOracleProof(bytes calldata _bytesProof) 
    external 
    returns (PriceData memory);
}


// Mock contract which can consume oracle pull data
contract MockOracleClient is Ownable {
    //The oracle contract
    ISupraOraclePull public oracle;

    //Event emitted when a pair price is received
    event PairPrice(uint256 pair, uint256 price, uint256 decimals);

    constructor(ISupraOraclePull oracle_) {
        oracle = oracle_;
 }

// Get the price of a pair from oracle data
    function GetPairPrice(bytes calldata _bytesProof, uint256 pair) external                 
    returns(uint256){
        ISupraOraclePull.PriceData memory prices = 
        oracle.verifyOracleProof(_bytesProof);
        uint256 price = 0;
        uint256 decimals = 0;
        for (uint256 i = 0; i < prices.pairs.length; i++) {
            if (prices.pairs[i] == pair) {
                price = prices.prices[i];
                decimals = prices.decimals[i];
                break;
            }
        }
        require(price != 0, "Pair not found");
        return price;
    }

    function updatePullAddress(ISupraOraclePull oracle_) 
    external 
    onlyOwner {
        oracle = oracle_;
    }
}


Last updated