Ethereum Asked on December 23, 2021
I’m trying to make a contract that interacts with a market price or a market out come : e.g APPL drops below X or NFLX > Y …
Is it possible in Solidity to call an external API during this consensus process (e.g Yahoo Finance) — I have seen the chain.link protocol and Augur, but I’m not sure how these work — any pointers on how to do something like this?
To do this, you'll need an oracle service, and a stock data API.
Ideally, you'd want to get the price of a stock from multiple Chainlink nodes, from multiple stock APIs, to decentralize the service as much as possible.
You can follow the Chainlink documentation that shows how to get data from any API, and use the API of your choice.
To get the mainnet/kovan/other version of each of the methods below, just swap the ORACLE_ADDRESS
and JOBID
to their values on each chain.
The below code shows you how to get free stock data from Alpha Vantage API using an API call through a Chainlink Node in ropsten. You can also deploy it with this Remix link. Notice you'll use a free API key. If the node has a lot of people testing with this free endpoint, you'll hit the API call cap.
Note: Alpha Vantage stock data is 1 day lagging and does not provide real-time data. You can check out this list for some of the best free and paid stock APIs currently on the market.
pragma solidity ^0.6.0;
import "github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/ChainlinkClient.sol";
// MyContract inherits the ChainlinkClient contract to gain the
// functionality of creating Chainlink requests
contract ChainlinkExample is ChainlinkClient {
// Stores the answer from the Chainlink oracle
uint256 public currentPrice;
address public owner;
// The address of an oracle - you can find node addresses on https://market.link/search/nodes
address ORACLE_ADDRESS = 0xB36d3709e22F7c708348E225b20b13eA546E6D9c;
// The address of the http get job that returns a uint256
// you can find job IDs on https://market.link/search/jobs
string constant JOBID = "628eded7db7f4f799dbf69538dec7ff2";
// 1 LINK / 10 = 0.1 LINK
uint256 constant private ORACLE_PAYMENT = 1 * LINK / 10;
constructor() public {
setPublicChainlinkToken();
owner = msg.sender;
}
// Creates a Chainlink request with the uint256 multiplier job
// Ideally, you'd want to pass the oracle payment, address, and jobID as parameters as well
// This will return the one day lagged price of whatever ticker you give it
function requestStockPrice(string memory ticker)
public
onlyOwner
{
// newRequest takes a JobID, a callback address, and callback function as input
Chainlink.Request memory req = buildChainlinkRequest(stringToBytes32(JOBID), address(this), this.fulfill.selector);
// Adds a URL with the key "get" to the request parameters
// NOTE, if this chainlink node gets a lot of requests using this API key, it will break (as the API is rate limited)
req.add("get", string(abi.encodePacked("https://www.alphavantage.co/query?function=GLOBAL_QUOTE&apikey=XXXXXXX&symbol=", ticker)));
// Uses input param (dot-delimited string) as the "path" in the request parameters
string[] memory path = new string[](2);
path[0] = "Global Quote";
path[1] = "05. price";
req.addStringArray("path", path);
// Adds an integer with the key "times" to the request parameters
req.addInt("times", 100000000);
// Sends the request with the amount of payment specified to the oracle
sendChainlinkRequestTo(ORACLE_ADDRESS, req, ORACLE_PAYMENT);
}
// fulfill receives a uint256 data type
function fulfill(bytes32 _requestId, uint256 _price)
public
// Use recordChainlinkFulfillment to ensure only the requesting oracle can fulfill
recordChainlinkFulfillment(_requestId)
{
currentPrice = _price;
}
// withdrawLink allows the owner to withdraw any extra LINK on the contract
function withdrawLink()
public
onlyOwner
{
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer");
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
// A helper funciton to make the string a bytes32
function stringToBytes32(string memory source) private pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly { // solhint-disable-line no-inline-assembly
result := mload(add(source, 32))
}
}
}
After deploying, fund your contract with some test/mainnet LINK (this is the oracle gas), enter the sting of the ticker you want to get the price of into the requestStockPrice
function, then hit requestStockPrice
. After the contract finishes (give it a few blocks), you'll see the price by hitting currentPrice
.
NOTE, the ropsten Alpha Chain oracle has been put on hold at the moment
You can also use an oracle that already has an API key to the data. In this case, you will just specify the job ID that as the API key in it. For example this job has access to the Alpha Vantage API. You can also check out honeycomb.market which also has access to paid stock APIs.
You'll notice in our example below that there are only a few things different. It has a different JOBID
, and uses the copyPath
keyword in the requestStockPrice
function instead of the path
keyword. It also doesn't call the URL of the API, but just adds one of the parameters symbol
.
In our example, we are using the Alpha Chain oracle with a job that allows you to get data from the Alpha Vantage stock API without having to input an API key. Below is the code how to do that, and here is the remix deployment for ropsten. You still have to fund the contract with LINK
after deploying it.
pragma solidity ^0.6.0;
import "github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/ChainlinkClient.sol";
// MyContract inherits the ChainlinkClient contract to gain the
// functionality of creating Chainlink requests
contract ChainlinkExample is ChainlinkClient {
// Stores the answer from the Chainlink oracle
uint256 public currentPrice;
address public owner;
// The address of an oracle - you can find node addresses on https://market.link/search/nodes
address ORACLE_ADDRESS = 0xB36d3709e22F7c708348E225b20b13eA546E6D9c;
// The address of the http get job that returns a uint256
// you can find job IDs on https://market.link/search/jobs
string constant JOBID = "f9528decb5c64044b6b4de54ca7ea63e";
// 1 LINK / 10 = 0.1 LINK
uint256 constant private ORACLE_PAYMENT = 1 * LINK / 10;
constructor() public {
setPublicChainlinkToken();
owner = msg.sender;
}
// Creates a Chainlink request with the uint256 multiplier job
// Ideally, you'd want to pass the oracle payment, address, and jobID as parameters as well
// This will return the one day lagged price of whatever ticker you give it
function requestStockPrice(string memory ticker)
public
onlyOwner
{
// newRequest takes a JobID, a callback address, and callback function as input
Chainlink.Request memory req = buildChainlinkRequest(stringToBytes32(JOBID), address(this), this.fulfill.selector);
// you'll notice you just have to add the parameters from the query of an alpha vantage call
// we are hard coding "GLOBAL_QUOTE"
req.add("function", "GLOBAL_QUOTE");
req.add("symbol", ticker);
// Uses input param (dot-delimited string) as the "path" in the request parameters
// you'll notice this is the same as the other
string[] memory copyPath = new string[](2);
copyPath[0] = "Global Quote";
copyPath[1] = "05. price";
req.addStringArray("copyPath", copyPath);
// Adds an integer with the key "times" to the request parameters
req.addInt("times", 100000000);
// Sends the request with the amount of payment specified to the oracle
sendChainlinkRequestTo(ORACLE_ADDRESS, req, ORACLE_PAYMENT);
}
// fulfill receives a uint256 data type
function fulfill(bytes32 _requestId, uint256 _price)
public
// Use recordChainlinkFulfillment to ensure only the requesting oracle can fulfill
recordChainlinkFulfillment(_requestId)
{
currentPrice = _price;
}
// withdrawLink allows the owner to withdraw any extra LINK on the contract
function withdrawLink()
public
onlyOwner
{
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer");
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
// A helper funciton to make the string a bytes32
function stringToBytes32(string memory source) private pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly { // solhint-disable-line no-inline-assembly
result := mload(add(source, 32))
}
}
}
Disclosure: I run the Alpha Chain Chainlink node, please feel free to use any node that you wish. Also, in a production system you will want to use many different nodes with different data sources to prevent a centralized point of failure
Answered by Patrick Collins on December 23, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP