📂 Developers

# Calling an Airnode

Table of Contents

A requester is a contract that can trigger an Airnode request. To do so, the requester needs to be sponsored and make the request using a matching sponsor wallet. See Requesters and Sponsors on how to sponsor a requester and derive the sponsor wallet.

Airnode consists of two parts: the off-chain Airnode (a.k.a. "the node") deployed as self hosted or cloud provider functions, e.g., AWS) and the on-chain protocol contract AirnodeRrp.sol. A requester calls the protocol contract, which emits a blockchain event with the request parameters. Airnode listens to the events emitted by the AirnodeRrp contract. During the next run cycle, Airnode gets the request parameters from the emitted event. The diagram below and the diagram in the Overview doc for developers illustrate the mechanics of the entire process.

The AirnodeRrp protocol is designed to be flexible and is meant to serve a variety of use cases. See the Airnode requester examples (opens new window) for potential design patterns.

Ignoring the mechanics of the overall process, the requester calling an Airnode primarily focuses on two tasks, indicated by points A & B in the diagram below.

  • 1: Make the request
  • 2: Accept and decode the response
  1. A requester makes a request to the AirnodeRrp contract which adds the requestId to storage, emits the request to the event logs and returns the requestId to the requester. The request is retrieved by the Airnode during its next run cycle. It then verifies the requester is authorized by checking authorizer contracts assigned to the Airnode.

  2. If the request is authorized, Airnode proceeds to respond. It first gathers the requested data from the API and calls the fulfill() function in AirnodeRrp, which removes the pending requestId from storage and makes a callback to myFulfill(). The gas costs associated with the response are covered by the sponsor of the requester.

The following section of this document discusses the requester implementation, its deployment and sponsoring.

# Step #1: Inherit RrpRequester.sol

A requester inherits from the RrpRequester.sol (opens new window) contract. This will expose the AirnodeRrp.sol protocol contract to the requester allowing it to make Airnode requests.

import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequester.sol";
contract MyRequester is RrpRequester {
  ...
  constructor (address airnodeRrpAddress)
      public
      RrpRequester(airnodeRrpAddress)
  {}
  ...
}
1
2
3
4
5
6
7
8
9
10

Note the constructor parameter airnodeRrpAddress, which is the public address of the AirnodeRrp.sol protocol contract on the blockchain you wish to use. It is used by RrpRequester.sol to point itself to AirnodeRrp.sol.

See the list of all Airnode contract addresses in the reference section.

# Step #2: Implement the request logic

There are two types of requests provided by the AirnodeRrp.sol contract. See the Request page for information related to each request type.

This example uses a full request type (note the airnodeRrp.makeFullRequest function call in the code below) which is called from the requester's own function callTheAirnode. The function makeFullRequest requires that the requester pass all parameters needed by Airnode to call its underlying API.

Once the request has been made to airnodeRrp.makeFullRequest, the AirnodeRrp.sol contract returns a requestId confirming the request has been accepted and is in process of being executed. Your requester would most likely wish to track all requestIds. Note the line incomingFulfillments[requestId] = true; in the code below that stores the requestIds in a mapping. This is useful when the Airnode responds to the requester later at the function (airnodeCallback) with the requestId and the data requested.

import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequester.sol";
contract MyRequester is RrpRequester {
  mapping(bytes32 => bool) public incomingFulfillments;
  mapping(bytes32 => int256) public fulfilledData;
  constructor (address airnodeRrpAddress)
      public
      RrpRequester(airnodeRrpAddress)
  {}
  function callTheAirnode(
      address airnode,
      bytes32 endpointId,
      address sponsor,
      address sponsorWallet,
      bytes calldata parameters // Inbound API parameters which may already be ABI encoded
      )
      external
  {
      bytes32 requestId = airnodeRrp.makeFullRequest( // Make the Airnode request
          airnode,                        // airnode
          endpointId,                     // endpointId
          sponsor,                        // sponsor's address
          sponsorWallet,                  // sponsorWallet
          address(this),                  // fulfillAddress
          this.airnodeCallback.selector,  // fulfillFunctionId
          parameters                      // API parameters
          );
      incomingFulfillments[requestId] = true;
  }
  function airnodeCallback(   // The AirnodeRrp.sol protocol contract will callback here.
      bytes32 requestId,
      bytes calldata data
  {
      ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# Request Parameters

A full request using the AirnodeRrp.sol contract makeFullRequest function requires all parameters needed by the Airnode application to be passed at runtime. This is in contrast to a template request that would use a template for some or all of the required parameters. Learn more about using templates.

Since the callTheAirnode function makes a full request, it must gather the following parameters to pass on to airnodeRrp.makeFullRequest.

  • airnode and endpointId: As a pair, these uniquely identify the endpoint desired at a particular Airnode.

  • sponsor: The sponsor address.

  • sponsorWallet: The sponsor wallet address that the sponsor received when deriving the wallet for the Airnode being called.

  • fulfillAddress and fulfillFunctionId: The public address of your requester contract and its function that is called upon the return of the request.

  • parameters: Specify the API parameters and any reserved parameters, these must be encoded. See Airnode ABI specifications for how these are encoded.

    In most, cases the parameters are encoded off-chain and passed to the requester which only forwards them. You can use the @api3/airnode-abi package to perform the encoding and decoding. Take a look at the JavaScript snippet below.

    // JavaScript snippet
    import { encode } from '@api3/airnode-abi';
    const parameters = [
      { type: 'string32', name: 'coin', value: 'ETH' },
      { type: 'string32', name: 'apiKey', value: 'UHHS7SRGC975E' },
    ];
    const encodedData = encode(parameters);
    console.log(encodedData);
    // '0x...'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    However, this is not a hard requirement and you can encode the parameters on-chain as well. Take a look at the Solidity snippet below.

    // Solidity snippet
    abi.encode(
      string32("1SS"),
      string32("period"), "30d",
      string32("symbols"), "btc,eth,matic,link,uni,sushi,aave,chz,theta,rsr,grt,enj,ocean,kacy"
    )
    
    1
    2
    3
    4
    5
    6
    7

For additional information on request parameters when calling airnodeRrp.makeFullRequest(), see Request Parameters in the Reference section.

# Step #3: Capture the Response

As soon as the Airnode gets a request, it gathers the data, encodes it and starts an on-chain transaction responding to the request. The Airnode calls the AirnodeRrp.sol contract function fulfill(), which in turn calls the requester, in this case, at airnodeCallback. For the purposes of the callback, recall the request supplied the request contract address and the desired callback function which the AirnodeRrp.sol protocol contract stored with the requestId.

import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequester.sol";
contract MyRequester is RrpRequester {
    mapping(bytes32 => bool) public incomingFulfillments;
    mapping(bytes32 => int256) public fulfilledData;
    constructor (address airnodeRrpAddress)
        public
        RrpRequester(airnodeRrpAddress)
    {}
    function callTheAirnode(
        ...
    }
    function airnodeCallback(        // The AirnodeRrp.sol protocol contract will callback here.
        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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# Response Parameters

The callback to a requester contains two parameters, as shown in the airnodeCallback function in the code sample above.

  • 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 which has been encoded and contains a timestamp in addition to other response data. Decode it using the function decode() from the abi object.

# Step #4: Deploy and Sponsor the Requester

Deploy the requester to the desired blockchain and then sponsor the requester. See Requesters and Sponsors to learn more about sponsoring a requester.

Last Updated: 8/16/2022, 11:50:05 AM