James Bachini

Creating Your Own Oracle Solidity & NodeJS

Private Oracle

In this tutorial I’m going to go through the steps to setting up your own oracle service to bring data on-chain. We are going to be deploying a simple smart contract, then setting up a NodeJS client to fetch the price of Bitcoin, then uploading this to the contract.

  1. Oracle Smart Contract
  2. Oracle NodeJS App

Full code for this is open source at: https://github.com/jamesbachini/Private-Oracle

Oracle Smart Contract

Let’s create a new wallet to use for testing and load it with some Sepolia testnet ETH to pay for transaction fees.

Then we can go to Remix and deploy the following contract

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

contract BitcoinPrice {
    string public latestPrice;
    address public oracle;

    constructor() {
        oracle = msg.sender;
    }

    function updatePrice(string memory _newPrice) public {
        require(msg.sender == oracle, "Only the Oracle can update");
        latestPrice = _newPrice;
    }
}

We can compile with CTRL + S then go to the deploy tab, set the environment to “injected provider – metamask”, double check we are on testnet, then go ahead and deploy the contract.

I deployed it to this contract address which we will use later: 0xFD0F9A208fE892E6f5110A92bBcB58DfD4f361C7

Note. You’ll need to deploy your own because the address you deploy with needs to be the same address that we update the bitcoin price data with.

Deploying Solidity Oracle In Remix

Oracle NodeJS App

Next step is to create a little server-side app to fetch the latest bitcoin price and update the contract using ethers.js

You’ll need NodeJS, ethers, axios and dotenv installed. Download nodejs from the official site then install the dependencies from the command line with:

npm install ethers dotenv axios

You’ll now need to add the private key for you wallet to a .env file in the main directory. Note that your private key is sensitive and anyone who get’s hold of it can take all your funds. This is why we setup a new wallet for testnet purposes which shouldn’t have any funds on it and don’t upload it to github.

The .env file should look something like this:

PRIVATE_KEY=0x123donotshareyourkeyABC

Let’s write some code.

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

const API_ENDPOINT = 'https://api.coindesk.com/v1/bpi/currentprice.json';
const RPC_URL = 'https://rpc.sepolia.org';
const CONTRACT_ADDRESS = '0xFD0F9A208fE892E6f5110A92bBcB58DfD4f361C7';
const CONTRACT_ABI = [
    {
        "inputs": [
            {
                "internalType": "string",
                "name": "_newPrice",
                "type": "string"
            }
        ],
        "name": "updatePrice",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
];

async function fetchBitcoinPrice() {
    const response = await axios.get(API_ENDPOINT);
    return response.data.bpi.USD.rate_float;
}

async function updateOracle() {
    try {
        const bitcoinPrice = await fetchBitcoinPrice();
        // You probably wouldn't want to format this in json
        const json = `{"bitcoin":"${bitcoinPrice}"}`;

        const provider = new ethers.JsonRpcProvider(RPC_URL);
        const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
        const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer);

        const tx = await contract.updatePrice(json);
        await tx.wait();

        console.log('The oracle has spoken:', json);
    } catch (error) {
        console.error('The spirits are angry:', error);
    }
}

setInterval(updateOracle, 60 * 1000); // every 60 seconds
updateOracle();

We can put this into a file called oracle.js and then run

node ./oracle.js

Wait a few seconds for the tx to confirm and you’ll start seeing some updates going through…

Bitcoin Price Oracle

We can check this on-chain using etherscan: https://sepolia.etherscan.io/address/0xFD0F9A208fE892E6f5110A92bBcB58DfD4f361C7#readContract

etherscan oracle

In a production environment there are a few things you’d want to consider.

One is if a private oracle is good enough for what you are doing or if you’d want a decentralized oracle service like chainlink to update the data on your behalf.

You would probably want to collect data from multiple sources and do sanity checks on the data at the app level before updating the contract. Something like the median of 5-10 different exchange price data points would work well.

You’d want to normalize the data in the app to a Big Number and then store it in the contract as a UINT variable with a set amount of decimals to ensure there are no Javascriptesque floating point shenanigans.

I hope you’ve found this tutorial useful and it helps you bring some real world data on chain ♥️

Full code for this is open source at: https://github.com/jamesbachini/Private-Oracle



Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.