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.
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.
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…
We can check this on-chain using etherscan: https://sepolia.etherscan.io/address/0xFD0F9A208fE892E6f5110A92bBcB58DfD4f361C7#readContract
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