link_king300
link_king300

Reputation: 31

How do I use a Chainlink api call to retrieve a string value from json returne by api

I am trying to use a chainlink request to make an api call, then update the volume variable with the result of the api call.

The api call should retrieve a string. After deploying and funding the smart contract it seems I am able to make the api call successfully (although I'm not sure about this). The problem is the volume variable isn't updating.

I adapted the code from a chainlink tutorial. The original tutorial code works, to my mind the changes I made should also work. Can anyone help with this?

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

import "@chainlink/contracts/src/v0.8/ChainlinkClient.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 WHICH USES HARDCODED VALUES FOR CLARITY.
 * PLEASE DO NOT USE THIS CODE IN PRODUCTION.
 */
contract APIConsumer is ChainlinkClient {
    using Chainlink for Chainlink.Request;
  
    string public volume;
    
    address private oracle;
    bytes32 private jobId;
    uint256 private fee;
    
    /**
     * Network: Kovan
     * Oracle: 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8 (Chainlink Devrel   
     * Node)
     * Job ID: d5270d1c311941d0b08bead21fea7747
     * Fee: 0.1 LINK
     */
    constructor() {
        setPublicChainlinkToken();
        oracle = 0xF405B99ACa8578B9eb989ee2b69D518aaDb90c1F;
        jobId = "c51694e71fa94217b0f4a71b2a6b565a";
        fee = 0.1 * 10 ** 18; // (Varies by network and job)
    }
    
    /**
     * Create a Chainlink request to retrieve API response, find the target
     * data, then multiply by 1000000000000000000 (to remove decimal places from data).
     */
    function requestVolumeData() public returns (bytes32 requestId) 
    {
        Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
        
        // Set the URL to perform the GET request on
        request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
        
        // Set the path to find the desired data in the API response, where the response format is:
        // {"RAW":
        //   {"ETH":
        //    {"USD":
        //     {
        //      "VOLUME24HOUR": xxx.xxx,
        //     }
        //    }
        //   }
        //  }
        request.add("path", "RAW.ETH.USD.MARKET");
        
        // // Multiply the result by 1000000000000000000 to remove decimals
        // int timesAmount = 10**18;
        // request.addInt("times", timesAmount);
        
        // Sends the request
        return sendChainlinkRequestTo(oracle, request, fee);
    }
    
    /**
     * Receive the response in the form of uint256
     */ 
    function fulfill(bytes32 _requestId, bytes32 _volume) public recordChainlinkFulfillment(_requestId)
    {
        volume = bytes32ToString(_volume);
        
    }
    
    
    function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
        uint8 i = 0;
        while(i < 32 && _bytes32[i] != 0) {
            i++;
        }
        bytes memory bytesArray = new bytes(i);
        for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return string(bytesArray);
    }

    // function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
}

Upvotes: 3

Views: 807

Answers (4)

Derek
Derek

Reputation: 71

Below is a working example that will retrieve your string object on Ethereum Sepolia testnet.

The following Chainlink consumer contract uses the function getResponseString() to typecast the returned bytes object into a string object:


//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";

/**
 * LinkWell Nodes consumer contract example
 */

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 */

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

    address private oracleAddress;
    bytes32 private jobId;
    uint256 private fee;
    
    constructor() ConfirmedOwner(msg.sender) {
    
        // LinkWell Nodes oracle address and jobId
        setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789);
        setOracleAddress(0x0FaCf846af22BCE1C7f88D1d55A038F27747eD2B);
        setJobId("8ced832954544a3c98543c94a51d6a8d");
    
        setFeeInHundredthsOfLink(0);     // 0 LINK
    }

    // Send a request to the Chainlink oracle
    function request() public {
    
        Chainlink.Request memory req = buildOperatorRequest(jobId, this.fulfill.selector);
             
        // DEFINE THE REQUEST PARAMETERS (example)
        req.add('method', 'GET');
        req.add('url', 'https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD');
        req.add('headers', '["accept", "application/json"]');
        req.add('body', '');
        req.add('contact', 'anonymous');
                
        // PROCESS THE RESULT (example)
        req.add('path', 'RAW,ETH,USD,MARKET');
        
        // Send the request to the Chainlink oracle
        sendOperatorRequest(req, fee);
    }

    bytes public responseBytes;

    // Receive the result from the Chainlink oracle
    event RequestFulfilled(bytes32 indexed requestId);
    function fulfill(bytes32 requestId, bytes memory bytesData) public recordChainlinkFulfillment(requestId) {
        // Process the oracle response
        // emit RequestFulfilled(requestId);    // (optional) emits this event in the on-chain transaction logs, allowing Web3 applications to listen for this transaction
        responseBytes = bytesData;              // example value: 0x426974636f696e
    }
    
    // Retrieve the response data as a string
    function getResponseString() public view onlyOwner returns (string memory) {
        return string(responseBytes);               // example value: Bitcoin
    }

    // Update oracle address
    function setOracleAddress(address _oracleAddress) public onlyOwner {
        oracleAddress = _oracleAddress;
        setChainlinkOracle(_oracleAddress);
    }
    function getOracleAddress() public view onlyOwner returns (address) {
        return oracleAddress;
    }

    // Update jobId
    function setJobId(string memory _jobId) public onlyOwner {
        jobId = bytes32(bytes(_jobId));
    }
    function getJobId() public view onlyOwner returns (string memory) {
        return string(abi.encodePacked(jobId));
    }
    
    // Update fees
    function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
        fee = _feeInJuels;
    }
    function setFeeInHundredthsOfLink(uint256 _feeInHundredthsOfLink) public onlyOwner {
        setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
    }
    function getFeeInHundredthsOfLink() public view onlyOwner returns (uint256) {
        return (fee * 100) / LINK_DIVISIBILITY;
    }

    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }
}

If you're looking to replicate this result on other testnets besides Ethereum Sepolia, you can replace the oracle contract address with the one for the corresponding testnet.

Oracle contract addresses for other testnets are as follows:

  • Arbitrum Sepolia: 0xd36c6B1777c7f3Db1B3201bDD87081A9045B7b46
  • Avalanche Fuji: 0xd0EbC86a4f67654B654Feb0e615d7f5C139a6406
  • Base Goerli: 0xa57f0cEd49bB1ed7327f950B12a8419cFD01855f
  • Binance Smart Chain testnet: 0xd08FEb8203E76f836D74608595346ab6b0f768C9
  • Ethereum Goerli: 0xB9C47B9609174716CE536324d4FbEad9292c1d3a
  • Optimism Goerli: 0x14bc7F6Da6cA3E072793c185e01a76E62341CC61
  • Polygon Mumbai: 0x12A3d7759F745f4cb8EE8a647038c040cB8862A5

I hope that helps!

Upvotes: 0

Vince Reid
Vince Reid

Reputation: 11

I had a similar issue, and you should be able to do this without the bytes to string function. What worked for me was:

  • Ensuring that the returned data value definition is matching the stored data definition. So in your solidity API consumer file you need to redefine the fulfill function _volume argument to string memory so that it can be assigned to the string storage value for volume at the top of the contract.
  • The variable needs to have identical name in the node Job return data as in the receiving fulfill function, if its _volume in the TOML file encode_mwr it should also be '_volume' in the solidity fulfill function arguments.
  • Other thing to check is to make sure that the node Job has the _volume data defined as a string in the encode_mwr. This can also be extended to return multiple values of mixed types of uint, int, string, bytes in a single call.

Hope this helps someone and sorry if my response is not well formatted, it is my first answer I have submitted to stackoverflow.

e.g below TOML code snippet:

encode_mwr [type="ethabiencode"
            abi="(bytes32 requestId, string _volume)"
            data="{\\"requestId\\": $(decode_log.requestId), \\"_volume\\": $(volume_parse)}"
            ]

Edited solidity file:

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

import "@chainlink/contracts/src/v0.8/ChainlinkClient.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 WHICH USES HARDCODED VALUES FOR CLARITY.
 * PLEASE DO NOT USE THIS CODE IN PRODUCTION.
 */
contract APIConsumer is ChainlinkClient {
    using Chainlink for Chainlink.Request;
  
    string public volume;
    
    address private oracle;
    bytes32 private jobId;
    uint256 private fee;
    
    /**
     * Network: Kovan
     * Oracle: 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8 (Chainlink Devrel   
     * Node)
     * Job ID: d5270d1c311941d0b08bead21fea7747
     * Fee: 0.1 LINK
     */
    constructor() {
        setPublicChainlinkToken();
        oracle = 0xF405B99ACa8578B9eb989ee2b69D518aaDb90c1F;
        jobId = "c51694e71fa94217b0f4a71b2a6b565a";
        fee = 0.1 * 10 ** 18; // (Varies by network and job)
    }
    
    /**
     * Create a Chainlink request to retrieve API response, find the target
     * data, then multiply by 1000000000000000000 (to remove decimal places from data).
     */
    function requestVolumeData() public returns (bytes32 requestId) 
    {
        Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
        
        // Set the URL to perform the GET request on
        request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
        
        // Set the path to find the desired data in the API response, where the response format is:
        // {"RAW":
        //   {"ETH":
        //    {"USD":
        //     {
        //      "VOLUME24HOUR": xxx.xxx,
        //     }
        //    }
        //   }
        //  }
        request.add("path", "RAW.ETH.USD.MARKET");
        
        // // Multiply the result by 1000000000000000000 to remove decimals
        // int timesAmount = 10**18;
        // request.addInt("times", timesAmount);
        
        // Sends the request
        return sendChainlinkRequestTo(oracle, request, fee);
    }
    
    /**
     * Receive the response in the form of uint256
     */ 
    function fulfill(bytes32 _requestId, string memory _volume) public recordChainlinkFulfillment(_requestId)
    {
        volume = _volume;
        
    }
    
           // function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
}

Upvotes: 1

MutantMahesh
MutantMahesh

Reputation: 1717

Here is one working example with AviationStack. Replace the <access_key> with your access_key, in the API URL.

//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 AviationGenericLargeResponse is ChainlinkClient, ConfirmedOwner {
    using Chainlink for Chainlink.Request;

    // variable bytes(arbitrary-length raw byte data) returned in a single oracle response
    bytes public data;
    string public flightStatus;

    bytes32 private jobId;
    uint256 private fee;

    event RequestFulfilled(bytes32 indexed requestId, bytes indexed data);
    /**
     * @notice Initialize the link token and target oracle
     * @dev The oracle address must be an Operator contract for multiword response
     *
     *
     * Goerli Testnet details:
     * Link Token: 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
     * Oracle: 0xCC79157eb46F5624204f47AB42b3906cAA40eaB7 (Chainlink DevRel)
     * jobId: 7da2702f37fd48e5b1b9a5715e3509b6
     *
     */
    constructor() ConfirmedOwner(msg.sender) {
        setChainlinkToken(0x326C977E6efc84E512bB9C30f76E30c160eD06FB);
        setChainlinkOracle(0xCC79157eb46F5624204f47AB42b3906cAA40eaB7);
        jobId = '7da2702f37fd48e5b1b9a5715e3509b6';
        fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varies by network and job)
    }

    /**
     * @notice Request variable bytes from the oracle
     */
    function requestBytes() public {
        Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfillBytes.selector);
        req.add(
            'get',
            'http://api.aviationstack.com/v1/flights?access_key=a6656fabf7fbe4fde6e4061f5be7042a&flight_number=611&airline_name=Qantas&flight_status=landed&limit=1'
        );
        req.add('path', 'data,0,flight_status');
        sendChainlinkRequest(req, fee);
    }


    /**
     * @notice Fulfillment function for variable bytes
     * @dev This is called by the oracle. recordChainlinkFulfillment must be used.
     */
    function fulfillBytes(bytes32 requestId, bytes memory bytesData) public recordChainlinkFulfillment(requestId) {
        emit RequestFulfilled(requestId, bytesData);
        data = bytesData;
        flightStatus = string(data);
    }

    /**
     * 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');
    }
}

Upvotes: 1

Patrick Collins
Patrick Collins

Reputation: 6131

To return a string, you actually have to return a bytes32 and cast it to a string on-chain.

You can use something like this, to turn a bytes32 to string:

function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
        uint8 i = 0;
        while(i < 32 && _bytes32[i] != 0) {
            i++;
        }
        bytes memory bytesArray = new bytes(i);
        for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return string(bytesArray);
    }

Upvotes: 1

Related Questions