- Introduction
- 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: ```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],
},
},
};
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 theverifyOracleProof
function which performs verification and returns aPriceData
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
npx hardhat compile
Compiled 1 Solidity file successfully
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. ```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 deployedSupra
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.
Top comments (0)