Building an Ethereum Oracle with Web3.js 1.0

Robin Gist
7 min readApr 22, 2018

--

Ethereum oracle: a very buzzy term that one might hear tossed about at Meetups or other tech events. If you’re not in the know, you might not realize that blockchain oracles are just a specialized case of an off-chain data source that interacts with the Ethereum EVM by responding to on-chain events in contracts. That might sound like a lot to get your head around, but it really isn’t — just stick with me for a few minutes.

An oracle’s primary concern is to return outside data to contracts when the contract asks for it. This data could be anything required for the contract to do its business: the number of customers stored on a database, an actuarial risk assessment calculated by a 40 year old mainframe. In this article, we’ll concern ourselves with the temperature at request time in Los Angeles as obtained through the easy to use Dark Sky weather forecast API.

Chatty Contracts

People need to initiate contact with the outside world, and the Ethereum Virtual Machine is no exception. Otherwise, it would just be another isolated nerd, sitting around, being shy only talking to itself. The EVM is a social animal, and the way it lets you know it wants to chat is by firing an event (hopefully someone is listening). In the case of an oracle, the event tells you what the contract wants to talk about, what information you are to provide and sometimes, how you are to join in on the conversation. And if the running contract is polite, it might even finish up the conversation by letting you know how you did, if you care to know. However, just like with strippers and attorneys, getting chatty with the EVM costs money, so we keep the unnecessary flirting to a minimum.

source: Futurama — the best animated series EVER

Anyway, let’s get started building our Weather oracle. We’re using Web3.js 1.0, which does things a bit differently than the 0.2.x version. We’re also using Ganache for the testnet, which handles Websockets connections and events nicely. You won’t need Truffle or Embark or other tools, aside from Node and Javascript — the oracle will compile and deploy the contract for you.

Make sure you have the latest Solidity compiler installed, as well as the solc npm package. And don’t forget to install the request package for outbound HTTP calls to Dark Sky.

If you want to play along at home, the full code for both an oracle and a test client is located at https://github.com/robinagist/EthereumWeatherOracle. There’s some redundancy for the sake of clarity. (note: I am a bit of a noob at Javascript and Node, but not at software, so please pardon that I may have Pythonized your beloved Javascript a bit — Python and Go are my main languages.)

You’ll also need to get yourself a Dark Sky API account at https://darksky.net.

The Details

The process is simple:

  1. An account (either EOA or contract) calls a method on the main contract, asking for some off chain data, then goes back about its business.
  2. The contract method is tied to an event, which is emitted during execution.
  3. The oracle (which is already online listening for this particular event at the contract address) is notified, and begins processing the data request.
  4. After processing, the oracle runs another method on the contract address, fulfilling the data request of the calling account/contract. Sometimes, an event is emitted from this method, so that the caller can be notified.

The Contract

Our contract is simple. Outside of the usual suspects (a contract with a constructor), we have two methods and two events. The first method called is request. request has no parameters, and it’s only purpose in life is to emit a TempRequest event when called — which is hopefully picked up by an oracle. If our oracle picks up the event, it calls Dark Sky to get the most recent data, then calls the fill method with the current temperature. The fill method requires an unsigned integer as a parameter, sets the contract variable temp to the passed value and fires the FilledRequest event, with the current temperature as a parameter. (note: return values are not necessary here — we’re just passing them around here for console output purposes.)

pragma solidity ^0.4.21;

contract WeatherOracle {

address public owner;
uint public temp;
uint public timestamp;

event TempRequest(address sender);
event FilledRequest(uint rtemp);

function constructor() public {
temp = 0;
owner = msg.sender;
}

function request() public returns (string) {
emit TempRequest(msg.sender);
return "sent";
}

function fill(uint rtemp) public returns (uint){
temp = rtemp;
emit FilledRequest(rtemp);
return temp;
}


}

The Code

Just a standard setup. We declare abi for later assignment. The account is a Ganache account.

The one thing to notice (especially if you’ve worked with prior versions of Web3.js) is that we are now using Websockets. This gives us true peer-to-peer capabilities, and the ability catch event notifications without polling.


// SETUP //
let Web3 = require('web3')
let fs = require('fs')
let request = require('request')

// using Websockets URL. Ganache works w/ Web3 1.0 and Websockets
const testNetWS = "ws://127.0.0.1:7545"

// create a WebsocketProvider
const web3 = new Web3(new Web3.providers.WebsocketProvider(testNetWS))

// account address
const account = "0xf17f52151EbEF6C7334FAD080c5704D77216b732"

// we're using the Dark Sky API - get your API key at darksky.net
const apiKey = "GetYourOwnKey"
// using the Dark Sky API - getting the forecast, centered on LA
let url= "https://api.darksky.net/forecast/"+apiKey+"/37.8267,-122.4233"
// store the ABI for the contract so we can use it later
let abi = null

The next piece of code compiles and deploys the contract, which should be residing in the same directory as the oracle. After the contract is successfully deployed and mined (returning a contract address), the event listener is started on that address.

// LOAD, COMPILE, DEPLOY //// load, compile and deploy the included contract (contract.sol)
let c = loadCompileDeploy("contract.sol", ":WeatherOracle").then(function(address) {
console.log("contract address: " + address)
startListener(address)
}, function(err) {
console.log("didn't work. here's why: " + err)
})

Our listener sets up to listen for the TempRequest event coming in on any block since the very beginning of blockchain time. If an event is picked up, it logs the event data, and calls handler with the contract address.

// EVENT LISTENER //function startListener(address) {
console.log("starting event monitoring on contract: " + address)
let myContract = new web3.eth.Contract(abi, address);
myContract.events.TempRequest({fromBlock: 0, toBlock: 'latest'
}, function(error, event){ console.log(">>> " + event) })
.on('data', (log) => {
console.log("event data: " + JSON.stringify(log, undefined, 2))
handler(address)
})
.on('changed', (log) => {
console.log(`Changed: ${log}`)
})
.on('error', (log) => {
console.log(`error: ${log}`)
})
}

The request handler is the function that fetches the temperature data from Dark Sky, cleans up the data a bit, and calls the fill method on the contract at the address provided.

// REQUEST HANDLER //// handles a request event and sends the response to the contract
function handler(address) {
request(url, function(error, response, body) {
if(error)
console.log("error: " + error)

console.log("status code: " + response.statusCode)
let wx = JSON.parse(body)
let temperature = Math.round(wx.currently.temperature)
console.log("temp: " + temperature)

// run the 'fill' method on the contract
let MyContract = new web3.eth.Contract(abi, address);
MyContract.methods.fill(temperature).call()
.then(function(result) {
console.log("call result: " + result)
if (temperature == result)
console.log("+++success+++")
}, function(error) {
console.log("error " + error)
})

})
}

Start the Oracle

  1. Clone the repo https://github.com/robinagist/EthereumWeatherOracle
  2. Configure with your Dark Sky API Key and an account from Ganache.
  3. run ‘npm install’
  4. run the oracle: node wxoracle.js
  5. after startup, copy the contract address from the console output. You’ll need it for the test client.

Test Client

The test client is wxclient.js and is located in the same directory as wxoracle.js in the Github repo. After starting the oracle, copy the contact address from the console output, and paste it so that it is assigned to address at the bottom of this code.

Also, get an account from Ganache and paste in for account.

// account address
const account = "0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef"

'''
'''
// cut and paste the deployed contract address
const address = "contract address here"

Run the Test Client

Start a new terminal, and from the prompt, type ‘node wxclient’

Currently, the test client compiles the contract.sol file to get an ABI, which is unnecessary, but quick and dirty for this example. I won’t go into the workings of the test client. It’s pretty straightforward.

If you’re watching the oracle output, you should see something like this:

On the test client, running from another terminal:

You can see from the output on the oracle that it got back a 200 status code from Dark Sky, and a rounded temperature of 53 (that’s much too cold for L.A.). The temperature is stored in contract storage, and another event is fired to notify listeners that a new temperature is available.

Conclusions

Web3 1.0 and Websockets make oracle building a snap. No longer do you have to build systems that poll for events.

Other oracle tutorials are either dated or too convoluted. I tried to keep this one simple, and keep tools like Truffle and Embark out of the way, so you can see how everything really works. The oracle does its own deployment and both the oracle and test client compile and generate their own ABI (though I’ll optimize this so there isn’t so much compiling and cutting-and-pasting).

Peace and Respect

--

--