Oracles on Rootstock: Fetching Off-Chain Data for Smart Contracts

Introduction

Rootstock (RSK) is a layer 2 solution founded to bring Ethereum’s smart contract features to Bitcoin while making on-chain transactions cheaper and more scalable than Bitcoin. This means RSK now combines the security offered by Bitcoin and the extensibility provided by Ethereum. That way, Bitcoin isn’t just a store of value but a fully decentralized financial platform. Over time, Rootstock has expanded to offer the Rootstock Infrastructure Framework (RIF) to allow developers to use RSK to build products on Bitcoin.

Blockchain technology needs to connect with off-chain systems and environments to achieve its practical uses. Data inputs from off-chain events in systems like IoT and financial systems are needed to trigger smart contract fulfillment based on predefined conditions. For instance, if we want our blockchain service to mint an NFT reward to a music artist when their music streams reach a certain count on a streaming service like Spotify, our on-chain smart contracts need to be able to get the play count data from the streaming service to trigger the minting.

However, blockchain systems cannot inherently access these off-chain events as they don’t operate on APIs, gRPC, or any of the other standards that computer systems commonly operate by in non-blockchain services. Multiple blockchains cannot communicate with each other without using services called bridges. That is where oracles come in. Oracles are third-party services that accept, verify, and interpret off-chain data in a way that blockchains can understand and work with, and vice versa.

Oracles can be classified based on the direction of data flow:

  • Input oracles provide external data to blockchains, which prompts the execution of smart contracts.

  • Output oracles transmit commands from on-chain contracts to off-chain systems to carry out specific actions.

However, there are cross-chain oracles that facilitate both inbound and outbound communication.

How Oracles Work on Rootstock (RSK)

Oracles exist as a solution to a critical problem in blockchain adoption. That is to bridge the real and physical world events with the on-chain smart contracts. However, they also pose some challenges that border on their reliability and security.

Chief among these challenges is that a bug or security vulnerability can compromise the security of the underlying blockchain. If the data from an oracle is manipulated, the smart contract on the blockchain may receive incorrect input during execution. Additionally, there is a growing need for users building on blockchain to have a unified, user-friendly interface that allows them to browse, compare, and select affordable oracles and off-chain data sources tailored to their specific use cases, without requiring a deep understanding of the technical details of each oracle.

To address these needs, Rootstock developed the RSK-powered RIF Gateways. This marketplace provides access to oracles, cross-chain integrations, and other interoperability tools. RIF Gateways enable decentralized finance (DeFi) and decentralized application (dApp) builders to connect with decentralized oracle networks that aggregate various oracles and their data sources, thereby reducing reliance on any single oracle for their operations.

Implementing a Decentralized Oracle in a Rootstock dApp

In this section, we will provide a quick guide on setting up a mini oracle that supplies off-chain data to an RSK-based smart contract.

Smart Contract:

Let’s start by writing the smart contract by installing the Foundry development kit, and creating the contract and its necessary configurations.

1. Set up the Contract Project with Foundry: follow the Getting Started with Foundry guide in the official RSK documentation to install and configure Foundry for RSK development.

2. Create and Compile the Smart Contract: Create a file in the src folder titled Oracle.sol and add the following code into it:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Oracle {
 uint256 public data;
 event DataUpdated(uint256 newData);

 function updateData(uint256 data) external {
    data = data;
    emit DataUpdated(_data);
 }
}

The above code is a simple smart contract definition that accepts data from the oracle. Be sure to specify the RSK testnet RPC URL in your foundry.toml file in the root of the project:

[rpc_endpoints]
rsk_testnet = "https://public-node.testnet.rsk.co"

Then, compile the contract with the following CLI command:

forge build

3. Deploy the Smart Contract: Deploy the contract to the RSK testnet using the following CLI command:

forge create --rpc-url https://public-node.testnet.rsk.co \
  --private-key your_wallet_secret_key_here \          
  --legacy \   
  --broadcast \        
  src/Oracle.sol:Oracle

The:

  • --private-key specifies the wallet private key and is used to sign transactions during deployment. You can configure MetaMask wallet for Rootstock so you can connect to the Rootstock network with your MetaMask wallet.

  • --legacy flag ensures a fixed gas price is assumed like in older Ethereum versions.

  • --broadcast flag sends the transaction to the testnet network nodes for verification.

After running the above CLI command, you should get a response that looks like the following:

Deployer: 0xfcdF314daed7E8c39E8591870e20A8c25D138a5C
Deployed to: 0xB869402476c38e3fea58c6875258A141561C8460...
Transaction hash: 0x8266e5eb546813eae3087e032a0f8ccaef8a81565538e1d3f472d0b5bfaed190

NB: Note the contract address returned as “Deployed to“ because you will need it in the next section.

4. Get the Contract ABI: Obtain and keep the contract ABI by copying the value from the first few lines of the out/Oracle.sol/oracle.json file. Alternatively, if you have jq installed, you can run the following CLI command from the root of the directory:

cat out/Oracle.sol/Oracle.json | jq '.abi'

You should get the following response:

[
  {
 "type": "function",
 "name": "data",
 "inputs": [],
 "outputs": [
  {
    "name": "",
    "type": "uint256",
    "internalType": "uint256"
  }
 ],
 "stateMutability": "view"
  },
  {
 "type": "function",
 "name": "updateData",
 "inputs": [
  {
    "name": "_data",
    "type": "uint256",
    "internalType": "uint256"
  }
 ],
 "outputs": [],
 "stateMutability": "nonpayable"
  },
  {
 "type": "event",
 "name": "DataUpdated",
 "inputs": [
  {
    "name": "newData",
    "type": "uint256",
    "indexed": false,
    "internalType": "uint256"
  }
 ],
 "anonymous": false
  }
]

Off-chain Oracle:

1. Create and Run oracle in Node.js Local Server: We will use Node.js to run a local server and use ethers.js to interact with RSK in your oracle project. Start by creating a directory and navigate into it:

mkdir rsk-oracle && cd rsk-oracle

Then, initialize the project:

npm init -y

Next, install the necessary libraries:

npm install express axios ethers dotenv

Express.js: JavaScript framework to build the project

Axios: to access external web services

ethers.js: to interface with the RSK network

dotenv: to manage environment variables

Next, create a .env file in the root of the Node.js project and add the following credentials:

RPC_URL=https://public-node.testnet.rsk.co
PRIVATE_KEY=your_private_key_here
CONTRACT_ADDRESS=0xYourSmartContractAddress

RPC_URL: the RSK testnet RPC URL

PRIVATE_KEY: the wallet private key

CONTRACT_ADDRESS: the address of the deployed RSK contract

Now, create an oracle.js file in the root of the Node.js project and add the following code to it:

require('dotenv').config();
const axios = require('axios');
const { ethers } = require('ethers');

// Load environment variables
const RPC_URL = process.env.RPC_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;

// Connect to RSK
const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Smart contract ABI
const contractABI = [
 {
    "type": "function",
    "name": "data",
    "inputs": [],
    "outputs": [
        {
            "name": "",
            "type": "uint256",
            "internalType": "uint256"
        }
    ],
    "stateMutability": "view"
 },
 {
    "type": "function",
    "name": "updateData",
    "inputs": [
        {
            "name": "_data",
            "type": "uint256",
            "internalType": "uint256"
        }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
 },
 {
    "type": "event",
    "name": "DataUpdated",
    "inputs": [
        {
            "name": "newData",
            "type": "uint256",
            "indexed": false,
            "internalType": "uint256"
        }
    ],
    "anonymous": false
 }
];

// Connect to the contract
const contract = new ethers.Contract(CONTRACT_ADDRESS, contractABI, wallet);

// Function to fetch data from an external API (Example: BTC price from CoinGecko)
async function fetchExternalData() {
 try {
    const response = await axios.get('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd');
    return response.data.bitcoin.usd;
 } catch (error) {
    console.error('Error fetching external data:', error);
    return null;
 }
}

// Function to update smart contract with fetched data
async function updateSmartContract() {
 const data = await fetchExternalData();
 if (data === null) return;

 console.log(`Fetched Data: ${data}, Sending to contract...`);

 try {
    const tx = await contract.updateData(data);
    console.log(`Transaction sent: ${tx.hash}`);
    await tx.wait();
    console.log('Transaction confirmed!');
 } catch (error) {
    console.error('Error updating contract:', error);
 }
}

// Run the update every 60 seconds
setInterval(updateSmartContract, 60000);

The above code:

  • Gets the environment variables from the .env file

  • Connects to the RSK network with the RSK RPC URL and the wallet private key

  • Connects to the already deployed contract

  • Interacts with the contract via the contract ABI (application binary interface), which consists of the functions that external programs like our oracle can call.

  • Fetches the BTC/USD price feed, which is external data from the Coingecko API

  • Updates the deployed contract with the new fetched price

  • Makes console logs to keep track of the update process

  • Makes a new price update happen every 60 seconds

Next, run the Node.js server with the following command:

node oracle.js

After a minute, you would get a shell response similar to the following:

Fetched Data: 83250, Sending to contract...
Transaction sent: 0x1f1b2d2c64ac10fedc6cd0689c6a31f6748add02aeaaac47e1033a8b242b81e9

2. Confirm the Data Updates: Go to the RSK testnet block explorer and search for the deployed contract address. You should see its page listing the transactions that have happened there like the following screenshot.

Screenshot of deployed contract page on Rootstock Explorer

Figure: Screenshot of deployed contract page on Rootstock Explorer

Real-World Use Cases of Oracles on Rootstock

Insurance and Betting Platforms: Oracles hold great promises in the deFi applications in the insurance and betting industries as the blockchains need the knowledge or real-world physical conditions to issue out claims and competition outcome data for betting conditions. To pay out compensation for a car accident, an on-chain smart contract needs real-time IoT sensor data to provide verifiable crash data from vehicles. An off-chain oracle supplies this data, enabling the smart contract to execute correctly. In sports betting, if a smart contract is set to pay out a winner based on the outcome of a football match, it must know the result of that match. An off-chain oracle can obtain this information from APIs offered by sports media organizations such as ESPN or FIFA

Price Feeds and Lending Protocols: Lending protocols like Creditas and DAOs like OrdinalDAO need off-chain oracles because blockchains don’t natively keep credit history data. However, an off-chain oracle can access traditional credit scores from off-chain providers, and that enables risk assessment for DeFi loans.

Supply Chain and Logistics: Blockchain-based shipping and delivery systems require sensors to confirm the arrival of shipments, along with APIs that integrate off-chain records from the customs clearance system. IoT devices and APIs can supply this real-world data through an oracle that updates the blockchain smart contracts with confirmations of shipment and delivery.

Stablecoins and Exchange Rates: Stablecoins on Rootstock require real-time exchange rates from off-chain forex markets. When off-chain oracles retrieve exchange rates from sources like Binance and Forex APIs, the stablecoins can maintain their price stability and ensure appropriate collateralization.

Challenges and Limitations of Oracles on Rootstock

We will expand on some of the challenges of off-chain oracles in this section. It’s good to bear them in mind as we create and use them in our dApps and DeFi systems.

Security Risks: Oracles play a crucial role in the blockchain ecosystem by connecting to external data sources and facilitating communication between different blockchains. However, if an oracle is compromised, it can provide incorrect data to smart contracts. For instance, a manipulated off-chain price feed could trigger erroneous financial transactions on-chain. To mitigate this risk, marketplaces like RIF Gateways aggregate data from multiple independent oracles, making it more difficult for an attacker to compromise several oracles at once. This approach is known as decentralized oracles.

Data Reliability Concerns: Oracles must avoid using feeds from unverified or biased sources, as this could result in blockchains receiving malicious data. To address this issue, decentralized oracles can be utilized.

dApps Scalability Issues: As the number and size of decentralized applications (dApps) that rely on an oracle increase, the volume of data requests that the oracle must manage also grows. This surge in demand can impact performance. To address this challenge, it's essential to optimize oracle operations through consensus mechanisms and to design networks and systems effectively. This approach will help maintain efficiency, ensure proper load distribution, and enhance scalability.

Gas Fees and Efficiency: Oracles often perform complex computations and require frequent data updates, which can lead to increased gas consumption and decreased efficiency on the blockchain network. To keep operational costs manageable for developers and builders, some computations and data aggregation can be handled off-chain. This approach reduces the number of on-chain transactions, helps keep transaction costs low, and maintains the practicality of using decentralized applications (dApps).

Conclusion

In this article, we’ve studied oracles with a focus on interacting with off-chain environments and external data sources. We also looked at the applications of off-chain oracles and their integration and implementation in the Rootstock ecosystem via the RIF Gateway marketplace. This encourages DeFi builders and dApp developers to use oracles and benefit from decentralized oracle networks through RIF Gateways to address the potential challenges of using oracles.

We also went through the step-by-step process of setting up a smart contract to retrieve off-chain data and deploying a custom oracle on the RSK platform using Solidity. This gives an insight into how oracles work in real-world scenarios and should help developers looking to integrate oracles or use RSK in their projects.

For more information about Rootstock, RIF gateways, and other RSK-related concepts, the Rootstock documentation is an excellent starting point.

4
Subscribe to my newsletter

Read articles from Jekayin-Oluwa Olabemiwo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jekayin-Oluwa Olabemiwo
Jekayin-Oluwa Olabemiwo