POST Data to an API
This tutorial shows you how to send a request to a Decentralized Oracle Network to call the Countries information GraphQL API. After OCR completes off-chain computation and aggregation, it returns the name, capital, and currency for the specified country to your smart contract. Because the endpoint is a GraphQL API, write a function that sends a GraphQL query in a POST HTTP method.
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/4-post-data directory.
git checkout tutorials
Tutorial
This tutorial is configured to get the country name, capital, and currency from countries.trevorblades.com in one request. For a detailed explanation of the code example, read the Explanation section.
- Open
config.js
. Theargs
value is["JP"]
to fetch information about Japan. You can adaptargs
to retrieve information about other countries as well. Read the Trevorblades API docs to get the list of supported countries. For more information about the request config file, read the request config explanation. - Open
source.js
to analyze the JavaScript source code. Read the source code explanation for a more detailed explanation of the source code file.
Simulation
The Chainlink Functions Hardhat Starter Kit includes a simulator to test your Functions code on your local machine. The functions-simulate
command executes your code in a local runtime environment and simulates an end-to-end fulfillment. This helps you to fix issues before you submit your functions to a Decentralized Oracle Network.
Run the functions-simulate
task to run the source code locally and make sure config.js
and source.js
are correctly written:
npx hardhat functions-simulate --configpath REPLACE_CONFIG_PATH
Example:
$ npx hardhat functions-simulate --configpath tutorials/4-post-data/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__
Get name, capital and currency for country code: JP
HTTP POST Request to https://countries.trevorblades.com/
country response { country: { name: 'Japan', capital: 'Tokyo', currency: 'JPY' } }
__Output from sandboxed source code__
Output represented as a hex string: 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
Decoded as a string: {"name":"Japan","capital":"Tokyo","currency":"JPY"}
__Simulated On-Chain Response__
Response returned to client contract represented as a hex string: 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
Decoded as a string: {"name":"Japan","capital":"Tokyo","currency":"JPY"}
Gas used by sendRequest: 352913
Gas used by client callback function: 98242
The output of this example tells you that the country name is Japan, capital is Tokyo, and the currency is JPY. Because the final result is a JSON object, convert it to a string and return the bytes
encoded value 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
in the callback. Read the source code explanation for a more detailed explanation.
Request
Send a request to the Decentralized Oracle Network to run the GraphQL query. Run the functions-request
task with the subid
(subscription ID) and contract
parameters. This task passes the JavaScript source code, arguments, and secrets when it calls the executeRequest
function in your deployed FunctionsConsumer
contract. Read the functionsConsumer section for a more detailed explanation of the contract.
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/4-post-data/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.0004738875050548 MATIC, which (using mainnet value) is $0.0005282675179223535
If the request's callback uses all 100,000 gas, this request will charge the subscription:
0.200148583233322474 LINK
Continue? Enter (y) Yes / (n) No
y
Simulating Functions request locally...
__Console log messages from sandboxed code__
Get name, capital and currency for country code: JP
HTTP POST Request to https://countries.trevorblades.com/
country response { country: { name: 'Japan', capital: 'Tokyo', currency: 'JPY' } }
__Output from sandboxed source code__
Output represented as a hex string: 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
Decoded as a string: {"name":"Japan","capital":"Tokyo","currency":"JPY"}
⣾ Request 0xfd3458d471506c9c59fce518d8b7267240940903e4a42dac144a29b90c9c9ab4 has been initiated. Waiting for fulfillment from the Decentralized Oracle Network...
ℹ Transaction confirmed, see https://mumbai.polygonscan.com/tx/0xb52fa5780be565ee0df732765d2e0e08e827e46be1df1f1eadab98a717c78c2b for more details.
✔ Request 0xfd3458d471506c9c59fce518d8b7267240940903e4a42dac144a29b90c9c9ab4 fulfilled! Data has been written on-chain.
Response returned to client contract represented as a hex string: 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
Decoded as a string: {"name":"Japan","capital":"Tokyo","currency":"JPY"}
Actual amount billed to subscription #443:
┌──────────────────────┬─────────────────────────────┐
│ Type │ Amount │
├──────────────────────┼─────────────────────────────┤
│ Transmission cost: │ 0.000067030219895325 LINK │
│ Base fee: │ 0.2 LINK │
│ │ │
│ Total cost: │ 0.200067030219895325 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 0xb52fa5780be565ee0df732765d2e0e08e827e46be1df1f1eadab98a717c78c2b. - The request ID is
0xfd3458d471506c9c59fce518d8b7267240940903e4a42dac144a29b90c9c9ab4
. - The DON successfully fulfilled your request. The total cost was:
0.200067030219895325 LINK
. - The consumer contract received a response in
bytes
with a value of0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
. Decoding it off-chain to astring
gives you a result:{"name":"Japan","capital":"Tokyo","currency":"JPY"}
.
At any time, you can run the functions-read
task again 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/4-post-data/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: 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
Decoded as a string: {"name":"Japan","capital":"Tokyo","currency":"JPY"}
Decoding 0x7b226e616d65223a224a6170616e222c226361706974616c223a22546f6b796f222c2263757272656e6379223a224a5059227d
from bytes
to string
gives you a string {"name":"Japan","capital":"Tokyo","currency":"JPY"}
. 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. In this example, the settings are the following:
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: ["JP"]
: These arguments are passed to the source code. You can change the country code to request information of another country.expectedReturnType: ReturnType.string
: The response the DON receives is encoded inbytes
. Because the country name, capital, and currency are put in a JSON string, defineReturnType.string
to inform users how to decode the response received by the DON. Read the source code explanation for more information.`
source.js
To check the expected API response:
-
In your browser, open the countries GraphQL playground:
https://countries.trevorblades.com/
-
Write this query:
{ country(code: "JP") { name capital currency } }
-
Click on play to get the answer :
{ "data": { "country": { "name": "Japan", "capital": "Tokyo", "currency": "JPY" } } }
Read the JavaScript code section for a detailed explanation of how to write a compatible JavaScript source code. This JavaScript source code uses Functions.makeHttpRequest to make HTTP requests. To request the JP
country information, the source code calls the https://countries.trevorblades.com/
URL and provides the query data in the HTTP request body. If you read the Functions.makeHttpRequest documentation, you see that you must provide the following parameters:
-
url:
https://countries.trevorblades.com/
-
data (HTTP body):
{ query: `{\ country(code: "${countryCode}") { \ name \ capital \ currency \ } \ }`, }
The countryCode
is fetched from the args
. See the request config section for more information.
The code is self-explanatory and has comments to help you understand all the steps. The main steps are:
-
Construct the HTTP object
countryRequest
usingFunctions.makeHttpRequest
. -
Run the HTTP request.
-
Read the country name, capital, and currency from the response.
-
Construct a JSON object:
const result = { name: countryData.country.name, capital: countryData.country.capital, currency: countryData.country.currency, }
-
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.