Chainlink Oracle¶
Introduction¶
Developers can now use Chainlink's decentralized Oracle network to fetch data from a Moonbeam-based network. There are two main architectures: Price Feeds and Basic Request Model. Price Feeds contain real-time price data that is continuously updated by Oracle operators in a smart contract so that other smart contracts can fetch and consume it. The Basic Request Model describes an on-chain architecture for requesting data from a single oracle source. This guide will show you how to fetch the latest price data using both architectures.
Price Feeds¶
Before going into fetching the data itself, it is important to understand the basics of price feeds.
In a standard configuration, each price feed is updated by a decentralized oracle network. Each oracle node is rewarded for publishing the price data to the aggregator contract. The aggregator contract receives periodic data updates from the network of oracles and aggregates and stores the data on-chain so that consumers can easily fetch it. However, the information is only updated if a minimum number of responses from oracle nodes are received (during an aggregation round).
The end-user can retrieve price feeds with read-only operations via an aggregator interface, or via a Consumer interface through the Proxy.
Fetch Price Data¶
There are data feed contracts available for Moonbeam-based networks to help simplify the process of requesting price feeds. In the current configuration for Moonbase Alpha, the Moonbeam team is running only one oracle node that fetches the price from a single API source. Price data is checked and updated in the smart contracts every 12 hours. As such, the price feeds on Moonbase Alpha are not authoritative and are for testing purposes only. The Moonbeam and Moonriver data feed contracts are updated by multiple Chainlink nodes on a regular basis.
The data lives in a series of smart contracts (one per price feed) and can be fetched with the aggregator interface:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
/**
* Returns the decimals to offset on the getLatestPrice call
*/
function decimals() external view returns (uint8);
/**
* Returns the description of the underlying price feed aggregator
*/
function description() external view returns (string memory);
/**
* Returns the version number representing the type of aggregator the proxy points to
*/
function version() external view returns (uint256);
/**
* Returns price data about a specific round
*/
function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
/**
* Returns price data from the latest round
*/
function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
As seen above in the interface, there are five functions for fetching data: decimals
, description
, version
, getRoundData
, and latestRoundData
.
Currently, there are data feed contracts for Moonbeam, Moonriver, and Moonbase Alpha for the following price pairs (proxy addresses):
Base/Quote | Data Feed Contract (Proxy Address) |
---|---|
ATOM to USD | 0x4F152D143c97B5e8d2293bc5B2380600f274a5dd |
BNB to USD | 0x0147f2Ad7F1e2Bc51F998CC128a8355d5AE8C32D |
BTC to USD | 0x8c4425e141979c66423A83bE2ee59135864487Eb |
DOT to USD | 0x1466b4bD0C4B6B8e1164991909961e0EE6a66d8c |
ETH to USD | 0x9ce2388a1696e22F870341C3FC1E89710C7569B5 |
FRAX to USD | 0x05Ec3Fb5B7CB3bE9D7150FBA1Fb0749407e5Aa8a |
GLMR to USD | 0x4497B606be93e773bbA5eaCFCb2ac5E2214220Eb |
LINK to USD | 0xd61D7398B7734aBe7C4B143fE57dC666D2fe83aD |
USDC to USD | 0xA122591F60115D63421f66F752EF9f6e0bc73abC |
Base/Quote | Data Feed Contract (Proxy Address) |
---|---|
1INCH to USD | 0x1466b4bD0C4B6B8e1164991909961e0EE6a66d8c |
AAVE to USD | 0x37f35ef6735c594e6E803bC81577bAC759d8179C |
ANKR to USD | 0x94Ee35E8b9B1b4Cd3BDB720242d6d1796b43C2Ff |
AVAX to USD | 0x992F9B8Aa09B8e084acf4e3213d8b2da5D366D6a |
AXS to USD | 0x9322CeAd48BA0C76Fecc78e82499ce8a829Eab89 |
BNB to USD | 0xD6B013A65C22C372F995864CcdAE202D0194f9bf |
BTC to USD | 0x1B5C6cF9Df1CBF30387C24CC7DB1787CCf65C797 |
BUSD to USD | 0x596129F6ABCaB2E6E81D19284B78eA73C176D170 |
CAKE to USD | 0xc44ecD8C11fd1F281A3d6044CA65e649484B228c |
COMP to USD | 0x29710821d57a1Fc46E2D9FdDE65Df2cF205bad2A |
CRV to USD | 0x03d44d68EdF41c540A90C6eB2BE27C4a75ee689f |
DAI to USD | 0x7ba0e3EbCe25DD3b5A0f36dd7aB34019B863b08D |
DOT to USD | 0x54B584eb643375C41c55ddD8Da4b90124b18d05c |
ETH to USD | 0xc3cF399566220dc5Ed6C8CFbf8247214Af103C72 |
EUR to USD | 0xe6Ccbe1Cb33dF799a59E37a1382c7009dbaBE9ff |
FRAX to USD | 0xD080d4760318710e795B0a59f181f6C1512ffB15 |
FTM to USD | 0x5e70fC5f38cB930F9BE8BEAEaF80CF927Af3B17E |
FXS to USD | 0xE5B624e1098C25C94279bA20A0CC68Fa9215e63b |
KSM to USD | 0x6e0513145FCE707Cd743528DB7C1cAB537DE9d1B |
LINK to USD | 0xdD27789b504fEd690F406A82F16B45a0901172C0 |
LUNA to USD | 0x5F8E0c452EcA522a2208Fff7443515AaFF3cAaE6 |
MANA to USD | 0x424807fA7B16f747CbD30963fAe25fB8Db0b97bF |
MIM to USD | 0xdD6296BD7515271F7E4b10C3A87A2f9863fECa97 |
MKR to USD | 0xD8542f327FaD60b80D8C19025147E6b9d857bb99 |
MOVR to USD | 0x3f8BFbDc1e79777511c00Ad8591cef888C2113C1 |
SAND to USD | 0x5403385DF6eb607fc1fA6983eF5801A11eC7fD9a |
SNX to USD | 0x26E3F9273abC8a01228bE97a106E60FA38b98df2 |
SUSHI to USD | 0x28A9E2747a10eE94D2d7359DEB60023D19FfdD96 |
THETA to USD | 0xA0784167e040906b5580e3c4a53932B288f615ce |
UNI to USD | 0x05Ec3Fb5B7CB3bE9D7150FBA1Fb0749407e5Aa8a |
USDC to USD | 0x12870664a77Dd55bBdcDe32f91EB3244F511eF2e |
USDT to USD | 0xF80DAd54AF79257D41c30014160349896ca5370a |
XRP to USD | 0x3FD363679fb59596d45881bbfBe4bb864f3545A2 |
YFI to USD | 0xE3324ea60FA272BBB4511dDBD4776feFE4674fa0 |
Base/Quote | Data Feed Contract (Proxy Address) |
---|---|
AAVE to USD | 0x9DA76AE39AECA424801DF1b63e9E2750643F9e76 |
ALGO to USD | 0x49E1021974902B4a279fC7D359b4890FC38F8261 |
AVAX to USD | 0xA356990bCDed8Cc6865Be606D64E7381bfe00B72 |
BAND to USD | 0x8997c0A8Dc9d13061FCf303711D1db4278e53a1B |
BNB to USD | 0x4ADfBA6138A5bdA31f980EF5D59Fc7f8440A5D92 |
BTC to USD | 0xa39d8684B8cd74821db73deEB4836Ea46E145300 |
COMP to USD | 0xaa18FFaF5E2f410250D318b57EAd2C68603A382c |
CRV to USD | 0x1a435D01CB50E6B1F8e429a32EE03BF164B43ece |
CVX to USD | 0x3A1Ac4DA79a3c813b2B335d1a631adEec360A59c |
DAI to USD | 0x5A5737F6C0683be63863131bfCAd793a9F828DDa |
DOT to USD | 0xA8B2138c8E765288D4f6fb7D3ebCe2507A006a9C |
ETH to USD | 0x0BAA6E884cfD628b33867F9E081B44a76276fA2D |
FRAX to USD | 0xB616464cB6135AF542de908FAa3FbE3a0b3048e4 |
FTM to USD | 0xD23f056ceD090Bc9Cf6c75d2FB43c0ccC35AD542 |
GLMR to USD | 0x537879A0beA294c1ce04161Ae827919e92C23e92 |
KSM to USD | 0x4E9A1ebc0bEebe3516a8A9a911D781c37414bb39 |
LINK to USD | 0x5310f2d4B531BCEA8126e2aEE40BAd71B707f530 |
MKR to USD | 0x2257c46D4ddF160bACBD00cd67cD6Fd87C48c581 |
OP to USD | 0xC5837B50a86fD87e1D2FbE4FEbFe7C042a9CFBE8 |
stETH to USD | 0x49CEee3f8e9e05f26A81f1122A78D058Ef3c05a9 |
SUSHI to USD | 0xd265FAfc809108D9cf47b6970d750419ba9355B8 |
UNI to USD | 0x326fAFb9E9B5188d7c2e95C0D782ECB19E5EfDF9 |
USDC to USD | 0xCb1c08B86C6EaC617b1bEd5D1E9cD7Fd308FdA4d |
USDT to USD | 0x141Ec95dB9298cB4Bb20CbAC422A42A047a764b1 |
YFI to USD | 0x8E812AA9f56BBAa5B936c8F08B0a260f3eF7cF46 |
For example, you can use the aggregator interface to fetch the price feed of BTC to USD
using Remix. If you need help loading a contract into Remix, check out the Using Remix page of the documentation site.
You will need to connect your MetaMask account to Remix, so make sure you have MetaMask installed and are connected to the correct network. To get help setting up MetaMask, check out the Interacting with Moonbeam Using MetaMask guide.
After creating the file and compiling the contract, you will need to follow these steps:
- Head to the Deploy and Run Transactions tab
- Set the ENVIRONMENT to Injected Web3
- If your MetaMask is already connected it will appear in the ACCOUNT selector. Otherwise, you will be prompted by MetaMask to select and connect your account(s)
- Select the
AggregatorV3Interface
contract from the CONTRACT dropdown -
Enter the Data Feed contract address corresponding to
BTC to USD
in the At Address field and click the At Address button:0x8c4425e141979c66423A83bE2ee59135864487Eb
0x1B5C6cF9Df1CBF30387C24CC7DB1787CCf65C797
0xa39d8684B8cd74821db73deEB4836Ea46E145300
This will create an instance of the aggregator interface that you can interact with and it will appear under the Deployed Contracts section in Remix. To get the latest price data you can follow these steps:
- Expand the
AggregatorV3Interface
contract to reveal the available functions - Click
latestRoundData()
to query the data of the corresponding price feed, in this case BTC to USD
Note that to obtain the real price, you must account for the decimals of the price feed, available with the decimals()
method.
If there is any specific pair you want to be included, feel free to reach out through Discord.
Basic Request Model¶
Before going into fetching the data itself, it is important to understand the basics of the "basic request model." The generic flow for requesting and receiving data from a Chainlink oracle is as follows:
- A client contract creates and makes a request for data to a Chainlink oracle
- The request is sent through the
transferAndCall
function of the LINK token contract, which is an ERC-677 compliant token, that allows tokens to be transferred and relays the request to the oracle contract - Once the token is transferred the LINK contract calls the oracle contract's
onTokenTransfer
function - The oracle contract is owned by the oracle node operators and is responsible for handling on-chain requests made through the LINK token. Once the request is received by the oracle contract an event is emitted to the node which acts upon it
- After the request has been fulfilled by the oracle node, the node uses the
fulfillOracleRequest
function of the oracle contract to return the result to the client via the callback function defined in the original request
When a request for data is created through the client contract, the following parameters need to be passed in to ensure the transaction will go through and the correct information will be returned:
- Oracle address - address of the contract deployed by the oracle node
- Job ID - the task to be executed. An oracle node has a set of job IDs, where each job corresponds to a task that can be requested by a user, for example, fetching a price feed
- Payment - payment in LINK tokens that the oracle will receive for fulfiling the request
Fetching Data¶
There are a few ways you can get started fetching data from an oracle on Moonbeam:
- You can use the pre-deployed client contract, LINK token contract, and oracle contract that rely on the oracle node being run by Moonbeam
- You can create your own custom client contract instead of the pre-deployed client contract to be used with the Moonbeam oracle node
- You can create your own custom contracts and run your own oracle node
The pre-deployed contracts and oracle node run by Moonbeam support a limited set of job IDs that can be used to fetch price data for various asset pairs. If you need additional data, please refer to the Create Custom Contracts using your own Oracle Node section below to learn how to get started.
It's also important to note that the client contract must have a LINK tokens balance to be able to pay for requests. For the pre-deployed setup, the LINK value has been set to zero. If you deploy your own setup, you can also set the LINK value to zero in your ChainlinkClient.sol
contract, and you can choose to deploy your own LINK token contract or use the pre-deployed one.
Use Pre-deployed Contracts¶
If you want to skip the hurdles of deploying all contracts, setting up your oracle node, creating job IDs, and so on, you can use a custom client contract that has already been deployed to Moonbase Alpha. The contract makes all requests to an oracle contract that has also already been deployed, with a zero LINK token payment. These requests are fulfilled by an oracle node that is run by the Moonbeam team.
The client contract deployed on Moonbase Alpha is as follows:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
import "https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.6/ChainlinkClient.sol";
/**
* @title Client based in ChainlinkClient
* @notice End users can deploy this contract to request the Prices from an Oracle
*/
contract Client is ChainlinkClient {
// Stores the answer from the Chainlink oracle
uint256 public currentPrice;
address public owner;
// Deploy with the address of the LINK token
constructor(address _link) public {
// Set the address for the LINK token for the network
setChainlinkToken(_link);
owner = msg.sender;
}
// Creates Chainlink Request
function requestPrice(address _oracle, string memory _jobId, uint256 _payment)
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);
// Sends the request with the amount of payment specified to the oracle
sendChainlinkRequestTo(_oracle, req, _payment);
}
// Callback function called by the Oracle when it has resolved the request
function fulfill(bytes32 _requestId, uint256 _price)
public
recordChainlinkFulfillment(_requestId)
{
currentPrice = _price;
}
// Allows the owner to cancel an unfulfilled request
function cancelRequest(
bytes32 _requestId,
uint256 _payment,
bytes4 _callbackFunctionId,
uint256 _expiration
)
public
{
cancelChainlinkRequest(_requestId, _payment, _callbackFunctionId, _expiration);
}
// Allows the owner to withdraw the LINK tokens in the contract to the address calling this function
function withdrawLink()
public
onlyOwner
{
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer");
}
// Decodes an input string in a bytes32 word
function stringToBytes32(string memory _source)
private
pure
returns (bytes32 result)
{
bytes memory emptyStringTest = bytes(_source);
if (emptyStringTest.length == 0) {
return 0x0;
}
assembly { // solhint-disable-line no-inline-assembly
result := mload(add(_source, 32))
}
return result;
}
// Reverts if the sender is not the owner of the contract
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
}
The core functions of the contract are as follows:
constructor
- runs when the contract is deployed. It sets the address of the LINK token and the owner of the contractrequestPrice
- needs the oracle contract address, the job ID, and the payment (in LINK) tokens to the fulfiller of the request. Builds the new request that is sent using thesendChainlinkRequestTo
function from theChainlinkClient.sol
importfulfill
- callback used by the oracle node to fulfill the request by storing the queried information in the contract
Note that the client contract must have a LINK tokens balance to be able to pay for this request. However, in this instance, the LINK value has been set to zero.
The client contract is deployed at 0x8ea35EdC1709ea0Ea2C86241C7D1C84Fd0dDeB11
. You can try interacting with the client contract by using following interface contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
/**
* @title Simple Interface to interact with Universal Client Contract
* @notice Client Address 0x8ea35EdC1709ea0Ea2C86241C7D1C84Fd0dDeB11
*/
interface ChainlinkInterface {
/**
* @notice Creates a Chainlink request with the job specification ID,
* @notice and sends it to the Oracle.
* @notice _oracle The address of the Oracle contract fixed top
* @notice _payment For this example the PAYMENT is set to zero
* @param _jobId The job spec ID that we want to call in string format
*/
function requestPrice(string calldata _jobId) external;
function currentPrice() external view returns (uint);
}
This provides two functions:
requestPrice()
- only needs the job ID of the data you want to query. This function starts the chain of events explained before.currentPrice()
is a view function that returns the latest price stored in the contract
Currently, the oracle node has a set of Job IDs for different price data for the following pairs:
Base/Quote | Job ID Reference |
---|---|
AAVE to USD | 7ddc5b9e9eef410184666620dbc740a4 |
ALGO to USD | 9ac119b772ed4d2580965fa84ef26f56 |
AVAX to USD | 007ba4a3bf424ea79d2fad13fd763808 |
BAND to USD | 6405edbca42e4b92bd4de2601310de79 |
BNB to USD | f4d1d07ce01d4039a410f0592ab9df4c |
BTC to USD | 02d8ae1716924ea7af12dba7d08aa9a2 |
COMP to USD | 35c893eca2eb4a35b6ee8b786920df2d |
CRV to USD | f8b5326162214b22874d3df2394f4da2 |
CVX to USD | a0f8e66321a64383b4ab92b98c44a500 |
DAI to USD | e191bd95b8134508bb258fc8c4375956 |
DOT to USD | 58a5aba286a143b68f9fcf9c1f022fd5 |
ETH to USD | e539daf468bb465c9c795deb5ff7fe22 |
FRAX to USD | 2a6d5b0b0e4b4d38b63dc4ea7a4aa2b9 |
FTM to USD | 6bf63cdfd9d54512b53671375d489079 |
KSM to USD | db34748e6cad4eab895eae8cbfacc80b |
LINK to USD | caea548c900d49959c91d595ef3854b6 |
MKR to USD | 2b32e18e39404182a8357c1caa9c6f74 |
OP to USD | 99c4ff9ec4194208a06c082353cef852 |
stETH to USD | 7d8b047a8e5e4a88adf4d24896a833e5 |
SUSHI to USD | 36e18c5b30b24b988c007dc9343cad5e |
UNI to USD | 49f014fd0475434cbe32e8f14d0d5d6d |
USDC to USD | 76dcaca128a24ec4aa46a3f17d084d02 |
USDT to USD | 7407e378b901429d884eebee1c1b7ee6 |
YFI to USD | f0f56b5c976649cb9d681f18efce34d1 |
For this example, you can go ahead and use the interface contract with the BTC to USD
job ID in Remix. After creating the file and compiling the contract, you can take the following steps:
- Head to the Deploy and Run Transactions tab
- Make sure you have set the ENVIRONMENT to Injected Web3, and you have your MetaMask connected to Moonbase Alpha
- Enter the client contract address,
0x8ea35EdC1709ea0Ea2C86241C7D1C84Fd0dDeB11
, and click on At Address. This will create an instance of the client contract that you can interact with - Under the Deployed Contracts section, use the
requestPrice()
function to query the data of the corresponding job ID - Confirm the transaction. You will have to wait until the whole request process that was previously explained occurs
- You can then check the price using the view function,
currentPrice()
If there is any specific pair you want to be included, feel free to reach out to the Moonbeam team through Discord.
Create a Custom Client Contract¶
If you want to run your own custom client contract but use the oracle node being run by Moonbeam, you can do so with the following information:
Contract Type | Address |
---|---|
Oracle Contract | 0x1d693d883BeAeE16edD0D7588D6a9f7E1967E798 |
LINK Token | 0xa36085F69e2889c224210F603D836748e7dC0088 |
If you decide to go this route, please keep in mind that the oracle node only supports the job IDs listed in the previous section. You'll only be able to access the pricing data for the supported pairs. If you need more functionality or want to use another API, please check out the Create Custom Contracts using your own Oracle Node section.
To build your own client contract using the ChainlinkClient
, you'll need to start by importing the contract:
import "https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.6/ChainlinkClient.sol";
You can checkout out the Chainlink documentation on ChainlinkClient API Reference for more information.
Keep in mind that the LINK token payment is set to zero.
Create Custom Contracts using your own Oracle Node¶
To get started with your own setup, including your own client contract, oracle contract, and oracle node, you'll need to start off running an oracle node. You can follow the Run a Chainlink Oracle Node on Moonbeam guide to spin up your own oracle node. You'll also learn how to setup your oracle contract and create jobs.
If you created a job to be used with any API, you can then create a client contract that sets the API endpoint URL to perform the GET request on.
Note that the client contract must have a LINK tokens balance to be able to pay for requests. Therefore, you will need to set the LINK value to zero in your ChainlinkClient.sol
contract. You'll also need to make sure that your oracle node has a MINIMUM_CONTRACT_PAYMENT
of 0
. You can verify that it has been set to zero by checking out the Configuration section of your node.
The following client contract is an example of how to use any API from within your client contract:
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
contract Client is ChainlinkClient {
using Chainlink for Chainlink.Request;
address private oracle;
bytes32 private jobId;
uint256 private fee;
uint256 public volume;
/**
This example uses the LINK token address on Moonbase Alpha.
Make sure to update the oracle and jobId.
*/
constructor() {
setChainlinkToken(address(0xa36085F69e2889c224210F603D836748e7dC0088));
oracle = INSERT_YOUR_ORACLE_NODE_ADDRESS;
jobId = "INSERT_YOUR_JOB_ID";
fee = 0;
}
/**
* 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.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;
}
}
Note
The above example uses the pre-deployed LINK token contract address. You also have the option of deploying your own LINK token contract and using that instead.
Once you've deployed the contract on Remix, you can begin to request the volume data. After you make a request, you can check the status of the job by going to the Jobs section of your node.
| Created: November 26, 2020