1

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?

1

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.

1. The blockchain oracle API call

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.

2. The oracle already has access to the data

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

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.