Use API3 To Request Off-Chain Data on Moonbeam¶
Introduction¶
API3 is a decentralized solution for delivering traditional API services to smart contract platforms in an easily accessible and scalable way. It is governed by a Decentralized Autonomous Organization (DAO), the API3 DAO. API3 enables developers to access off-chain resources from within their smart contracts without worrying about security implications. API3 makes this possible through Airnodes, which are first-party oracles, and on-chain data feeds sourced from these oracles.
Developers can use Airnode to request off-chain data inside their smart contracts on Moonbeam networks. An Airnode is a first-party oracle that pushes off-chain API data to your on-chain contract. Airnode lets API providers easily run their own first-party oracle nodes. That way, they can provide data to any on-chain dApp interested in their services, all without an intermediary.
An on-chain smart contract requests the RRP (Request Response Protocol) contract (AirnodeRrpV0.sol
) that adds the request to the event logs. The Airnode then accesses the event logs, fetches the API data, and performs a callback to the requester with the requested data.
Request Off-Chain Data From an Airnode¶
Requesting off-chain data essentially involves triggering an Airnode and getting its response through your smart contract. The smart contract in this case would be the requester contract, which will make a request to the desired off-chain Airnode and then capture its response.
The requester calling an Airnode primarily focuses on two tasks:
- Making the request
- Accepting and decoding the response
Here is an example of a basic requester contract to request data from an Airnode:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";
import "@openzeppelin/contracts@4.9.5/access/Ownable.sol";
// A Requester that will return the requested data by calling the specified Airnode.
contract Requester is RrpRequesterV0, Ownable {
mapping(bytes32 => bool) public incomingFulfillments;
mapping(bytes32 => int256) public fulfilledData;
// Make sure you specify the right _rrpAddress for your chain while deploying the contract.
constructor(address _rrpAddress) RrpRequesterV0(_rrpAddress) {}
// To receive funds from the sponsor wallet and send them to the owner.
receive() external payable {
payable(owner()).transfer(address(this).balance);
}
// The main makeRequest function that will trigger the Airnode request.
function makeRequest(
address airnode,
bytes32 endpointId,
address sponsor,
address sponsorWallet,
bytes calldata parameters
) external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode, // airnode address
endpointId, // endpointId
sponsor, // sponsor's address
sponsorWallet, // sponsorWallet
address(this), // fulfillAddress
this.fulfill.selector, // fulfillFunctionId
parameters // encoded API parameters
);
incomingFulfillments[requestId] = true;
}
function fulfill(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(incomingFulfillments[requestId], "No such request made");
delete incomingFulfillments[requestId];
int256 decodedData = abi.decode(data, (int256));
fulfilledData[requestId] = decodedData;
}
// To withdraw funds from the sponsor wallet to the contract.
function withdraw(address airnode, address sponsorWallet) external onlyOwner {
airnodeRrp.requestWithdrawal(
airnode,
sponsorWallet
);
}
}
You can also try deploying the example contract on Remix.
Contract Addresses¶
The _rrpAddress
is the main airnodeRrpAddress
. The RRP contracts have already been deployed on-chain. The addresses for the _rrpAddress
on Moonbeam networks are as follows:
Contract | Addresses |
---|---|
AirnodeRrpV0 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd |
Contract | Addresses |
---|---|
AirnodeRrpV0 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd |
Contract | Addresses |
---|---|
AirnodeRrpV0 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd |
Request Parameters¶
The makeRequest()
function expects the following parameters to make a valid request:
airnode
- specifies the Airnode addressendpointId
- specifies which endpoint to be usedsponsor
andsponsorWallet
- specifies which wallet will be used to fulfill the requestparameters
- specifies the API and Reserved Parameters (see Airnode ABI specifications for how these are encoded). Parameters can be encoded off-chain using the@airnode-abi
library
Response Parameters¶
The callback to the requester contract contains two parameters:
requestId
- first acquired when making the request and passed here as a reference to identify the request for which the response is intendeddata
- in case of a successful response, this is the requested data encoded and contains a timestamp in addition to other response data. Decode it using thedecode()
function from theabi
object
Note
Sponsors should not fund a sponsorWallet
with more than they can trust the Airnode with, as the Airnode controls the private key to the sponsorWallet
. The deployer of such Airnode undertakes no custody obligations, and the risk of loss or misuse of any excess funds sent to the sponsorWallet
remains with the sponsor.
dAPIs: API3 Data Feeds¶
dAPIs are continuously updated streams of off-chain data, such as the latest cryptocurrency, stock, and commodity prices. They can power decentralized applications such as DeFi lending, synthetic assets, stablecoins, derivatives, NFTs, and more.
The data feeds are continuously updated by first-party oracles using signed data. DApp owners can read the on-chain value of any dAPI in real-time.
Because they are composed of first-party data feeds, dAPIs offer security, transparency, cost-efficiency, and scalability in a turnkey package.
To learn more about how dAPIs work, please refer to API3's documentation.
Subscribe to dAPIs¶
The API3 Market lets users access dAPIs on Moonbeam, Moonriver, and the Moonbase Alpha TestNet (currently labeled as the Moonbeam TestNet).
From the API3 Market home page, you can search for a given chain. After selecting the chain, you can view the list of available dAPIs and click on one for more information. For example, you can click on the USDT/USD
pair available for Moonbeam to view the parameters of the dAPI, including the deviation and the heartbeat.
The supported parameters for dAPIs are:
Deviation | Heartbeat |
---|---|
0.25% | 24 hours |
0.5% | 24 hours |
1% | 24 hours |
5% | 24 hours |
Configure and Activate a dAPI¶
Once you've selected a dAPI to interact with, check the expiration date and update the parameters as needed. You can update the parameters and extend the subscription by purchasing a new configuration. If the dAPI has been activated and the configurations listed will work for you, you can skip ahead to the next section to learn how to interact with the dAPI.
To purchase a plan with new configurations, click on Purchase new plan and take the following steps:
- Select your parameters
- Click on Connect Wallet
Once connected, you'll be able to purchase your new plan. Click on Purchase and sign the transaction. After the transaction has been confirmed, you will be able to see the updated configuration for the dAPI.
Get Data from a dAPI¶
To interact with a dAPI, you'll need to get the proxy address for it. Click on the Integrate button from the dAPI details page. Then, on the integration page, copy the proxy address.
With the proxy address in hand, you'll be able to integrate the dAPI into a smart contract. Here's an example of a basic contract that reads from a dAPI:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "@openzeppelin/contracts@4.9.5/access/Ownable.sol";
import "@api3/contracts/api3-server-v1/proxies/interfaces/IProxy.sol";
contract DataFeedReaderExample is Ownable {
// The proxy contract address obtained from the API3 Market UI
address public proxyAddress;
// Updating the proxy contract address is a security-critical
// action. In this example, only the owner is allowed to do so
function setProxyAddress(address _proxyAddress) public onlyOwner {
proxyAddress = _proxyAddress;
}
function readDataFeed()
external
view
returns (int224 value, uint256 timestamp)
{
// Use the IProxy interface to read a dAPI via its
// proxy contract
(value, timestamp) = IProxy(proxyAddress).read();
// If you have any assumptions about `value` and `timestamp`,
// make sure to validate them after reading from the proxy
}
}
The example contract contains two functions:
setProxyAddress()
- used to set the address of the dAPI proxy contractreadDataFeed()
- aview
function that returns the latest price of the set dAPI
API3 QRNG¶
API3 QRNG is a public utility provided with the courtesy of Australian National University (ANU), Quintessence Labs and Quantum Blockchains. It is powered by an Airnode deployed by the QRNG Providers, meaning that it is a first-party service. It is served as a public good and is free of charge (apart from the gas costs), and it provides ‘true’ quantum randomness via an easy-to-use solution when requiring RNG on-chain.
To request randomness on-chain, the requester submits a request for a random number to AirnodeRrpV0
. The QRNG Airnode gathers the request from the AirnodeRrpV0
protocol contract, retrieves the random number off-chain, and sends it back to AirnodeRrpV0
. Once received, it performs a callback to the requester with the random number.
Click here to check out the AirnodeRrpV0
and available QRNG Providers on Moonbeam.
Here is an example of a basic QrngRequester
that requests a random number:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";
import "@openzeppelin/contracts@4.9.5/access/Ownable.sol";
/// @title Example contract that uses Airnode RRP to access QRNG services
contract QrngExample is RrpRequesterV0, Ownable {
event RequestedUint256(bytes32 indexed requestId);
event ReceivedUint256(bytes32 indexed requestId, uint256 response);
event RequestedUint256Array(bytes32 indexed requestId, uint256 size);
event ReceivedUint256Array(bytes32 indexed requestId, uint256[] response);
event WithdrawalRequested(address indexed airnode, address indexed sponsorWallet);
address public airnode; /// The address of the QRNG Airnode
bytes32 public endpointIdUint256; /// The endpoint ID for requesting a single random number
bytes32 public endpointIdUint256Array; /// The endpoint ID for requesting an array of random numbers
address public sponsorWallet; /// The wallet that will cover the gas costs of the request
uint256 public _qrngUint256; /// The random number returned by the QRNG Airnode
uint256[] public _qrngUint256Array; /// The array of random numbers returned by the QRNG Airnode
mapping(bytes32 => bool) public expectingRequestWithIdToBeFulfilled;
constructor(address _airnodeRrp) RrpRequesterV0(_airnodeRrp) {}
/// @notice Sets the parameters for making requests
function setRequestParameters(
address _airnode,
bytes32 _endpointIdUint256,
bytes32 _endpointIdUint256Array,
address _sponsorWallet
) external {
airnode = _airnode;
endpointIdUint256 = _endpointIdUint256;
endpointIdUint256Array = _endpointIdUint256Array;
sponsorWallet = _sponsorWallet;
}
/// @notice To receive funds from the sponsor wallet and send them to the owner.
receive() external payable {
payable(owner()).transfer(msg.value);
emit WithdrawalRequested(airnode, sponsorWallet);
}
/// @notice Requests a `uint256`
/// @dev This request will be fulfilled by the contract's sponsor wallet,
/// which means spamming it may drain the sponsor wallet.
function makeRequestUint256() external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256.selector,
""
);
expectingRequestWithIdToBeFulfilled[requestId] = true;
emit RequestedUint256(requestId);
}
/// @notice Called by the Airnode through the AirnodeRrp contract to
/// fulfill the request
function fulfillUint256(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256 qrngUint256 = abi.decode(data, (uint256));
_qrngUint256 = qrngUint256;
// Do what you want with `qrngUint256` here...
emit ReceivedUint256(requestId, qrngUint256);
}
/// @notice Requests a `uint256[]`
/// @param size Size of the requested array
function makeRequestUint256Array(uint256 size) external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256Array,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256Array.selector,
// Using Airnode ABI to encode the parameters
abi.encode(bytes32("1u"), bytes32("size"), size)
);
expectingRequestWithIdToBeFulfilled[requestId] = true;
emit RequestedUint256Array(requestId, size);
}
/// @notice Called by the Airnode through the AirnodeRrp contract to
/// fulfill the request
function fulfillUint256Array(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256[] memory qrngUint256Array = abi.decode(data, (uint256[]));
// Do what you want with `qrngUint256Array` here...
_qrngUint256Array = qrngUint256Array;
emit ReceivedUint256Array(requestId, qrngUint256Array);
}
/// @notice Getter functions to check the returned value.
function getRandomNumber() public view returns (uint256) {
return _qrngUint256;
}
function getRandomNumberArray() public view returns(uint256[] memory) {
return _qrngUint256Array;
}
/// @notice To withdraw funds from the sponsor wallet to the contract.
function withdraw() external onlyOwner {
airnodeRrp.requestWithdrawal(
airnode,
sponsorWallet
);
}
}
The example contract contains these functions:
setRequestParameters
- accepts and sets the following three request parameters:airnode
- address of an Airnode that will be called to retrieve QRNG dataendpointIdUint256
- the endpoint ID of the AirnodesponsorWallet
- the address of the sponsor wallet
makeRequestUint256
andmakeRequestUint256Array
- calls theairnodeRrp.makeFullRequest()
function of theAirnodeRrpV0.sol
protocol contract which adds the request to its storage and returns arequestId
fulfillUint256
andfulfillUint256Array
- accepts and decodes the requested random numbergetRandomNumber
andgetRandomNumberArray
- returns the requested random number and array after the request is fulfilledwithdraw
- allows the owner to withdraw funds from the sponsor wallet
Note
You can get the airnode
address and endpointIdUint256
from the QRNG Providers section below.
QRNG Airnode and Endpoint Providers¶
You can try QRNG using the following Airnodes and endpoints:
Variable | Value |
---|---|
ANU QRNG Airnode Address | 0x9d3C147cA16DB954873A498e0af5852AB39139f2 |
ANU QRNG Airnode xpub | xpub6DXSDTZBd4aPVXnv6Q3SmnGUweFv6j24SK77W4qrSFuhGgi666awUiXakjXruUSCDQhhctVG7AQt67gMdaRAsDnDXv23bBRKsMWvRzo6kbf |
ANU Endpoint ID (uint256) | 0xfb6d017bb87991b7495f563db3c8cf59ff87b09781947bb1e417006ad7f55a78 |
ANU Endpoint ID (uint256[]) | 0x27cc2713e7f968e4e86ed274a051a5c8aaee9cca66946f23af6f29ecea9704c3 |
Quintessence QRNG Airnode Address | 0x224e030f03Cd3440D88BD78C9BF5Ed36458A1A25 |
Quintessence QRNG Airnode xpub | xpub6CyZcaXvbnbqGfqqZWvWNUbGvdd5PAJRrBeAhy9rz1bbnFmpVLg2wPj1h6TyndFrWLUG3kHWBYpwacgCTGWAHFTbUrXEg6LdLxoEBny2YDz |
Quintessence Endpoint ID (uint256) | 0xffd1bbe880e7b2c662f6c8511b15ff22d12a4a35d5c8c17202893a5f10e25284 |
Quintessence Endpoint ID (uint256[]) | 0x4554e958a68d68de6a4f6365ff868836780e84ac3cba75ce3f4c78a85faa8047 |
Quantum Blockchains QRNG Airnode Address | 0x07D7F4EcaC8aDf3f0412ab058a051fD8b8507348 |
Quantum Blockchains QRNG Airnode xpub | xpub6C7WhXL7KNoa9GnCwEfLCWzf326hXhqD3nUyKg4ZxzXvjcmFeVQ9GZENgKtHxLfo3uPa13GBHCAb1nRgdUnP5YGJFX97KqpeFvHQzpjhmoi |
Quantum Blockchains Endpoint ID (uint256) | 0x8cf0e7205eff59b8695db96b34ffe7a5a125a3ed263afa3353aef6c3d82debf9 |
Quantum Blockchains Endpoint ID (uint256[]) | 0x09fd3663fdc0ab359a5ebcba098571122ddb2a80b00a503960b32e67491f52d7 |
Variable | Value |
---|---|
ANU QRNG Airnode Address | 0x9d3C147cA16DB954873A498e0af5852AB39139f2 |
ANU QRNG Airnode xpub | xpub6DXSDTZBd4aPVXnv6Q3SmnGUweFv6j24SK77W4qrSFuhGgi666awUiXakjXruUSCDQhhctVG7AQt67gMdaRAsDnDXv23bBRKsMWvRzo6kbf |
ANU Endpoint ID (uint256) | 0xfb6d017bb87991b7495f563db3c8cf59ff87b09781947bb1e417006ad7f55a78 |
ANU Endpoint ID (uint256[]) | 0x27cc2713e7f968e4e86ed274a051a5c8aaee9cca66946f23af6f29ecea9704c3 |
Quintessence QRNG Airnode Address | 0x224e030f03Cd3440D88BD78C9BF5Ed36458A1A25 |
Quintessence QRNG Airnode xpub | xpub6CyZcaXvbnbqGfqqZWvWNUbGvdd5PAJRrBeAhy9rz1bbnFmpVLg2wPj1h6TyndFrWLUG3kHWBYpwacgCTGWAHFTbUrXEg6LdLxoEBny2YDz |
Quintessence Endpoint ID (uint256) | 0xffd1bbe880e7b2c662f6c8511b15ff22d12a4a35d5c8c17202893a5f10e25284 |
Quintessence Endpoint ID (uint256[]) | 0x4554e958a68d68de6a4f6365ff868836780e84ac3cba75ce3f4c78a85faa8047 |
Quantum Blockchains QRNG Airnode Address | 0x07D7F4EcaC8aDf3f0412ab058a051fD8b8507348 |
Quantum Blockchains QRNG Airnode xpub | xpub6C7WhXL7KNoa9GnCwEfLCWzf326hXhqD3nUyKg4ZxzXvjcmFeVQ9GZENgKtHxLfo3uPa13GBHCAb1nRgdUnP5YGJFX97KqpeFvHQzpjhmoi |
Quantum Blockchains Endpoint ID (uint256) | 0x8cf0e7205eff59b8695db96b34ffe7a5a125a3ed263afa3353aef6c3d82debf9 |
Quantum Blockchains Endpoint ID (uint256[]) | 0x09fd3663fdc0ab359a5ebcba098571122ddb2a80b00a503960b32e67491f52d7 |
Variable | Value |
---|---|
TestNet QRNG Airnode Address | 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D |
TestNet QRNG Airnode xpub | xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo |
TestNet QRNG Endpoint ID (uint256) | 0x94555f83f1addda23fdaa7c74f27ce2b764ed5cc430c66f5ff1bcf39d583da36 |
TestNet QRNG Endpoint ID (uint256[]) | 0x9877ec98695c139310480b4323b9d474d48ec4595560348a2341218670f7fbc2 |
For a complete list of all the QRNG Providers, please refer to API3's documentation.
Additional Resources¶
Here are some additional developer resources:
| Created: January 3, 2023