In this chapter, we’ll explore how to use Chainlink to implement custom oracle logic in your smart contracts. We’ll cover the process of creating a custom Chainlink contract, writing custom oracle logic, and integrating the contract with external APIs.
Creating a Custom Chainlink Contract
To create a custom Chainlink contract, you’ll need to do the following:
- Import the ChainlinkClient contract:
import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.4/ChainlinkClient.sol";
- Define the ChainlinkClient contract as a public variable:
Define a function that sends a data request to the Chainlink node:
- Define any variables that you’ll need to store the data that is returned by the API:
uint256 public price;
string public stockSymbol;
- Define a function that sends a data request to the Chainlink node:
function requestStockPrice(string _stockSymbol) public {
chainlink.requestData(chainlink.ORACLE_ADDRESS, jobId, _stockSymbol);
}
- Define a function that is called when the data request is fulfilled:
function onData(bytes32 _jobId, uint256 _price) public {
require(_jobId == jobId, "Invalid job ID");
price = _price;
}
Here’s the complete Chainlink contract:
import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.4/ChainlinkClient.sol";
contract StockPriceOracle {
ChainlinkClient public chainlink;
uint256 public price;
string public stockSymbol;
constructor(ChainlinkClient _chainlink, string _stockSymbol) public {
chainlink = _chainlink;
stockSymbol = _stockSymbol;
}
function requestStockPrice(string _stockSymbol) public {
chainlink.requestData(chainlink.ORACLE_ADDRESS, jobId, _stockSymbol);
}
function onData(bytes32 _jobId, uint256 _price) public {
require(_jobId == jobId, "Invalid job ID");
price = _price;
}
}
Writing Custom Oracle Logic
Now that you have a custom Chainlink contract set up, you can start writing custom oracle logic. Oracle logic is the code that is executed when a data request is fulfilled. It can be used to transform or manipulate the data in some way, or to trigger other actions based on the data.
To write custom oracle logic, you’ll need to do the following:
- Define a function that contains your custom oracle logic:
function customOracleLogic(uint256 _price) public {
// your custom oracle logic goes here
}
- Call the custom oracle logic function from the onData function:
function onData(bytes32 _jobId, uint256 _price) public {
require(_jobId == jobId, "Invalid job ID");
customOracleLogic(_price);
}
- Write your custom oracle logic in the customOracleLogic function. For example, you could use the data to trigger a conditional action:
function customOracleLogic(uint256 _price) public {
if (_price > 100) {
// trigger some action
}
}
You can also use the data to perform calculations or transformations:
function customOracleLogic(uint256 _price) public {
uint256 convertedPrice = _price * 0.8; // convert price from USD to EUR
// trigger some action using the converted price
}
Integrating the Custom Chainlink Contract with External APIs
To integrate your custom Chainlink contract with external APIs, you’ll need to use the Chainlink External Adapter. The process is similar to what we covered in the previous chapter on integrating Chainlink with external APIs.
First, install the External Adapter:
npm install -g @chainlink/external-adapter
Then, create a configuration file for the External Adapter. The configuration file should specify the API endpoint, the data to be requested, and any relevant parameters. Here’s an example configuration file for a stock price API:
{
"name": "stock-price-adapter",
"jobId": "stock-price-job",
"url": "https://stock-price-api.com/prices",
"params": {
"symbol": "${symbol}"
},
"resultPath": "$.price",
"responseDataType": "uint256"
}
Finally, run the External Adapter:
external-adapter run stock-price-adapter.json
Now, when the custom Chainlink contract sends a data request to the External Adapter, it will forward the request to the API and return the data to the contract when the request is fulfilled. The custom oracle logic in the contract will then be executed, using the data from the API.
Conclusion
In this chapter, we’ve covered how to use Chainlink to implement custom oracle logic in your smart contracts. We’ve created a custom Chainlink contract, written custom oracle logic, and integrated the contract with external APIs using the Chainlink External Adapter. With these tools, you can build smart contracts that can access and interact with a wide range of external data sources, and execute custom logic based on that data.
Exercises
To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.
Write a custom Chainlink contract that sends a data request to an external weather API to retrieve the current temperature for a given city. The contract should store the temperature in a variable and include a function that returns the temperature when called. The contract should also include a function that calculates the wind chill based on the temperature and wind speed, using the following formula: (windChill = 35.74 + 0.6215 * temperature – 35.75 * windSpeed^0.16 + 0.4275 * temperature * windSpeed^0.16)
import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.4/ChainlinkClient.sol";
contract WeatherOracle {
ChainlinkClient public chainlink;
uint256 public temperature;
uint256 public windSpeed;
string public city;
constructor(ChainlinkClient _chainlink, string _city) public {
chainlink = _chainlink;
city = _city;
}
function requestTemperature(string _city) public {
chainlink.requestData(chainlink.ORACLE_ADDRESS, jobId, _city);
}
function onData(bytes32 _jobId, uint256 _temperature, uint256 _windSpeed) public {
require(_jobId == jobId, "Invalid job ID");
temperature = _temperature;
windSpeed = _windSpeed;
}
function getTemperature() public view returns (uint256) {
return temperature;
}
function getWindChill() public view returns (uint256) {
return 35.74 + 0.6215 * temperature - 35.75 * windSpeed^0.16 + 0.4275 * temperature * windSpeed^0.16;
}
}
Write a configuration file for the Chainlink External Adapter that sends a data request to an external currency exchange API to retrieve the exchange rate for a given pair of currencies. The configuration file should specify the API endpoint, the data to be requested, and any relevant parameters.
{
"name": "currency-exchange-adapter",
"jobId": "currency-exchange-job",
"url": "https://currency-exchange-api.com/rates",
"params": {
"from": "${from}",
"to": "${to}"
},
"resultPath": "$.rate",
"responseDataType": "uint256"
}
Write a function in the custom Chainlink contract from exercise 1 that calculates the wind chill based on the temperature and wind speed, using the following formula: (windChill = 35.74 + 0.6215 * temperature – 35.75 * windSpeed^0.16 + 0.4275 * temperature * windSpeed^0.16)
function getWindChill() public view returns (uint256) {
return 35.74 + 0.6215 * temperature - 35.75 * windSpeed^0.16 + 0.4275 * temperature * windSpeed^0.16;
}
Write a function in the custom Chainlink contract from exercise 2 that calculates the total cost of a purchase in the target currency, given the exchange rate, the price of the item in the source currency, and the quantity of items being purchased.
function calculateTotalCost(uint256 _exchangeRate, uint256 _price, uint256 _quantity) public view returns (uint256) {
return _exchangeRate * _price * _quantity;
}
Write a custom Chainlink contract that sends a data request to an external sports API to retrieve the current score for a given sports game. The contract should store the score in a variable and include a function that returns the score when called. The contract should also include a function that calculates the number of points needed for a given team to win the game, based on the current score and the number of points needed to win the game.
import "https://github.com/smartcontractkit/chainlink/evm-contracts/src/v0.4/ChainlinkClient.sol";
contract SportsScoreOracle {
ChainlinkClient public chainlink;
uint256 public scoreA;
uint256 public scoreB;
string public teamA;
string public teamB;
uint256 public pointsToWin;
constructor(ChainlinkClient _chainlink, string _teamA, string _teamB, uint256 _pointsToWin) public {
chainlink = _chainlink;
teamA = _teamA;
teamB = _teamB;
pointsToWin = _pointsToWin;
}
function requestScore(string _teamA, string _teamB) public {
chainlink.requestData(chainlink.ORACLE_ADDRESS, jobId, _teamA, _teamB);
}
function onData(bytes32 _jobId, uint256 _scoreA, uint256 _scoreB) public {
require(_jobId == jobId, "Invalid job ID");
scoreA = _scoreA;
scoreB = _scoreB;
}
function getScore(string _team) public view returns (uint256) {
if (_team == teamA) {
return scoreA;
} else if (_team == teamB) {
return scoreB;
} else {
return 0;
}
}
function calculatePointsNeeded(string _team) public view returns (uint256) {
if (_team == teamA) {
if (scoreA > scoreB) {
return 0;
} else {
return pointsToWin - (scoreB - scoreA);
}
} else if (_team == teamB) {
if (scoreB > scoreA) {
return 0;
} else {
return pointsToWin - (scoreA - scoreB);
}
} else {
return 0;
}
}
}