Skip to content

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.

A diagram detailing the Airnode flow.

The information presented herein is for informational purposes only and has been provided by third parties. Moonbeam does not endorse any project listed and described on the Moonbeam docs website (https://docs.moonbeam.network/).

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

A diagram detailing the process of requesting off-chain data from an Airnode.

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:

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 intended
  • data - 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 the decode() function from the abi 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.

The API3 Market dashboard.

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

The USDT/USD dAPI detail page.

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:

  1. Select your parameters
  2. Click on Connect Wallet

The activate data feed page.

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.

The integrate data feed page.

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 contract
  • readDataFeed() - a view function that returns the latest price of the set dAPI

Try deploying it on Remix!

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 data
    • endpointIdUint256 - the endpoint ID of the Airnode
    • sponsorWallet - the address of the sponsor wallet
  • makeRequestUint256 and makeRequestUint256Array - calls the airnodeRrp.makeFullRequest() function of the AirnodeRrpV0.sol protocol contract which adds the request to its storage and returns a requestId
  • fulfillUint256 and fulfillUint256Array - accepts and decodes the requested random number
  • getRandomNumber and getRandomNumberArray - returns the requested random number and array after the request is fulfilled
  • withdraw - 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.

Try deploying it on Remix!

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:

The information presented herein has been provided by third parties and is made available solely for general information purposes. Moonbeam does not endorse any project listed and described on the Moonbeam Doc Website (https://docs.moonbeam.network/). Moonbeam Foundation does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Moonbeam Foundation disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Moonbeam Foundation. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Moonbeam Foundation has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Moonbeam Foundation harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Last update: July 12, 2024
| Created: January 3, 2023