Post processing
This guide demonstrates Airnode's ability to execute its postProcessingSpecifications
capability which runs user defined code during its run cycle. The following describes the workflow Airnode uses:
- Run
preProcessingSpecifications
- Airnode calls requested OIS endpoint
- Run
postProcessingSpecifications
- Airnode encodes the response values defined by reservedParameters
In this guide you will use postProcessingSpecifications
to get the average price of the API3 token and its average market cap for the last seven days. This will be calculated after calling the CoinGecko operation /coins/{coin}/market_chart
.
1. Setup a Docker container to run Airnode
A local Docker container running Airnode is needed. Go to the guide Deploying Airnode locally using Docker and run steps #1-3. Make the following adjustment when doing so:
- After downloading the Project folder (step #2) replace the
config.json
file's content with the following content.
Copy/paste config.json
{
"chains": [
{
"authorizers": {
"requesterEndpointAuthorizers": [],
"crossChainRequesterAuthorizers": [],
"requesterAuthorizersWithErc721": [],
"crossChainRequesterAuthorizersWithErc721": []
},
"authorizations": {
"requesterEndpointAuthorizations": {}
},
"id": "11155111",
"providers": {
"myChainProvider": {
"url": "${CHAIN_PROVIDER_URL}"
}
},
"type": "evm",
"options": {
"gasPriceOracle": [
{
"gasPriceStrategy": "latestBlockPercentileGasPrice",
"percentile": 60,
"minTransactionCount": 20,
"pastToCompareInBlocks": 20,
"maxDeviationMultiplier": 2
},
{
"gasPriceStrategy": "providerRecommendedGasPrice",
"recommendedGasPriceMultiplier": 1.2
},
{
"gasPriceStrategy": "providerRecommendedEip1559GasPrice",
"baseFeeMultiplier": 2,
"priorityFee": {
"value": 3.12,
"unit": "gwei"
}
},
{
"gasPriceStrategy": "constantGasPrice",
"gasPrice": {
"value": 10,
"unit": "gwei"
}
}
]
},
"maxConcurrency": 100
}
],
"nodeSettings": {
"cloudProvider": {
"type": "local"
},
"airnodeWalletMnemonic": "${AIRNODE_WALLET_MNEMONIC}",
"heartbeat": {
"enabled": false
},
"httpGateway": {
"enabled": true,
"maxConcurrency": 20,
"corsOrigins": []
},
"httpSignedDataGateway": {
"enabled": false
},
"oevGateway": {
"enabled": false
},
"logFormat": "plain",
"logLevel": "DEBUG",
"nodeVersion": "0.14.1",
"stage": "quick-container"
},
"triggers": {
"rrp": [
{
"endpointId": "0x2de6e288ed16965b68a62d4b2a747b094b3c857941285e625bed3a7be31445e4",
"oisTitle": "API3 coin data",
"endpointName": "api3CoinMarketData",
"cacheResponses": false
}
],
"http": [
{
"endpointId": "0x2de6e288ed16965b68a62d4b2a747b094b3c857941285e625bed3a7be31445e4",
"oisTitle": "API3 coin data",
"endpointName": "api3CoinMarketData"
}
],
"httpSignedData": []
},
"templates": [],
"ois": [
{
"oisFormat": "2.3.2",
"title": "API3 coin data",
"version": "1.0.0",
"apiSpecifications": {
"servers": [
{
"url": "https://api.coingecko.com/api/v3"
}
],
"paths": {
"/coins/{coin}/market_chart": {
"get": {
"parameters": [
{
"in": "path",
"name": "coin"
},
{
"in": "query",
"name": "vs_currency"
},
{
"in": "query",
"name": "days"
},
{
"in": "query",
"name": "interval"
}
]
}
}
},
"components": {
"securitySchemes": {}
},
"security": {}
},
"endpoints": [
{
"name": "api3CoinMarketData",
"operation": {
"method": "get",
"path": "/coins/{coin}/market_chart"
},
"fixedOperationParameters": [
{
"operationParameter": {
"in": "query",
"name": "days"
},
"value": "6"
},
{
"operationParameter": {
"in": "query",
"name": "interval"
},
"value": "daily"
}
],
"reservedParameters": [
{
"name": "_type",
"fixed": "int256,int256"
},
{
"name": "_path",
"fixed": "avg_price,avg_market_cap"
},
{
"name": "_times",
"fixed": "1000000,"
}
],
"parameters": [
{
"name": "air_coin",
"operationParameter": {
"in": "path",
"name": "coin"
}
},
{
"name": "air_vs_currency",
"operationParameter": {
"in": "query",
"name": "vs_currency"
}
}
],
"preProcessingSpecifications": [],
"postProcessingSpecifications": [
{
"environment": "Node",
"timeoutMs": 5000,
"value": " let avg_price = 0; let avg_market_cap = 0; input.prices.forEach(el => { avg_price = avg_price + el[1]; }); input.avg_price = (avg_price / 7).toString(); input.market_caps.forEach(el => { avg_market_cap = avg_market_cap + el[1]; }); input.avg_market_cap = (avg_market_cap / 7).toString(); output = input; "
}
]
}
]
}
],
"apiCredentials": []
}
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
Once the project folder from the Deploying Airnode locally using Docker guide has been downloaded and the config.json
file updated, return to step #2 below. But first read about the changes made to the newer config.json
file shown below.
paths
The newer config.json
file points to a different API provider operation (/coins/{coin}/market_chart
).
"paths": {
"/coins/{coin}/market_chart": {
"get": {
"parameters": [
...
]
}
}
},
2
3
4
5
6
7
8
9
postProcessingSpecifications
After calling the API provider's operation but before encoding the resulting values for on-chain use, Airnode will run the post processing code defined in postProcessingSpecifications
. During execution of this code, the data from the API provider operation can be summarized into the values needed.
"preProcessingSpecifications": [],
"postProcessingSpecifications": [
{
"environment": "Node",
"timeoutMs": 5000,
"value": " let avg_price = 0; let avg_market_cap = 0; input.prices.forEach(el => { avg_price = avg_price + el[1]; }); input.avg_price = (avg_price / 7).toString(); input.market_caps.forEach(el => { avg_market_cap = avg_market_cap + el[1]; }); input.avg_market_cap = (avg_market_cap / 7).toString(); output = input; "
}
]
2
3
4
5
6
7
8
Below is a more human readable rendering of the post processing code. The input
variable contains the response from the API provider's operation. The output
variable must be set before the code exists as Airnode uses it to encode any values for use on-chain. See more about Input and Output in the OIS reference.
let avg_price = 0;
let avg_market_cap = 0;
input.prices.forEach((el) => {
avg_price = avg_price + el[1];
});
input.avg_price = (avg_price / 7).toString();
input.market_caps.forEach((el) => {
avg_market_cap = avg_market_cap + el[1];
});
input.avg_market_cap = (avg_market_cap / 7).toString();
output = input;
2
3
4
5
6
7
8
9
10
11
2. Deploy the Docker container
From your project root run the Docker container deploy command. It will use the updated config.json
file.
docker run \
--volume "$(pwd):/app/config" \
--name quick-start-container-airnode \
--publish 3000:3000 \
api3/airnode-client:latest
2
3
4
5
docker run ^
--volume "%cd%:/app/config" ^
--name quick-start-container-airnode ^
--publish 3000:3000 ^
api3/airnode-client:latest
2
3
4
5
docker run \
--volume "$(pwd):/app/config" \
--name quick-start-container-airnode \
--network host \
api3/airnode-client:latest
2
3
4
5
Leave the command active in the terminal window. It will display the log items resulting from the container's deployment. Note the line in the output contains the httpGateway
URL which you will use in step #3.
...
[2023-06-06 21:49:44.596] INFO HTTP (testing) gateway listening for request on "http://localhost:3000/http-data/01234567-abcd-abcd-abcd-012345678abc/:endpointId"
...
2
3
3. Call the Airnode endpoint
Use CURL and call the Airnode endpoint that gets the market data from the CoinGecko operation /coins/{coin}/market_chart
. The CURL command will call the httpGateway exposed by the Airnode. This avoids the need to use a smart contract to get the response. The httpGateway is a testing tool not normally enabled on production systems.
Request
# For Windows CMD replace line termination marker \ with ^
curl -X POST \
-d '{"parameters":{"air_coin":"api3","air_vs_currency":"usd"}}' \
-H 'Content-Type: application/json' \
'http://localhost:3000/http-data/01234567-abcd-abcd-abcd-012345678abc/0x2de6e288ed16965b68a62d4b2a747b094b3c857941285e625bed3a7be31445e4' | json_pp
2
3
4
5
Response
# response
{
"encodedValue" : "0x0...58a",
"rawValue" : {
"avg_market_cap" : "106632586.97711395",
"avg_price" : "1.2477250309068642",
"market_caps" : [
[
1685577600000,
105212548.064737
],
...
],
"prices" : [
[
1685577600000,
1.23324995186942
],
...
],
"total_volumes" : [
[
1685577600000,
3074677.52645394
],
...
]
]
},
"values" : [
"1247725",
"106632586"
]
}
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
The data retrieved from the API provider CoinGecko (market_caps
, prices
, and total_volumes
) was added to the rawValue
field. Then the rawValue
(as the input
variable) was "piped" through the postProcessingSpecifications
user defined code which in turn calculated the price and market cap averages which were added back into rawValue
via the output
variable.
"avg_price" : "1.2477250309068642"
"avg_market_cap" : "106632586.97711395"
After the postProcessingSpecifications
user defined code exited Airnode used the mappings of the reservedParameters
field in the config.json
file to determine what data should go into the values
field. Airnode then encoded the values into the encodedValue
field suitable for use on-chain.
4. Verify encodedValue
Verify that encodedValue
actually contains the two numbers in the values
field. Copy and paste the encodedValue
string you received after executing the Airnode endpoint into the form below and select the Decode button.
Summary
In this guide you extended the data from the API operation with additional summary values using post processing. For more examples on how to use preProcessingSpecifications
and postProcessingSpecifications
see the following monorepo examples.