DEV Community

tosynthegeek
tosynthegeek

Posted on

Building with Supra: Powering Decentralized Applications with Better Data

Introduction

Supra stands as a prominent player in the realm of decentralized oracles, providing a critical service for DeFi applications: secure and reliable data feeds for blockchain environments. This guide delves into Supra's functionalities and explores how it empowers developers to build trust-worthy DeFi applications. Let's dive in.

Image description

The Challenge of Data in Blockchain

Blockchains, while revolutionary for security and immutability, are isolated ecosystems. This strength becomes a weakness when smart contracts, the engines of DeFi applications, require real-world data to function. External data is crucial for various DeFi use cases:

  • Decentralized Exchanges (DEXs): Accurate and timely price feeds are essential for DEXs to facilitate fair and efficient token swaps.
  • Lending Protocols: Reliable data on asset values is necessary for collateralization calculations and risk management in lending and borrowing platforms.
  • Prediction Markets: External data like sports scores or election results are crucial for settling prediction market contracts.

Existing oracle solutions often face challenges in terms of scalability and security, leaving developers with a gap to bridge when it comes to reliable data access for their DeFi applications.

Supra: Bridging the Data Gap

Enter Supra, a next-generation oracle solution, addresses these limitations. Unlike traditional oracles that rely heavily on adding more nodes (horizontal scaling), Supra prioritizes vertical scaling. This means Supra focuses on packing more processing power and resources into each individual node within its network. This approach offers several potential advantages for DeFi data delivery:

  • Faster Speeds: With more powerful nodes, Supra can potentially retrieve and process data significantly faster. This is crucial for real-time needs in DeFi, such as constantly fluctuating cryptocurrency prices or dynamic interest rates.
  • Reduced Latency: By minimizing the number of communication needed between nodes, vertical scaling can lead to lower latency in data delivery. This translates to less time between when data is requested and when it reaches the smart contract, ensuring applications operate with the most real-time data.
  • Potential Cost Efficiency: A smaller network of more powerful nodes could translate to lower operating costs for Supra and this would benefit developers by potentially leading to more competitive pricing for data feeds compared to oracles with a larger, horizontally scaled network.

Supra's core strength lies in its decentralized oracle price feeds. These oracles act as secure bridges, delivering real-world data to DeFi applications across various on-chain and off-chain scenarios. This ensures the data powering DeFi is accurate and verifiable, a critical requirement for applications relying on real-time information like cryptocurrency prices or market movements.

Key Features of Supra's Data Feeds

  • Pull Oracles: Pull Oracles function like a just-in-time data delivery system for smart contracts. Instead of receiving a constant stream of updates, smart contracts can actively request specific data points from the Supra network whenever they need them, minimizing unnecessary network congestion and lowering gas fees. - V1: Initial version, providing basic on-demand data services. - V2: Enhanced version with improved performance and additional features.
  • Push Oracles: Not all data needs to be constantly refreshed. Some DeFi applications, like lending protocols that peg interest rates to established benchmarks, can function perfectly with regular data updates. Push Oracles handles this by proactively pushing data updates from the Supra network to smart contracts at predetermined intervals. This is a more efficient approach for situations where real-time data isn't crucial, saving resources and reducing network congestion.

Image description

  • Decentralized VRF Supra's dVRF is designed to deliver secure, verifiable, and decentralized random number generation, offering DeFi applications a secure and reliable source of random numbers. Smart contracts can leverage this randomness for various purposes, ensuring fairness and transparency in a decentralized ecosystem.

Image description

Consuming Supra Oracle Price Data Feeds with Ethers and Hardhat

We would be building a smart contract that fetches price data feeds and emits an event if it meets a specific threshold using Hardhat and ethers. Code with me!

Prerequisites

Before we dive in, ensure you have the following tools and prerequisites in place:

  • Node.js
  • Hardhat
  • Metamask wallet: Configure metamask to connect to Sepolia network
  • Alchemy or Infura: Get your HTTP endpoint for the Sepolia testnet. Guide here
  • Test tokens: Request for Sepolia test
  • gRPC Server address for testnet
  • Pull Contract Address from the Avaialble networks on the Supra Docs. You can check for other available networks.

Setting Up the Environment

Now that we've gathered our tools, it's time to set up our development environment. Here's a step-by-step guide:

  • Start by running the following commands:
mkdir auction
cd auction
npm init -y
npm install --save-dev hardhat
npx hardhat init
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm i dotenv
code .
Enter fullscreen mode Exit fullscreen mode
  • Next step would be to clone the Oracle pull example code from GitHub and install the necessary dependencies.
git clone https://github.com/Entropy-Foundation/oracle-pull-example
cd oracle-pull-example/javascript/evm_client
npm install
Enter fullscreen mode Exit fullscreen mode
  • To keep sensitive information like your Metamask private key and RPC URL secure, create a .env file in your project directory and store your keys there in the format below:
PRIVATE_KEY=""
RPC_URL=""
Enter fullscreen mode Exit fullscreen mode
  • Modify your Hardhat configuration file (hardhat.config.js) to recognize the keys from your .env file. Also, add sepolia as a network.
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();  

module.exports = {
    solidity:  "0.8.19",
    networks: {
        sepolia: {
            url:  process.env.RPC_URL,
            accounts: [process.env.PRIVATE_KEY],
        },
    },
};
Enter fullscreen mode Exit fullscreen mode

Build and Compile your Contract

The next section will delve into building a smart contract that interacts with Supra Oracles using Hardhat. We'll walk you through the code, step-by-step, and demonstrate how to fetch price data feeds and emit events based on specific thresholds. I've added some comments to give more details of what's going on.

  • First, we will add the ISupraOraclePull which contains the verifyOracleProof function which performs verification and returns a PriceData struct containing the extracted price data.
interface  ISupraOraclePull {
    /**
    * @dev Verified price data structure.
    * @param  pairs List of pairs.
    * @param  prices List of prices corresponding to the pairs.
    * @param  decimals List of decimals corresponding to the pairs.
    */

    struct  PriceData {
        uint256[] pairs;
        uint256[] prices;
        uint256[] decimals;
    }

    /**
    * @dev Verifies oracle proof and returns price data.
    * @param  _bytesproof The proof data in bytes.
    * @return PriceData Verified price data.
    */
    function  verifyOracleProof(
        bytes  calldata  _bytesproof
    ) external  returns (PriceData  memory);
}
Enter fullscreen mode Exit fullscreen mode
  • For our contract, we would create an internal variable oracle to represent the interface. With this we can call the oracle's verifyOracleProof function to validate the proofs accompanying price data (delivered as byte strings). Once verified, the contract extracts the price information (including pair IDs, prices, and decimals) and stores it in internal mappings latestPrices and latestDecimal.
contract  Supra {
    // The oracle contract
    ISupraOraclePull  internal oracle;

    // Stores the latest price data for a specific pair
    mapping(uint256 => uint256) public latestPrices;
    mapping(uint256 => uint256) public latestDecimals;

    // Event to notify when a price threshold is met
    event  PriceThresholdMet(uint256  pairId, uint256  price);

    /**
    * @dev Sets the oracle contract address.
    * @param  oracle_ The address of the oracle contract.
    */
    constructor(address  oracle_) {
        oracle = ISupraOraclePull(oracle_);
    }

    /**
    * @dev Extracts price data from the bytes proof data.
    * @param  _bytesProof The proof data in bytes.
    */
    function  deliverPriceData(bytes  calldata  _bytesProof) external {
        ISupraOraclePull.PriceData  memory prices = oracle.verifyOracleProof(
            _bytesProof
        );

        // Iterate over all the extracted prices and store them
        for (uint256 i = 0; i < prices.pairs.length; i++) {
            uint256 pairId = prices.pairs[i];
            uint256 price = prices.prices[i];
            uint256 decimals = prices.decimals[i];

            // Update the latest price and decimals for the pair
            latestPrices[pairId] = price;
            latestDecimals[pairId] = decimals;


            // Example utility: Trigger an event if the price meets a certain threshold
            if (price > 1000 * (10 ** decimals)) {
            // Example threshold
            emit  PriceThresholdMet(pairId, price);
            }
        }
    }

    /**
    * @dev Updates the oracle contract address.
    * @param  oracle_ The new address of the oracle contract.
    */
    function  updatePullAddress(address  oracle_) external {
        oracle = ISupraOraclePull(oracle_);
    }

    /**
    * @dev Returns the latest price and decimals for a given pair ID.
    * @param  pairId The ID of the pair.
    * @return  price The latest price of the pair.
    * @return  decimals The decimals of the pair.
    */
    function  getLatestPrice(
        uint256  pairId
    ) external  view  returns (uint256  price, uint256  decimals) {
        price = latestPrices[pairId];
        decimals = latestDecimals[pairId];
    }

}
Enter fullscreen mode Exit fullscreen mode

The getLatestPrice function woula allow us to retrieve the most recent price and its corresponding decimals for a specific pair ID.
Compile your contract by running this command: npx hardhat compile

npx hardhat compile 
Compiled 1 Solidity file successfully
Enter fullscreen mode Exit fullscreen mode

Deploying and Interacting with the Supra Contract

  • Now that you have your contract compiled, we would import necessary libraries and set up the configuration for our script.
const { ethers } = require("hardhat");
const  PullServiceClient = require("../oracle-pull-example/javascript/evm_client/pullServiceClient");
require("dotenv").config();

const  address = "testnet-dora.supraoracles.com";
const  pairIndexes = [0, 21, 61, 49];
const  sepoliaPullContractAdress = "0x6Cd59830AAD978446e6cc7f6cc173aF7656Fb917"; //Update for V1 or V2
const  privateKey = process.env.PRIVATE_KEY;
Enter fullscreen mode Exit fullscreen mode
  • In our main function, we create a PullServiceClient instance (client) to communicate with the Supra Oracle service. We use this client to call the getProof method on the client object, passing the request (which contains the indexes and the chain type) and a callback function which also acepts two arguments and err and response . Now we can call the calContract function, passing the retrieved proof data (response.evm) as an argument.
async  function  main() {
    const  client = new  PullServiceClient(address);
    const  request = {
        pair_indexes:  pairIndexes,
        chain_type:  "evm",
    };

    console.log("Getting proof....");

    const  proof = client.getProof(request, (err, response) => {
    if (err) {
        console.error("Error getting proof:", err.details);
        return;
    }
    console.log("Calling contract to verify the proofs.. ");
    callContract(response.evm);
    });
}
Enter fullscreen mode Exit fullscreen mode
  • The callContract function, takes retrieved price data proof (response) and prepares a transaction to interact with the deployed Supra contract.
async  function  callContract(response) {
    const  Supra = await  ethers.getContractFactory("Supra");
    const  supra = await  Supra.deploy(sepoliaPullContractAdress);
    const  contractAddress = await  supra.getAddress();

    console.log("Supra deployed to: ", contractAdress);

    const  hex = ethers.hexlify(response);    
    const  txData = await  supra.deliverPriceData(hex);
    const  gasEstimate = await  supra.estimateGas.deliverPriceData(hex);
    const  gasPrice = await  ethers.provider.getGasPrice();

    console.log("Estimated gas for deliverPriceData:", gasEstimate.toString());
    console.log("Estimated gas price:", gasPrice.toString());  

    const  tx = {
        from:  "0xDA01D79Ca36b493C7906F3C032D2365Fb3470aEC",
        to:  contractAddress,
        data:  txData,
        gas:  gasEstimate,
        gasPrice:  gasPrice,
    };

    const  wallet = new  ethers.Wallet(privateKey);
    const  signedTransaction = await  wallet.signTransaction(tx);
    const  txResponse = await  wallet.sendTransaction(tx);

    console.log("Transaction sent! Hash:", txResponse.hash);

    // (Optional) Wait for transaction confirmation (e.g., 1 block confirmation)
    const  receipt = await  txResponse.wait(1);
    console.log("Transaction receipt:", receipt);

}
Enter fullscreen mode Exit fullscreen mode

Deploying to Sepolia

To deploy to sepolia, run the following command:

npx hardhat run scripts/run-supra.js --network sepolia
Enter fullscreen mode Exit fullscreen mode

With this, you should be able to use Supra price data feeds with Hardhat in building decentralized applications. You can find the full code for this tutorial here.

What's Next?

By leveraging Supra Oracles, developers can build secure and reliable DeFi applications with access to trustworthy and timely data feeds. To continue building with Supra and exploring other services, I recommend the following resources:

Top comments (0)