NEW

Attend SmartCon 2023 to explore the future of Web3. Sign up now.

Multi-Variable Responses

This guide explains how to make an HTTP GET request to an external API from a smart contract, using Chainlink's Request & Receive Data cycle and then receive multiple responses. This is known as multi-variable or multi-word responses.

Example

This example shows how to:

  • Fetch several responses in one single call.

Cryptocompare GET /data/price/ API returns the current price of any cryptocurrency in any other currency that you need. To check the response, you can directly paste the following URL in your browser https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=BTC or run this command in your terminal:

curl -X 'GET' \
  'https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=BTC' \
  -H 'accept: application/json'

The response should be similar to the following:

{
  "BTC": 0.07297
}

The request above shows how to get the price of ETH against BTC. Now let say we want the price of ETH against several currencies: BTC, USD, and EUR. Our contract will have to support receiving multiple responses. To consume an API with multiple responses, your contract should inherit from ChainlinkClient. This contract exposes a struct called Chainlink.Request, which your contract should use to build the API request. The request should include the following parameters:

  • Link token address
  • Oracle address
  • Job id
  • Request fee
  • Task parameters
  • Callback function signature

Assume that a user wants to obtain the ETH price quoted against three different currencies: BTC , USD and EUR. If they use only a single-word job, it would require three different requests. For a comparison, see the Single Word Response example. To make these requests more efficient, use multi-word responses to do it all in a single request as shown in the following example:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract MultiWordConsumer is ChainlinkClient, ConfirmedOwner {
    using Chainlink for Chainlink.Request;

    bytes32 private jobId;
    uint256 private fee;

    // multiple params returned in a single oracle response
    uint256 public btc;
    uint256 public usd;
    uint256 public eur;

    event RequestMultipleFulfilled(
        bytes32 indexed requestId,
        uint256 btc,
        uint256 usd,
        uint256 eur
    );

    /**
     * @notice Initialize the link token and target oracle
     * @dev The oracle address must be an Operator contract for multiword response
     *
     *
     * Sepolia Testnet details:
     * Link Token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
     * Oracle: 0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD (Chainlink DevRel)
     * jobId: 53f9755920cd451a8fe46f5087468395
     *
     */
    constructor() ConfirmedOwner(msg.sender) {
        setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789);
        setChainlinkOracle(0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD);
        jobId = "53f9755920cd451a8fe46f5087468395";
        fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varies by network and job)
    }

    /**
     * @notice Request mutiple parameters from the oracle in a single transaction
     */
    function requestMultipleParameters() public {
        Chainlink.Request memory req = buildChainlinkRequest(
            jobId,
            address(this),
            this.fulfillMultipleParameters.selector
        );
        req.add(
            "urlBTC",
            "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=BTC"
        );
        req.add("pathBTC", "BTC");
        req.add(
            "urlUSD",
            "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"
        );
        req.add("pathUSD", "USD");
        req.add(
            "urlEUR",
            "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=EUR"
        );
        req.add("pathEUR", "EUR");
        sendChainlinkRequest(req, fee); // MWR API.
    }

    /**
     * @notice Fulfillment function for multiple parameters in a single request
     * @dev This is called by the oracle. recordChainlinkFulfillment must be used.
     */
    function fulfillMultipleParameters(
        bytes32 requestId,
        uint256 btcResponse,
        uint256 usdResponse,
        uint256 eurResponse
    ) public recordChainlinkFulfillment(requestId) {
        emit RequestMultipleFulfilled(
            requestId,
            btcResponse,
            usdResponse,
            eurResponse
        );
        btc = btcResponse;
        usd = usdResponse;
        eur = eurResponse;
    }

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }
}

To use this contract:

  1. Open the contract in Remix.

  2. Compile and deploy the contract using the Injected Provider environment. The contract includes all the configuration variables for the Sepolia testnet. Make sure your wallet is set to use Sepolia. The constructor sets the following parameters:

    • The Chainlink Token address for Sepolia by calling the setChainlinkToken function.
    • The Oracle contract address for Sepolia by calling the setChainlinkOracle function.
    • The jobId: A specific job for the oracle node to run. In this case, you must call a job that is specifically configured to return ETH price against BTC, USD and EUR. You can find the job spec for the Chainlink node here.
  3. Fund your contract with 0.1 LINK. To learn how to send LINK to contracts, read the Fund Your Contracts page.

  4. Call the btc, usd , and eur functions to confirm that the respective btc, usd , and eur state variables are equal to zero.

  5. Run the requestMultipleParameters function. This builds the Chainlink.Request using the correct parameters:

    • The req.add("urlBTC", "<cryptocompareETHBTCURL>") request parameter provides the oracle node with the url where to fetch the ETH-BTC price. Same logic for req.add("urlEUR", "<cryptocompareETHEURURL>") and req.add("urlUSD", "<cryptocompareETHUSDURL>").
    • THe req.add('pathBTC', 'BTC') request parameter tells the oracle node where to fetch the ETH-BTC price in the json response. Same logic for req.add('pathUSD', 'EUR') and req.add('pathEUR', 'USD'). Because you provide the URLs and paths, the MultiWordConsumer in the example can call any public API as long as the URLs and paths are correct.
  6. After few seconds, call the btc, usd , and eur functions. You should get a non-zero responses. The job spec for the Chainlink node in this example can be found here.

Response Types

Make sure to choose an oracle job that supports the data type that your contract needs to consume. Multiple data types are available such as:

  • uint256 - Unsigned integers
  • int256 - Signed integers
  • bool - True or False values
  • string - String
  • bytes32 - Strings and byte values. If you need to return a string, use bytes32. Here's one method of converting bytes32 to string. Currently, any return value must fit within 32 bytes. If the value is bigger than that, make multiple requests.
  • bytes - Arbitrary-length raw byte data

The setChainlinkToken function sets the LINK token address for the network you are deploying to. The setChainlinkOracle function sets a specific Chainlink oracle that a contract makes an API call from. The jobId refers to a specific job for that node to run.

Each job is unique and returns different types of data. For example, a job that returns a bytes32 variable from an API would have a different jobId than a job that retrieved the same data, but in the form of a uint256 variable.

Check the Find Existing Jobs page to learn how to find a job suitable to your use case.

What's next

Stay updated on the latest Chainlink news