The definitive guide to successfully integrating off-chain data into your Ethereum smart contract with Chainlink & Amberdata

Trevor Clarke
Aug 12, 2019 · 9 min read

Blockchain & decentralized ledger technology is powerful, but requires that all information used in smart contracts either lives in a transaction or in the ledger state. This means off-chain data is not accessible to on-chain logic.

For example, if I want to create a contract that does simple logic based on different exchange prices, there is no way to know or access those values within my contract. An oracle contract can create a request queue, and send my contract the data I need to operate on-chain logic.

Image for post
Image for post

Off-chain data is possible with:

  • Chainlink — A network that provides reliable tamper-proof inputs and outputs for complex smart contracts on any blockchain.
  • Amberdata — A data provider enabling access to the full breadth of blockchain data and multi-exchange market data.

Today, we’re going to deep-dive into how to use several examples to accomplish tasks with chainlink and amberdata.

  • Setup
  • Remix IDE
  • Using A Data Provider — Amberdata
  • Requesting Data
    * Functions
    * Remix Settings
    * Using Oracle & Job Ids
  • Explorer
  • Checking Data
  • Helper functions
  • Going further

Setup Development Wallet

Before you can develop smart contracts or interact with Oracles in Ethereum you need to setup a few things. We will be using Metamask, adding test ETH and LINK token. We’re going to work through a few setup steps that are necessary for using Chainlink, a reference guide can also be seen here: Example Walkthrough.

We won’t be using mainnet today, however you should still install with security in mind, do not throw away or publish your private keys or phrases! Once you have metamask installed, click the network dropdown and switch to “Ropsten Test Network”. We will primarily be using Ropsten.

Click your Ropsten address to copy it from metamask. Paste your address in the faucet input, then click “Send me Test Ether”. Wait approximately 15 seconds and you will have 1 ETH in your account.

Use your same ropsten address as in the previous step, and paste into the input. Confirm you are not a robot, you’re not a robot… right? Click “Send me 100 Test Link”, and within ~15 seconds the transaction will confirm. You’re not quite done yet!

A. In order to see your LINK token balance in Metamask, you will need to add the token.
B. In Metamask click the hamburger menu button on the left, and click on Add Token and then Custom Token.
C. On Ropsten the LINK token address is: 0x20fE562d797A42Dcb3399062AE9546cd06f63280.
Copy that address, then paste the token contract address into MetaMask in the Token Address input. The token symbol and decimals of precision will auto-populate. Click Next.
D. A new window will appear, showing the LINK token details. Click Add Tokens.
E. You’re all set!

For other test networks, there are other faucets. Obtaining test Ether is as easy as above. Here are some helpful links for other networks:

Image for post
Image for post
Remix Solidity Editor

Setup Remix

Now we’re ready to start building our smart contract! We’ll be using remix to test the functionality, and do simple operations:

  1. Go to Remix (Note: We’re using solidity version 0.4.24, for compatibility with ChainlinkClient.sol)
  2. Start a file for ChainlinkTest.sol — Click the “Plus” button in the top left of remix. Name the file ChainlinkTest.sol

You now have a blank slate, we need to add the ChainlinkClient solidity code to add all the features needed to talk to the Oracle providers. The easiest way to do this (for now) is to copy & paste the following file into remix:

Why did we just paste a large file into a single solidity file? For testing in remix, we’ve found that there have been compile issues using linked file setup, causing memory issues or not finishing compile. This is not the case in truffle or embark, so we will just be using this setup for remix.

Image for post
Image for post
Amberdata Website

Using A Data Provider — Amberdata

We’re ready for the fun part! Using real world data in your project is greatly simplified using Chainlink, we just need to ask for it within our contract. Let’s cover a couple key concepts before diving into code.

It’s important to know the exact flow of execution needed before building applications with oracle logic. All contracts must use the following order of operations before they can use off-chain requested values:

Every time you deploy a new contract, it needs LINK token to pay for requests. This is as simple as copying the contract address, sending LINK tokens with metamask.

Sending a transaction to the contract’s “request” style function will trigger a chainlink request to an outside data provider. This goes into a queue to be fulfilled once the data provider returns the requested data.

Once a request has been made, fulfilled data will take several blocks to complete before it is available in your contract.

Upon transaction completion, it’s now available to any logic triggered upon the fulfillment callback.

Requesting Data

There are several ways to request data. We’re going to look at several examples, however there are many other ways not documented here. The first example starts with market data for token prices.

For Price, we’re going to store the value returned as a public variable.

contract AmberdataPriceBasic {
uint256 public currentTokenPrice;
}

Important pieces needed:

  • Oracle Address — The address of the contract responsible for returning the requested data
  • Job ID — The unique identifier for each type of job, based on the type of value expected to be returned. Full docs here
  • Token Address — The address of the token price to be requested
function requestTokenPrice(address _oracle, bytes32 _jobId, string memory _tokenAddress)
public onlyOwner {
Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillTokenPrice.selector);

// Configure the url to token prices
req.add(“extPath”, concat(“market/tokens/prices/”, _tokenAddress, “/latest”));

// Configure the pointer to the exact data location
// this will be the value submitted on fulfillment
req.add(“path”, “payload.0.priceUSD”);

// Since the price is in decimal format
// needs a minor change for solidity to handle correctly
req.addInt(“times”, 100);
sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT);
}
// Small helper function to help construct
function concat(string memory a, string memory b, string memory c)
private pure returns (string memory) {
return string(abi.encodePacked(a, b, c));
}
function fulfillTokenPrice(bytes32 _requestId, uint256 _price)
public
recordChainlinkFulfillment(_requestId)
{
// Simply store the returned value for use or access later
currentTokenPrice = _price;
}

View a completed code sample here →

Similar to the first example, we can request different fields from other endpoints. As you can see there’s not much changed, but the data possibilities are endless.

uint256 public currentGasPrice;function requestGasPrice(address _oracle, bytes32 _jobId)
public onlyOwner {
Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillGasPrice.selector);
req.add(“extPath”, “transactions/gas/predictions”);
req.add(“path”, “payload.average.gasPrice”);
sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT);
}
function fulfillGasPrice(bytes32 _requestId, uint256 _gasPrice)
public
recordChainlinkFulfillment(_requestId)
{
currentGasPrice = _gasPrice;
}

View a completed code sample here →

What if we want to store values in a mapping, so for every token address requested, the latest price is returned. Currently, Chainlink only returns a single value, but it also returns the completed request ID, which allows us to setup a temporary allocation for the returned value.

Here’s an example for how to achieve a storage mapping in your contract:

mapping(bytes32 => uint256) public currentTokensPrice;
mapping(bytes32 => bytes32) internal receipts;
// Store request ID for use later
function requestTokenPrice(address _oracle, bytes32 _jobId, string memory _hash)
public {
Chainlink.Request memory req = buildChainlinkRequest(_jobId, address(this), this.fulfillTokenPrice.selector);
req.add(“extPath”, concat(“market/tokens/prices/”, _hash, “/latest”));
req.add(“path”, “payload.0.priceUSD”);
req.addInt(“times”, 100);
receipts[sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT)] = stringToBytes32(_hash);
}
// Use request ID to transition value to main price mapping
function fulfillTokenPrice(bytes32 _requestId, uint256 _price)
public
recordChainlinkFulfillment(_requestId)
{
bytes32 tokenHash = receipts[_requestId];
delete receipts[_requestId];
currentTokensPrice[tokenHash] = _price;
}
// Simple getter for retrieving specific latest value
function getCurrentPriceByAddress(string memory _hash)
public view returns (uint256) {
return currentTokensPrice[stringToBytes32(_hash)];
}

View a completed code example here →

Image for post
Image for post
Chainlink’s Ropsten Job Explorer

Debugging secondary contract calls can be difficult, the chainlink explorer helps show all related data to the jobs context.
You can check the status of all transactions using the chainlink explorer:

Take a look at: https://ropsten.explorer.chain.link/job-runs?search=b7dcd7e256454497b230f7bd883b5c1a
This has a list at the latest jobs requested by smart contracts, and shows all details pertaining to the transaction triggered.

A successful example for a bytes32 job looks like this: https://ropsten.explorer.chain.link/job-runs/2a2aba55-4fc0-45c1-ba29-c4aba75fbdb1

Quick note about jobId’s in remix — they expect a preceding `0x` to the string. For example:
b7dcd7e256454497b230f7bd883b5c1a will be 0xb7dcd7e256454497b230f7bd883b5c1a in the remix input.

Checking Data

Once your transaction has finalized, it’s quite easy to check if the value was updated correctly or not. Let’s check on the previous previous examples and the data that was returned!

In the price example code, we request USD price for an Ethereum token, we’re going to use 0x Protocol Token, and see what its current day value is:

Image for post
Image for post
0x Protocol Token — USD Price

Obtaining the gas price is as simple as a button click to request the current stored value for our defined variable:
uint256 public currentGasPrice;
Remix knows it is a public value, and creates a getter for us to easily access:

Image for post
Image for post
Amberdata Gas Price Predictions

When we stored token prices in a mapping, and also as a mapping with an Array, we also created 2 ways to access the stored values. The first method:
getCurrentPriceByAddress(string memory _hash)
returns the value in non-decimal format:

Image for post
Image for post
0x Protocol Token — USD Price Example

The second method,
getHistoricalPriceByAddress(string memory _hash)
returns an array of values, which correspond to each value requested:

Image for post
Image for post
0x Protocol Token — USD Prices AggregationExample

Helper Functions

// Small helper function to help construct 
function concat(string memory a, string memory b, string memory c)
private pure returns (string memory) {
return string(abi.encodePacked(a, b, c));
}
function stringToBytes32(string memory source)
internal pure returns (bytes32 result) {
bytes memory tempEmptyString = bytes(source);
if (tempEmptyString.length == 0) {
return 0x0;
}
assembly {
result := mload(add(source, 32))
}
}

Going Further

There’s a whole lot more waiting for you! Here are a few ways to continue building with Oracles on Ethereum using Chainlink & Amberdata:

Clone the example repository:

Read API docs:

Join me at Web3Summit for my talk on Chainlink + Amberdata:

Get your free API Key with Amberdata, build on top of blockchain & market data easily:

Learn more about Chainlink:

Thank you!

That’s it for this deep dive, share with colleagues & friends on social!
We’d love to hear your thoughts in the comments below.

This post could not be possible without help from Thomas Hodges! His help & time debugging were incredible — Thank you!

amberdata

Amberdata is the leading data infrastructure for blockchain…

Trevor Clarke

Written by

VP of Product - Amberdata.io, Passionate Software Engineer 🤓, Hobbyist in 3D Design & Robotics

amberdata

amberdata

Amberdata is the leading data infrastructure for blockchain and digital assets. The API empowers our customer to build decentralized applications, backtest trading strategies, gain actionable insights and accelerate time to market for their digital asset and blockchain offering

Trevor Clarke

Written by

VP of Product - Amberdata.io, Passionate Software Engineer 🤓, Hobbyist in 3D Design & Robotics

amberdata

amberdata

Amberdata is the leading data infrastructure for blockchain and digital assets. The API empowers our customer to build decentralized applications, backtest trading strategies, gain actionable insights and accelerate time to market for their digital asset and blockchain offering

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store