Adapter
The airnode-adapter package has multiple responsibilities. It is used for building requests from an Oracle Integration Specification (OIS), executing them, parsing the responses, but also converting and encoding them for on chain use.
It is an internal dependency of Airnode, but can also be used standalone as an API.
Installation
You can install @api3/airnode-adapter
by adding it to the package.json
file in your project.
npm install --save @api3/airnode-adapter
You shouldn't need to use the adapter package directly. However, you might want to use its API to double check the conversion or encoding behavior for which you can install this package and verify your assumptions.
Conversion
While the adapter package has many responsibilities, many of those can be treated as implementation details. On the other hand, there are a few important behaviors to be noted when converting the response values based on the target type and making the response transaction on chain.
Altogether, the response cycle consists of multiple steps
- A successful API call is made and Airnode receives a response value.
- The value to be converted is extracted from the response using the _path from the OIS object.
- This extracted value is converted to the target type. Conversions are performed internally by the
castValue(value, type)
function. - The converted value is encoded to the native solidity type based on the _type from the OIS object. Encoding is performed internally by the
encodeValue(value, type)
function.
If any of the steps above fail, an error is thrown. This will fail the given API request and the error reason can be found in the logs.
The rest of this section covers the conversion logic for all of the supported types.
int256
or uint256
Converting any of the values in the following example will result in an error:
const ERROR_VALUES = [
null,
undefined,
Infinity,
NaN,
'', // empty string
'randomstring',
[], // arrays of any kind
{}, // objects of any kind
];
2
3
4
5
6
7
8
9
10
There are a few special strings and boolean values that are convertible to int256
or uint256
:
const SPECIAL_INT_VALUES = [false, 'false', true, 'true'];
const values = SPECIAL_INT_VALUES.map((v) => adapter.castValue(v, 'int256'));
console.log(values);
// [0, 0, 1, 1];
2
3
4
5
Number strings and numbers will attempt to be converted to BigNumbers. The value will also be multiplied by the value of the _times parameter if it is present.
const VALID_INT_VALUES = ['123.456', 7777];
const values = VALID_INT_VALUES.map((v) => adapter.castValue(v, 'uint256'));
console.log(values);
// [new BigNumber(123.456), new BigNumber(7777)];
2
3
4
5
Conversion for int256
and uint256
is the same - this means that -123
can be converted to uint256
. However, an error will be thrown while encoding.
Flooring
Beware that any floating point number will be floored. This is necessary, because floating point numbers are not valid in solidity. To mitigate precision loss, you can use the _times
parameter that is sufficiently large. For example, if the API response is a USD currency, you might want to use _times: "100"
to convert the value to cents.
bool
Converting values in the example are all considered false
.
const FALSE_BOOLEAN_VALUES = [0, '0', false, 'false', undefined, null];
const values = FALSE_BOOLEAN_VALUES.map((v) => {
return adapter.castValue(v, 'bool');
});
console.log(values);
// [false, false, false, false, false, false];
2
3
4
5
6
7
8
All other values are converted to true
.
bytes32
There is no conversion for bytes32
- the value is expected to be a valid hex string representing the encoded 32 bytes value. This means that the encoding must be implemented on the API side. If you want to delegate the encoding to Airnode, see the documentation for string32
.
For example, let's say the API wants to encode the following string simple string
with length 13. Its encoding is 0x73696d706c6520737472696e6700000000000000000000000000000000000000
. This is the value that should be sent as a response to Airnode request, together with the 0x
prefix.
You can use ethers to encode these on the API side
const value = 'simple string';
const encoded = ethers.utils.formatBytes32String(value);
console.log(encoded); // 0x73696d706c6520737472696e6700000000000000000000000000000000000000
2
3
address
There is no conversion for address
- the value is expected to be a string representing a valid address. Valid examples are:
0x0765baA22F6D4A53847D63B056DC79400b9A592a
- EIP-55 mixed case checksum of an address.0x0765baa22f6d4a53847d63b056dc79400b9a592a
- all lowercase address.
bytes
There is no conversion for bytes
- the value is expected to be a valid hex string representing the encoded value. This means that the encoding to bytes must be implemented on the API side. If you want to send a string, see the documentation for string
.
For example, let's say the API wants to encode the following string this is an example string that is a bit longer
. Its encoding is 0x7468697320697320616e206578616d706c6520737472696e672074686174206973206120626974206c6f6e676572
. This is the value that should be sent as a response to Airnode request, together with the 0x
prefix. Use ethers to encode these on the API side.
const value = 'this is an example string that is a bit longer';
const encodedValue = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(value));
console.log(encodedValue); // 0x7468697320697320616e206578616d706c6520737472696e672074686174206973206120626974206c6f6e676572
2
3
string
You can pass any value to convert it to string - with the exception of arrays and objects, which will throw an error. All other values will be converted to string
and before encoded on chain using the string
type.
const values = [
-1,
0,
777.89,
null,
'simple string',
'this is an example string that is a bit longer',
];
// ["-1", "0", "777.89", "null", "simple string", "this is an example string that is a bit longer"]
const mappedValues = values.map((v) => {
return adapter.castValue(v, 'string');
});
2
3
4
5
6
7
8
9
10
11
12
13
string32
You can pass any value to convert it to string - with the exception of arrays and objects, which will throw an error.
However, there is one exception, if the stringified value contains more than 31 characters it will be trimmed down to only the first 31 characters during conversion.
For example, if the API response is the following string this is an example string that is a bit longer
with length 46. It will be first trimmed to 31 characters, string this is an example string that
and afterwards converted to 0x7468697320697320616e206578616d706c6520737472696e6720746861742000
. Use ethers to decode the values off chain using the following snippet
const encoded =
'0x7468697320697320616e206578616d706c6520737472696e6720746861742000';
const decoded = ethers.utils.parseBytes32String(encoded);
console.log(decoded); // "this is an example string that "
2
3
4
Arrays
Conversion of arrays depends on the primitive type. All values of the array (or nested array) will be converted according to the rules of the primitive type.
For example:
int256[]
- has primitive typeint256
. All elements of this array follow theint256
rules.string32[7][][5]
- is a multidimensional array, where some dimensions are fixed and some not. This is irrelevant though, and all the elements are converted based onstring32
rules.