Return Custom Data Types
In this tutorial, you send a request to a Decentralized Oracle Network to call the Cryptocompare GET /data/pricemultifull API. After OCR completes off-chain computation and aggregation, it returns several responses to your smart contract. The response includes an asset price, daily volume, and market name to your smart contract. The example uses query parameters to specify the ETH/USD asset pair, but you can configure HTTP query parameters to make requests for different assets.
Before you begin
-
Complete the setup steps in the Getting Started guide: The Getting Started Guide shows you how to set up your environment with the necessary tools for these tutorials. You can re-use the same consumer contract for each of these tutorials.
-
Make sure your subscription has enough LINK to pay for your requests. Read Get Subscription details to learn how to check your subscription balance. If your subscription runs out of LINK, follow the Fund a Subscription guide.
-
Check out the tutorials branch of the Chainlink Functions Starter Kit. You can locate this tutorial in the /tutorials/3-custom-response directory.
git checkout tutorials
Tutorial
This tutorial is configured to get the ETH/USD
, daily volume, and market in a single request. For a detailed explanation of the code example, read the Explanation section.
- Open
config.js
. Note theargs
value is["ETH", "USD"]
: We want to fetch the currentETH/USD
price and daily volume. You can adaptargs
to get the list of supported symbols. Read the API docs to learn more about these configuration variables. For a more detailed explanation about the configuration in this example, read the request config explanation section. - Open
source.js
to analyze the JavaScript source code. Read the source code explanation section for a more detailed explanation of how the source file is written.
Simulation
The Chainlink Functions hardhat starter kit includes a simulator to test your Functions code on your local machine. The functions-simulate
command will execute your code in a local runtime environment and simulate an end-to-end fulfillment. Simulation can help you to fix any issues before you submit your requests to the Decentralized Oracle Network.
Run the functions-simulate
task to run the source code locally and make sure config.js
and Functions-request-source.js
are correctly written:
npx hardhat functions-simulate --configpath REPLACE_CONFIG_PATH
Example:
$ npx hardhat functions-simulate --configpath tutorials/3-custom-response/config.js
secp256k1 unavailable, reverting to browser version
__Compiling Contracts__
Nothing to compile
Duplicate definition of Transfer (Transfer(address,address,uint256,bytes), Transfer(address,address,uint256))
Executing JavaScript request source code locally...
__Console log messages from sandboxed code__
HTTP GET Request to https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD
ETH price is: 1810.29 USD. 24h Volume is 113255.87 USD. Market: Coinbase
__Output from sandboxed source code__
Output represented as a hex string: 0x7b227072696365223a22313831302e3239222c22766f6c756d65223a223131333235352e3837222c226c6173744d61726b6574223a22436f696e62617365227d
Decoded as a string: {"price":"1810.29","volume":"113255.87","lastMarket":"Coinbase"}
__Simulated On-Chain Response__
Response returned to client contract represented as a hex string: 0x7b227072696365223a22313831302e3239222c22766f6c756d65223a223131333235352e3837222c226c6173744d61726b6574223a22436f696e62617365227d
Decoded as a string: {"price":"1810.29","volume":"113255.87","lastMarket":"Coinbase"}
Gas used by sendRequest: 376604
Gas used by client callback function: 98391
Reading the output of the example above, you can see that the ETH/USD
price is 1810.29 USD, volume is 113255.87, and the market is Coinbase. Because the final result is a JSON object, the example converts it to a string and returns the bytes
encoded value 0x7b227072696365223a22313831302e3239222c22766f6c756d65223a223131333235352e3837222c226c6173744d61726b6574223a22436f696e62617365227d
in the callback. Read the source code explanation for a more detailed explanation.
Request
Send a request to the Decentralized Oracle Network to fetch the asset price. Run the functions-request
task with the subid
(subscription ID) and contract
parameters. This task passes the JavaScript source code, arguments, and secrets when you call the executeRequest
function in your deployed FunctionsConsumer
contract. Read the functionsConsumer section for a more detailed explanation.
npx hardhat functions-request --subid REPLACE_SUBSCRIPTION_ID --contract REPLACE_CONSUMER_CONTRACT_ADDRESS --network REPLACE_NETWORK --configpath REPLACE_CONFIG_PATH
Example:
$ npx hardhat functions-request --subid 443 --contract 0x4B4BA2Fd6b93aDF8d6b6002E10540E58394388Ea --network polygonMumbai --configpath tutorials/3-custom-response/config.js
secp256k1 unavailable, reverting to browser version
Estimating cost if the current gas price remains the same...
The transaction to initiate this request will charge the wallet (0x9d087fC03ae39b088326b67fA3C788236645b717):
0.00050942400509424 MATIC, which (using mainnet value) is $0.0005628062918760629
If the request's callback uses all 100,000 gas, this request will charge the subscription:
0.200148583233223418 LINK
Continue? Enter (y) Yes / (n) No
y
Simulating Functions request locally...
__Console log messages from sandboxed code__
HTTP GET Request to https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD
ETH price is: 1811.08 USD. 24h Volume is 113909.01 USD. Market: Bitfinex
__Output from sandboxed source code__
Output represented as a hex string: 0x7b227072696365223a22313831312e3038222c22766f6c756d65223a223131333930392e3031222c226c6173744d61726b6574223a2242697466696e6578227d
Decoded as a string: {"price":"1811.08","volume":"113909.01","lastMarket":"Bitfinex"}
⣾ Request 0x51d3aa3be08c71c49a37d586de07eb85e87a403e35a73521c5c914c430e2de2d has been initiated. Waiting for fulfillment from the Decentralized Oracle Network...
ℹ Transaction confirmed, see https://mumbai.polygonscan.com/tx/0xd0140c40e65fdf51ea1b4396258915438693b61cfacb6d28a368bdd4f94b0443 for more details.
✔ Request 0x51d3aa3be08c71c49a37d586de07eb85e87a403e35a73521c5c914c430e2de2d fulfilled! Data has been written on-chain.
Response returned to client contract represented as a hex string: 0x7b227072696365223a22313831312e3139222c22766f6c756d65223a223131333931302e3933222c226c6173744d61726b6574223a2242697466696e6578227d
Decoded as a string: {"price":"1811.19","volume":"113910.93","lastMarket":"Bitfinex"}
Actual amount billed to subscription #443:
┌──────────────────────┬─────────────────────────────┐
│ Type │ Amount │
├──────────────────────┼─────────────────────────────┤
│ Transmission cost: │ 0.000071836060128269 LINK │
│ Base fee: │ 0.2 LINK │
│ │ │
│ Total cost: │ 0.200071836060128269 LINK │
└──────────────────────┴─────────────────────────────┘
The output of the example above gives you the following information:
- The
executeRequest
function was successfully called in theFunctionsConsumer
contract. The transaction in this example is 0xd0140c40e65fdf51ea1b4396258915438693b61cfacb6d28a368bdd4f94b0443. - The request ID is
0x51d3aa3be08c71c49a37d586de07eb85e87a403e35a73521c5c914c430e2de2d
. - The DON successfully fulfilled your request. The total cost was:
0.200071836060128269 LINK
. - The consumer contract received a response in
bytes
with a value of0x7b227072696365223a22313831312e3038222c22766f6c756d65223a223131333930392e3031222c226c6173744d61726b6574223a2242697466696e6578227d
. Decoding it off-chain tostring
gives you the following result:{"price":"1811.19","volume":"113910.93","lastMarket":"Bitfinex"}
.
At any time, you can run the functions-read
task with the contract
parameter to read the latest received response.
npx hardhat functions-read --contract REPLACE_CONSUMER_CONTRACT_ADDRESS --network REPLACE_NETWORK --configpath REPLACE_CONFIG_PATH
Example:
$ npx hardhat functions-read --contract 0x4B4BA2Fd6b93aDF8d6b6002E10540E58394388Ea --network polygonMumbai --configpath tutorials/3-custom-response/config.js
secp256k1 unavailable, reverting to browser version
Reading data from Functions client contract 0x4B4BA2Fd6b93aDF8d6b6002E10540E58394388Ea on network mumbai
On-chain response represented as a hex string: 0x7b227072696365223a22313831312e3139222c22766f6c756d65223a223131333931302e3933222c226c6173744d61726b6574223a2242697466696e6578227d
Decoded as a string: {"price":"1811.19","volume":"113910.93","lastMarket":"Bitfinex"}
Decoding 0x7b227072696365223a22313831312e3139222c22766f6c756d65223a223131333931302e3933222c226c6173744d61726b6574223a2242697466696e6578227d
from bytes
to string
gives you {"price":"1811.19","volume":"113910.93","lastMarket":"Bitfinex"}
. Off-chain, you can use JSON.parse() to convert the JSON string to a JSON object.
Explanation
FunctionsConsumer.sol
-
To write a Chainlink Functions consumer contract, your contract must import FunctionsClient.sol. You can read the API reference: FunctionsClient.
This contract is not available in an NPM package, so you must download and import it from within your project.
import {Functions, FunctionsClient} from "./dev/functions/FunctionsClient.sol";
-
Use the Functions.sol library to get all the functions needed for building a Chainlink Functions request. You can read the API reference: Functions.
using Functions for Functions.Request;
-
The latest request id, latest received response, and latest received error (if any) are defined as state variables. Note
latestResponse
andlatestError
are encoded as dynamically sized byte arraybytes
, so you will still need to decode them to read the response or error:bytes32 public latestRequestId; bytes public latestResponse; bytes public latestError;
-
We define the
OCRResponse
event that your smart contract will emit during the callbackevent OCRResponse(bytes32 indexed requestId, bytes result, bytes err);
-
Pass the oracle address for your network when you deploy the contract:
constructor(address oracle) FunctionsClient(oracle)
-
At any time, you can change the oracle address by calling the
updateOracleAddress
function. -
The two remaining functions are:
-
executeRequest
for sending a request. It receives the JavaScript source code, encrypted secrets, list of arguments to pass to the source code, subscription id, and callback gas limit as parameters. Then:-
It uses the
Functions
library to initialize the request and add any passed encrypted secrets or arguments. You can read the API Reference for Initializing a request, adding secrets, and adding arguments.Functions.Request memory req; req.initializeRequest(Functions.Location.Inline, Functions.CodeLanguage.JavaScript, source); if (secrets.length > 0) { req.addRemoteSecrets(secrets); } if (args.length > 0) req.addArgs(args);
-
It sends the request to the oracle by calling the
FunctionsClient
sendRequest
function. You can read the API reference for sending a request. Finally, it stores the request id inlatestRequestId
.bytes32 assignedReqID = sendRequest(req, subscriptionId, gasLimit); latestRequestId = assignedReqID;
-
-
fulfillRequest
to be invoked during the callback. This function is defined inFunctionsClient
asvirtual
(readfulfillRequest
API reference). So, your smart contract must override the function to implement the callback. The implementation of the callback is straightforward: the contract stores the latest response and error inlatestResponse
andlatestError
before emitting theOCRResponse
event.latestResponse = response; latestError = err; emit OCRResponse(requestId, response, err);
-
config.js
Read the Request Configuration section for a detailed description of each setting. This example uses the following settings:
codeLocation: Location.Inline
: The JavaScript code is provided within the request.codeLanguage: CodeLanguage.JavaScript
: The source code is developed in the JavaScript language.source: fs.readFileSync(path.resolve(__dirname, "source.js")).toString()
: The source code must be a script object. This example usesfs.readFileSync
to readsource.js
and callstoString()
to get the content as astring
object.args: ["ETH", "USD"]
: These arguments are passed to the source code. This example requests theETH/USD
price and daily volume.expectedReturnType: ReturnType.string
: The response received by the DON is encoded inbytes
. Because the price, daily volume and market are put in a JSON string, defineReturnType.string
to inform users how to decode the response received by the DON. Read source code explanation for more information.
source.js
You can check the expected API response. Directly paste the following URL in your browser https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD
or run a curl
command in your terminal:
curl -X 'GET' \
'https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD' \
-H 'accept: application/json'
The response should be similar to the following example:
{
"RAW": {
"ETH": {
"USD": {
"TYPE": "5",
"MARKET": "CCCAGG",
"FROMSYMBOL": "ETH",
"TOSYMBOL": "USD",
"FLAGS": "2049",
"PRICE": 2867.04,
"LASTUPDATE": 1650896942,
"MEDIAN": 2866.2,
"LASTVOLUME": 0.16533939,
"LASTVOLUMETO": 474.375243849,
"LASTTRADEID": "1072154517",
"VOLUMEDAY": 195241.78281014622,
"VOLUMEDAYTO": 556240560.4621655,
"VOLUME24HOUR": 236248.94641103,
...
}
The price is located at RAW,ETH,USD,PRICE
, the volume is at RAW,ETH,USD,VOLUME24HOUR
, and the market is at RAW,ETH,USD,LASTMARKET
.
Read the JavaScript code section for a detailed explanation of how to write compatible JavaScript source code. This JavaScript source code uses Functions.makeHttpRequest to make HTTP requests. To request the ETH/USD
price, the source code calls this URL: https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD
If you read the documentation for Functions.makeHttpRequest, you see you must provide the following parameters:
-
url
:https://min-api.cryptocompare.com/data/pricemultifull
-
params
: The query parameters object:{ fsyms: fromSymbol, tsyms: toSymbol }
Note fromSymbol
and toSymbol
are fetched from args
(see request config).
The code is self-explanatory and has comments to help you understand all the steps. The main steps are:
-
Construct the HTTP object
cryptoCompareRequest
usingFunctions.makeHttpRequest
. -
Run the HTTP request.
-
Read the asset price, daily volume, and market from the response.
-
Construct a JSON object.
const result = { price: price.toFixed(2), volume: volume.toFixed(2), lastMarket, }
-
Convert the JSON object to a JSON string using
JSON.stringify(result)
. This step is mandatory before encodingstring
tobytes
. -
Return the result as a buffer using the
Functions.string
helper function. Note: Read this article if you are new to Javascript Buffers and want to understand why they are important.