Building with Supra: Powering Decentralized Applications with Better Data
Tags: web3, javascript, blockchain, data
Published: 2024-06-03 00:11:54

- [Introduction](#a)
	 - [The Challenge of Data in Blockchain](#b)
	 - [Supra: Bridging the Data Gap](#c)
	 - [Key Features of Supra's Data Feeds](#d)
- [Consuming Supra Oracle Price Data Feeds with Ethers and Hardhat](#e)
- [Prerequisites](#f)
- [Setting Up the Environment](#g)
- [Build and Compile your Contract](#h)
- [Deploying and Interacting with the Supra Contract](#i)
- [What's Next?](#j)

# <a id = "a">Introduction </a>
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soz5o9ri69jq0u36nof2.jpg)

## <a id = "b">The Challenge of Data in Blockchain </a>
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.

## <a id = "c">Supra: Bridging the Data Gap </a>
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.

### <a id = "d">Key Features of Supra's Data Feeds </a>

- **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bw1nnino5y2gd7592r3p.png)

- **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nbbinbvovchq9666m4f4.png)

## <a id = "e">Consuming Supra Oracle Price Data Feeds with Ethers and Hardhat </a>
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!

### <a id = "f">Prerequisites </a>
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](https://www.alchemy.com/overviews/how-to-add-sepolia-to-metamask)  network 
-   Alchemy or Infura: Get your HTTP endpoint for the Sepolia testnet.  [Guide here](https://docs.alchemy.com/docs/how-to-add-alchemy-rpc-endpoint-for-local-development#step-2-add-http-url-to-local-project)
-   Test tokens: Request for  [Sepolia test](https://sepoliafaucet.com/) 
- [gRPC Server address](https://supra.com/docs/data-feeds/pull-model/#configuration) for testnet
- Pull Contract Address from the [Avaialble networks](https://supra.com/docs/data-feeds/pull-model/networks/) on the Supra Docs. You can check for other available networks.

### <a id = "g">Setting Up the Environment </a>
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:
```bash
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.
```bash
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:
```javascript
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.
```javascript
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],
		},
	},
};
```

## <a id = "h">Build and Compile your Contract </a>
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.
```javascript
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`.

```javascript
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`
```bash
npx hardhat compile 
Compiled 1 Solidity file successfully
```
## <a id = "i">Deploying and Interacting with the Supra Contract </a>
- Now that you have your contract compiled, we would import necessary libraries and set up the configuration for our script.
```javascript
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.
```javascript
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.
```javascript
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](https://github.com/tosynthegeek/supraoracle-implementation).

## <a id = "j">What's Next? </a>
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](https://supra.com/docs/): for access to solution, repos, smart contract addresses, and other essential information
- [The Supra Research Page](https://supra.com/research/): for technical deep dives and researchs.
- [The Supra Academy](https://supra.com/academy/): # Explore topics. Dive in. Level up.