Building with Supra: Powering Decentralized Applications with Better Data

Building with Supra: Powering Decentralized Applications with Better Data

Introduction

The Challenge of Data in Blockchain
Supra: Bridging the Data Gap
Key Features of Supra’s Data Feeds

Consuming Supra Oracle Price Data Feeds with Ethers and Hardhat
Prerequisites
Setting Up the Environment
Build and Compile your Contract
Deploying and Interacting with the Supra Contract
What’s Next?

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.

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.

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.

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 .

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

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=“”

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],
},
},
};

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);
}

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];
}

}

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

Deploying and Interacting with the Supra Contract

Now that you have your contract compiled, we would imports necessary libraries and set up your 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;

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);
});
}

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);

}

Deploying to Sepolia

To deploy to sepolia, run the following command:

npx hardhat run scripts/run-supra.js –network sepolia

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:

The Supra Docs: for access to solution, repos, smart contract addresses, and other essential information

The Supra Research Page: for technical deep dives and researchs.

The Supra Academy: # Explore topics. Dive in. Level up.