Kendrick
Motivation: I've been trying to find a tutorial on building an Oracle in Ethereum, only problem is that the articles online are either out of date (pre web3 v1.0.x) or the source code provided is structured in such a way that makes it incredibly tough to follow (having python, c# and javascript in one project with no clear description of what each language is doing and why its necessary). This guide assumes you have a basic understanding of the solidity language
Goal: By the end of this guide, I hope to have helped you build and deploy your Oracle onto your own private testnet
An Oracle is, simply put, a “smart contract” that is able to interact with the outside world, in the world of Ethereum that is known as off-chain. I put smart contracts in quotations because some people argue that Oracles aren't exactly a real smart contract.
Example: Say you're writing a smart contract that needs to retrieve weather data, however your contract can't make arbitrary network requests on-chain. You need something that trustable (all user input aren't) and is able to listen and respond to specific events on the blockchain. The solution: Oracles.
This guide will be building a simple Oracle that retrieves bitcoin's total market cap from coinmarketcap and store it into the blockchain.
{
"total_market_cap_usd": 198558465250.0, // This is that we want to store
"total_24h_volume_usd": 4974818568.0,
"bitcoin_percentage_of_market_cap": 61.65,
"active_currencies": 896,
"active_assets": 360,
"active_markets": 6442
}
I'll be using the truffle framework and testrpc for this guide. You can install them by running:
npm install -g truffle ethereumjs-testrpc
I'm using truffle because is has some really nice abstractions that allows me to interact with web3 (almost) hassle free. Once you've installed truffle you can initialize a boilerplate by typing:
mkdir oracle-cmc && cd oracle-cmc
truffle init
npm install truffle-contract web3 bluebird fetch --save # Dependencies
You should see the following files in your folder:
Edit truffle.js (your truffle configuration file) to:
module.exports = {
// See <https://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
migrations_directory: "./migrations",
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*", // Match any network id
gas: 4710000
}
}
}
This points truffle to our local private chain (testrpc).
Create four new files: ./contracts/CMCOracle.sol
, ./migrations/2_deploy_migrations.js
, ./client.js
, and ./oracle.js
. Your project folder should now look like:
Edit the file ./contracts/CMCOracle.sol
so it looks like:
pragma solidity ^0.4.17;
contract CMCOracle {
// Contract owner
address public owner;
// BTC Marketcap Storage
uint public btcMarketCap;
// Callback function
event CallbackGetBTCCap();
function CMCOracle() public {
owner = msg.sender;
}
function updateBTCCap() public {
// Calls the callback function
CallbackGetBTCCap();
}
function setBTCCap(uint cap) public {
// If it isn't sent by a trusted oracle
// a.k.a ourselves, ignore it
require(msg.sender == owner);
btcMarketCap = cap;
}
function getBTCCap() constant public returns (uint) {
return btcMarketCap;
}
}
And the file ./migrations/2_deploy_contracts.js
to:
var CMCOracle = artifacts.require("./CMCOracle.sol");
module.exports = function(deployer) {
deployer.deploy(CMCOracle);
};
Run testrpc
in a separate terminal, and then truffle compile && truffle migrate
. This compiles our contracts and deploys them onto our private testnet.
Edit ./oracle.js
so it looks like:
var fetch = require('fetch')
var OracleContract = require('./build/contracts/CMCOracle.json')
var contract = require('truffle-contract')
var Web3 = require('web3');
var web3 = new Web3(new Web3.providers.HttpProvider('https://localhost:8545'));
// Truffle abstraction to interact with our
// deployed contract
var oracleContract = contract(OracleContract)
oracleContract.setProvider(web3.currentProvider)
// Dirty hack for web3@1.0.0 support for localhost testrpc
// see https://github.com/trufflesuite/truffle-contract/issues/56#issuecomment-331084530
if (typeof oracleContract.currentProvider.sendAsync !== "function") {
oracleContract.currentProvider.sendAsync = function() {
return oracleContract.currentProvider.send.apply(
oracleContract.currentProvider, arguments
);
};
}
// Get accounts from web3
web3.eth.getAccounts((err, accounts) => {
oracleContract.deployed()
.then((oracleInstance) => {
// Watch event and respond to event
// With a callback function
oracleInstance.CallbackGetBTCCap()
.watch((err, event) => {
// Fetch data
// and update it into the contract
fetch.fetchUrl('https://api.coinmarketcap.com/v1/global/', (err, m, b) => {
const cmcJson = JSON.parse(b.toString())
const btcMarketCap = parseInt(cmcJson.total_market_cap_usd)
// Send data back contract on-chain
oracleInstance.setBTCCap(btcMarketCap, {from: accounts[0]})
})
})
})
.catch((err) => {
console.log(err)
})
})
And ./client.js
so it looks like:
var OracleContract = require('./build/contracts/CMCOracle.json')
var contract = require('truffle-contract')
var Web3 = require('web3');
var web3 = new Web3(new Web3.providers.HttpProvider('https://localhost:8545'));
// Truffle abstraction to interact with our
// deployed contract
var oracleContract = contract(OracleContract)
oracleContract.setProvider(web3.currentProvider)
// Dirty hack for web3@1.0.0 support for localhost testrpc
// see https://github.com/trufflesuite/truffle-contract/issues/56#issuecomment-331084530
if (typeof oracleContract.currentProvider.sendAsync !== "function") {
oracleContract.currentProvider.sendAsync = function() {
return oracleContract.currentProvider.send.apply(
oracleContract.currentProvider, arguments
);
};
}
web3.eth.getAccounts((err, accounts) => {
oracleContract.deployed()
.then((oracleInstance) => {
// Our promises
const oraclePromises = [
oracleInstance.getBTCCap(), // Get currently stored BTC Cap
oracleInstance.updateBTCCap({from: accounts[0]}) // Request oracle to update the information
]
// Map over all promises
Promise.all(oraclePromises)
.then((result) => {
console.log('BTC Market Cap: ' + result[0])
console.log('Requesting Oracle to update CMC Information...')
})
.catch((err) => {
console.log(err)
})
})
.catch((err) => {
console.log(err)
})
})
You'll notice that there's some code duplication between the two (particularly in getting the web3 instance), I chose not to abstract that common functionality because I intent to keep this guide as barebones as possible.
Finally, run node oracle.js
in the background, and run node client.js
twice in another terminal. If you receive something similar to the following:
Then congratulations! You've successfully setup your own Oracle!
As you can see in our intiial request the market cap for bitcoin was 0 (default for uint
in solidity). As we ran client.js
we requested that the market cap be updated via the line oracleInstance.updateBTCCap({from: accounts[0]})
. This triggers the event CallbackGetBTCCap
which is handled by oracle.js
, which fetches the bitcoin marketcap and updates the data in the smart contract.
Oracles are a necessity for smart contracts to interact with the outside world, as they act as the bridge between the on-chain and off-chain world. I hope having a barebones example will help any newcomers trying to find a way in this rapidly changing field.