0

I have 2 Smart Contracts, the StockAPI which is a ChainlinkClient and the RepToken Smart Contract. The StockAPI is the Chainlink API Consumer and is called by RepToken via the evaluatePredictions function. Now I want to be able to return the API response back to the RepToken Smart Contract via the fulfill function.

I already looked at this example and did exactly what was proposed:

How to return a Chainlink API Call to another contract?

According to that, I created the following Smart Contracts:

contract StockAPI is ChainlinkClient {
    constructor() public {
        owner = payable(msg.sender);
        setPublicChainlinkToken();
        oracle = 0x3A56aE4a2831C3d3514b5D7Af5578E45eBDb7a40;
        jobId = "187bb80e5ee74a139734cac7475f3c6e";
        fee = 0.01 * 10**18; // 0.01 LINK
    }

    address payable public owner;
    address private oracle;

    uint256 private fee;

    bytes32 private jobId;

    function requestStockPrice(
        address _cbContract,
        bytes4 _cbFunction,
        string calldata _symbol,
        string calldata _date
    ) external returns (bytes32 requestId) {
        Chainlink.Request memory request =
            buildChainlinkRequest(jobId, _cbContract, _cbFunction);
        request.add(
            "get",
            string(
                abi.encodePacked(
                    "https://api.twelvedata.com/time_series?symbol=",
                    _symbol,
                    "&exchange=XETR&start_date=",
                    _date,
                    "&end_date=",
                    _date,
                    "&interval=1min&apikey=key"
                )
            )
        );
        request.add("path", "values.0.close");

        return sendChainlinkRequestTo(oracle, request, fee);
    }
}

contract RepToken is ChainlinkClient {
    using SafeMath for uint256;

    PredictionsDB private predictionsDB;
    StockAPI private stockAPI;

    constructor(PredictionsDB _predictionsDB, StockAPI _stockAPI) public {
        owner = payable(msg.sender);
        predictionsDB = _predictionsDB;
        stockAPI = _stockAPI;
    }

    address payable public owner;

    string public constant symbol = "REP";
    string public constant name = "REP Token";

    uint256 public totalSupply;
    uint256 public volume;

    uint8 public decimals = 18;

    bytes32[] public ids;

    struct Prediction {
        address predictor;
        string symbol;
        string date;
        uint256 unixDate;
        uint256 price;
        bool checked;
    }

    mapping(address => uint256) public balanceOf;
    mapping(bytes32 => Prediction) public requestMapping;

    event RepTokensMinted(address indexed to, uint256 totalSupply);
    event RepTokensBurned(address indexed from, uint256 totalSupply);

    function evaluatePredictions(address _predictor) external {
        (
            string[] memory symbols,
            string[] memory dates,
            uint256[] memory unixDates,
            uint256[] memory prices,
            bool[] memory checks
        ) = predictionsDB.getPredictions(_predictor);
        require(
            _predictor == msg.sender && prices.length > 0,
            "Not the predictor or no predictions!"
        );
        for (uint256 i = 0; i < prices.length; i = i.add(1)) {
            if (unixDates[i] > block.timestamp || checks[i] == true) {
                continue;
            } else {
                bytes32 requestId =
                    stockAPI.requestStockPrice(
                        address(this),
                        this.fulfillEvaluation.selector,
                        symbols[i],
                        dates[i]
                    );
                checks[i] = true;
                requestMapping[requestId] = Prediction(
                    _predictor,
                    symbols[i],
                    dates[i],
                    unixDates[i],
                    prices[i],
                    checks[i]
                );
                ids.push(requestId);
            }
        }
    }

    function parseInt(string memory _a, uint256 _b)
        private
        pure
        returns (uint256)
    {
        ...
    }

    function mint(address _predictor) private {
        ...
    }

    function burn(address _predictor) private {
        ...
    }

    function fulfillEvaluation(bytes32 _requestId, bytes32 _close)
        public
        recordChainlinkFulfillment(_requestId)
    {
        uint256 i;
        while (i < 32 && _close[i] != 0) {
            i = i.add(1);
        }
        bytes memory bytesArray = new bytes(i);
        for (i = 0; i < 32 && _close[i] != 0; i = i.add(1)) {
            bytesArray[i] = _close[i];
        }
        uint256 close = parseInt(string(bytesArray), 5);
        if (requestMapping[_requestId].price <= close) {
            mint(requestMapping[_requestId].predictor);
        } else {
            burn(requestMapping[_requestId].predictor);
        }
        delete requestMapping[_requestId];
    }
}

The function stockPriceAPI.requestStockPrice calls the method in the StockAPI contract and passes the address of the RepToken contract, the selector of the fulfillEvaluation function as well as a stock symbol and a date as a string for the API url.

The request is then executed in the StockAPI contract and now I would expect to receive the value in the fulfillEvaluation callback function in RepToken contract, but it seems that it doesn't receive any value. The API call itself definitely works, because I can see calls in the API dashboard, but somehow the data doesn't come back to the RepToken contract.

I would be glad if someone can help.

4
  • 1
    What values are you passing in to the call to requestStockPrice function? Assuming they're correct, I can't see any issues with the code you've put on first glance. Can you update the question to include your full code for both contracts, as well as what values you're passing in to the function calls Jun 21 at 2:14
  • @HarryPapacharissiou Hey thanks for the help, I updated the question. Jun 22 at 2:17
  • Your code looks good. Is the node your calling working correctly? Have you checked to see if anything is being returned at all? Jun 22 at 16:56
  • @PatrickCollins Yes, the node should work correctly, because if I put the code of the StockAPI smart contract into the RepToken contract, everything works fine. However, I want to encapsulate the logic. It seems that there is nothing returned. Jun 22 at 17:26
0

The recordChainlinkFulfillment modifier looks like this:

modifier recordChainlinkFulfillment(bytes32 _requestId) {
    require(msg.sender == pendingRequests[_requestId],
            "Source must be the oracle of the request");
    delete pendingRequests[_requestId];
    emit ChainlinkFulfilled(_requestId);
    _;
  }

This means, that using this modifier, you can't do cross-contract API calls, since the pendingRequests array won't be on multiple contracts.

Instead, you'll want to write your own modifier for the callback function where you pass in the array.

Or, simply remove the recordChainlinkFulfillment modifier - however just know that anyone can then call that function.

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.