Skip to content

Instantly share code, notes, and snippets.

@prayagsingh
Created August 7, 2021 07:36
Show Gist options
  • Save prayagsingh/860bfb0e4910ed2f181e78e249ff0691 to your computer and use it in GitHub Desktop.
Save prayagsingh/860bfb0e4910ed2f181e78e249ff0691 to your computer and use it in GitHub Desktop.
Polygon-Chainlink ConsumerAPI integration using solidity v0.8.0;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Chainlink.sol";
import "./interfaces/ENSInterface.sol";
import "./interfaces/LinkTokenInterface.sol";
import "./interfaces/OperatorInterface.sol";
import "./interfaces/PointerInterface.sol";
import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol";
/**
* @title The ChainlinkClient contract
* @notice Contract writers can inherit this contract in order to create requests for the
* Chainlink network
*/
contract ChainlinkClient {
using Chainlink for Chainlink.Request;
uint256 constant internal LINK_DIVISIBILITY = 10**18;
uint256 constant private AMOUNT_OVERRIDE = 0;
address constant private SENDER_OVERRIDE = address(0);
uint256 constant private ORACLE_ARGS_VERSION = 1;
uint256 constant private OPERATOR_ARGS_VERSION = 2;
bytes32 constant private ENS_TOKEN_SUBNAME = keccak256("link");
bytes32 constant private ENS_ORACLE_SUBNAME = keccak256("oracle");
address constant private LINK_TOKEN_POINTER = 0xC89bD4E1632D3A43CB03AAAd5262cbe4038Bc571;
ENSInterface private ens;
bytes32 private ensNode;
LinkTokenInterface private link;
OperatorInterface private oracle;
uint256 private requestCount = 1;
mapping(bytes32 => address) private pendingRequests;
event ChainlinkRequested(
bytes32 indexed id
);
event ChainlinkFulfilled(
bytes32 indexed id
);
event ChainlinkCancelled(
bytes32 indexed id
);
/**
* @notice Creates a request that can hold additional parameters
* @param specId The Job Specification ID that the request will be created for
* @param callbackAddress The callback address that the response will be sent to
* @param callbackFunctionSignature The callback function signature to use for the callback address
* @return A Chainlink Request struct in memory
*/
function buildChainlinkRequest(
bytes32 specId,
address callbackAddress,
bytes4 callbackFunctionSignature
)
internal
pure
returns (
Chainlink.Request memory
)
{
Chainlink.Request memory req;
return req.initialize(specId, callbackAddress, callbackFunctionSignature);
}
/**
* @notice Creates a Chainlink request to the stored oracle address
* @dev Calls `chainlinkRequestTo` with the stored oracle address
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function sendChainlinkRequest(
Chainlink.Request memory req,
uint256 payment
)
internal
returns (
bytes32
)
{
return sendChainlinkRequestTo(address(oracle), req, payment);
}
/**
* @notice Creates a Chainlink request to the specified oracle address
* @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to
* send LINK which creates a request on the target oracle contract.
* Emits ChainlinkRequested event.
* @param oracleAddress The address of the oracle for the request
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function sendChainlinkRequestTo(
address oracleAddress,
Chainlink.Request memory req,
uint256 payment
)
internal
returns (
bytes32 requestId
)
{
return rawRequest(oracleAddress, req, payment, ORACLE_ARGS_VERSION, oracle.oracleRequest.selector);
}
/**
* @notice Creates a Chainlink request to the stored oracle address
* @dev This function supports multi-word response
* @dev Calls `requestOracleDataFrom` with the stored oracle address
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function requestOracleData(
Chainlink.Request memory req,
uint256 payment
)
internal
returns (
bytes32
)
{
return requestOracleDataFrom(address(oracle), req, payment);
}
/**
* @notice Creates a Chainlink request to the specified oracle address
* @dev This function supports multi-word response
* @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to
* send LINK which creates a request on the target oracle contract.
* Emits ChainlinkRequested event.
* @param oracleAddress The address of the oracle for the request
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @return requestId The request ID
*/
function requestOracleDataFrom(
address oracleAddress,
Chainlink.Request memory req,
uint256 payment
)
internal
returns (
bytes32 requestId
)
{
return rawRequest(oracleAddress, req, payment, OPERATOR_ARGS_VERSION, oracle.requestOracleData.selector);
}
/**
* @notice Make a request to an oracle
* @param oracleAddress The address of the oracle for the request
* @param req The initialized Chainlink Request
* @param payment The amount of LINK to send for the request
* @param argsVersion The version of data support (single word, multi word)
* @return requestId The request ID
*/
function rawRequest(
address oracleAddress,
Chainlink.Request memory req,
uint256 payment,
uint256 argsVersion,
bytes4 funcSelector
)
private
returns (
bytes32 requestId
)
{
requestId = keccak256(abi.encodePacked(this, requestCount));
req.nonce = requestCount;
pendingRequests[requestId] = oracleAddress;
emit ChainlinkRequested(requestId);
bytes memory encodedData = abi.encodeWithSelector(
funcSelector,
SENDER_OVERRIDE, // Sender value - overridden by onTokenTransfer by the requesting contract's address
AMOUNT_OVERRIDE, // Amount value - overridden by onTokenTransfer by the actual amount of LINK sent
req.id,
req.callbackAddress,
req.callbackFunctionId,
req.nonce,
argsVersion,
req.buf.buf);
require(link.transferAndCall(oracleAddress, payment, encodedData), "unable to transferAndCall to oracle");
requestCount += 1;
}
/**
* @notice Allows a request to be cancelled if it has not been fulfilled
* @dev Requires keeping track of the expiration value emitted from the oracle contract.
* Deletes the request from the `pendingRequests` mapping.
* Emits ChainlinkCancelled event.
* @param requestId The request ID
* @param payment The amount of LINK sent for the request
* @param callbackFunc The callback function specified for the request
* @param expiration The time of the expiration for the request
*/
function cancelChainlinkRequest(
bytes32 requestId,
uint256 payment,
bytes4 callbackFunc,
uint256 expiration
)
internal
{
OperatorInterface requested = OperatorInterface(pendingRequests[requestId]);
delete pendingRequests[requestId];
emit ChainlinkCancelled(requestId);
requested.cancelOracleRequest(requestId, payment, callbackFunc, expiration);
}
/**
* @notice Sets the stored oracle address
* @param oracleAddress The address of the oracle contract
*/
function setChainlinkOracle(
address oracleAddress
)
internal
{
oracle = OperatorInterface(oracleAddress);
}
/**
* @notice Sets the LINK token address
* @param linkAddress The address of the LINK token contract
*/
function setChainlinkToken(
address linkAddress
)
internal
{
link = LinkTokenInterface(linkAddress);
}
/**
* @notice Sets the Chainlink token address for the public
* network as given by the Pointer contract
*/
function setPublicChainlinkToken()
internal
{
setChainlinkToken(PointerInterface(LINK_TOKEN_POINTER).getAddress());
}
/**
* @notice Retrieves the stored address of the LINK token
* @return The address of the LINK token
*/
function chainlinkTokenAddress()
internal
view
returns (
address
)
{
return address(link);
}
/**
* @notice Retrieves the stored address of the oracle contract
* @return The address of the oracle contract
*/
function chainlinkOracleAddress()
internal
view
returns (
address
)
{
return address(oracle);
}
/**
* @notice Allows for a request which was created on another contract to be fulfilled
* on this contract
* @param oracleAddress The address of the oracle contract that will fulfill the request
* @param requestId The request ID used for the response
*/
function addChainlinkExternalRequest(
address oracleAddress,
bytes32 requestId
)
internal
notPendingRequest(requestId)
{
pendingRequests[requestId] = oracleAddress;
}
/**
* @notice Sets the stored oracle and LINK token contracts with the addresses resolved by ENS
* @dev Accounts for subnodes having different resolvers
* @param ensAddress The address of the ENS contract
* @param node The ENS node hash
*/
function useChainlinkWithENS(
address ensAddress,
bytes32 node
)
internal
{
ens = ENSInterface(ensAddress);
ensNode = node;
bytes32 linkSubnode = keccak256(abi.encodePacked(ensNode, ENS_TOKEN_SUBNAME));
ENSResolver_Chainlink resolver = ENSResolver_Chainlink(ens.resolver(linkSubnode));
setChainlinkToken(resolver.addr(linkSubnode));
updateChainlinkOracleWithENS();
}
/**
* @notice Sets the stored oracle contract with the address resolved by ENS
* @dev This may be called on its own as long as `useChainlinkWithENS` has been called previously
*/
function updateChainlinkOracleWithENS()
internal
{
bytes32 oracleSubnode = keccak256(abi.encodePacked(ensNode, ENS_ORACLE_SUBNAME));
ENSResolver_Chainlink resolver = ENSResolver_Chainlink(ens.resolver(oracleSubnode));
setChainlinkOracle(resolver.addr(oracleSubnode));
}
/**
* @notice Ensures that the fulfillment is valid for this contract
* @dev Use if the contract developer prefers methods instead of modifiers for validation
* @param requestId The request ID for fulfillment
*/
function validateChainlinkCallback(
bytes32 requestId
)
internal
recordChainlinkFulfillment(requestId)
// solhint-disable-next-line no-empty-blocks
{}
/**
* @dev Reverts if the sender is not the oracle of the request.
* Emits ChainlinkFulfilled event.
* @param requestId The request ID for fulfillment
*/
modifier recordChainlinkFulfillment(
bytes32 requestId
)
{
require(msg.sender == pendingRequests[requestId],
"Source must be the oracle of the request");
delete pendingRequests[requestId];
emit ChainlinkFulfilled(requestId);
_;
}
/**
* @dev Reverts if the request is already pending
* @param requestId The request ID for fulfillment
*/
modifier notPendingRequest(
bytes32 requestId
)
{
require(pendingRequests[requestId] == address(0), "Request is already pending");
_;
}
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.5;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
contract APIConsumer is ChainlinkClient {
uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Matic Mumbai Testnet
* Oracle: 0x58bbdbfb6fca3129b91f0dbe372098123b38b5e9
* Job ID: da20aae0e4c843f6949e5cb3f7cfe8c4
* LINK address: 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
* Fee: 0.01 LINK
*/
constructor() {
//setPublicChainlinkToken;
setChainlinkToken(0x326C977E6efc84E512bB9C30f76E30c160eD06FB);
//oracle = 0x58bbdbfb6fca3129b91f0dbe372098123b38b5e9;
oracle = 0x58BBDbfb6fca3129b91f0DBE372098123B38B5e9;
jobId = "da20aae0e4c843f6949e5cb3f7cfe8c4";
fee = 0.01 * 10 ** 18; // 0.01 LINK
}
/**
* 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:
request.add("path", "RAW.ETH.USD.VOLUME24HOUR");
// 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, uint256 _volume) public recordChainlinkFulfillment(_requestId)
{
volume = _volume;
}
}
@prayagsingh
Copy link
Author

Getting the below error when using v0.8/ChainlinkClient.sol

TypeError: Member "add" not found or not visible after argument-dependent lookup in struct Chainlink.Request memory.
  --> contracts/APIConsumer.sol:40:9:
   |
40 |         request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
   |         ^^^^^^^^^^^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment