diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 80c71f26a31..da074f66e19 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -518,10 +518,6 @@ export const SIDEBAR: Partial> = { title: "Call an API", url: "chainlink-functions/tutorials/api-query-parameters", }, - { - title: "Return Custom Data Types", - url: "chainlink-functions/tutorials/api-custom-response", - }, { title: "POST Data to an API", url: "chainlink-functions/tutorials/api-post-data", @@ -530,6 +526,14 @@ export const SIDEBAR: Partial> = { title: "Using DON-hosted Secrets in Requests", url: "chainlink-functions/tutorials/api-use-secrets", }, + { + title: "Using Imports with Functions", + url: "chainlink-functions/tutorials/importing-packages", + }, + { + title: "Return multiple responses and decode them in your smart contract", + url: "chainlink-functions/tutorials/abi-decoding", + }, { title: "Offchain secrets - Using Gists", url: "chainlink-functions/tutorials/api-use-secrets-gist", @@ -554,14 +558,6 @@ export const SIDEBAR: Partial> = { title: "Automate your Functions (Custom Logic Automation)", url: "chainlink-functions/tutorials/automate-functions-custom-logic", }, - { - title: "Using Imports with Functions", - url: "chainlink-functions/tutorials/importing-packages", - }, - { - title: "Return multiple responses and decode them in your smart contract", - url: "chainlink-functions/tutorials/abi-decoding", - }, ], }, { diff --git a/src/content/chainlink-functions/tutorials/abi-decoding.mdx b/src/content/chainlink-functions/tutorials/abi-decoding.mdx index 804808cce29..dc41370f85a 100644 --- a/src/content/chainlink-functions/tutorials/abi-decoding.mdx +++ b/src/content/chainlink-functions/tutorials/abi-decoding.mdx @@ -224,3 +224,104 @@ The primary function that the script executes is `makeRequestMumbai`. This funct - Call the `s_answer`, `s_updatedAt`, `s_decimals`, and `s_description` functions of your consumer contract to fetch the decoded values. - Log the decoded values to the console. + +## Handling complex data types with ABI Encoding and Decoding + +This section details the process of encoding complex data types into [`Uint8Array` typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) to fulfill the Ethereum Virtual Machine (EVM) data handling requirements for transactions and smart contract interactions. It will then outline the steps for decoding these byte arrays to align with corresponding structures defined in Solidity. + +Consider a scenario where a contract needs to interact with a data structure that encapsulates multiple properties, including nested objects: + +```json +{ + "id": 1, + "metadata": { + "description": "Decentralized Oracle Network", + "awesome": true + } +} +``` + +Transferring and storing this kind of structured data requires encoding it into a format (array of 8-bit unsigned integers) that smart contracts can accept and process. + +### Encoding in JavaScript + +Because Chainlink Functions supports important external modules, you can import a web3 library such as `ethers.js` and perform encoding. +To encode complex data structures, you can use the [`defaultAbiCoder.encode`](https://docs.ethers.org/v6/api/abi/abi-coder/#AbiCoder-encode) function from the `ethers.js` library. The function takes two arguments: + +- An array of Solidity data types. +- The corresponding data in JavaScript format. + +and returns the encoded data as a hexadecimal string. + +Here's how you can encode the aforementioned complex data: + +```javascript +const { ethers } = await import("npm:ethers@6.10.0") // Import ethers.js v6.10.0 + +const abiCoder = ethers.AbiCoder.defaultAbiCoder() + +// Define the data structure +const complexData = { + id: 1, + metadata: { + description: "Decentralized Oracle Network", + awesome: true, + }, +} + +// Define the Solidity types for encoding +const types = ["tuple(uint256 id, tuple(string description, bool awesome) metadata)"] + +// Data to encode +const data = [ + [ + complexData.id, + { + description: complexData.metadata.description, + awesome: complexData.metadata.awesome, + }, + ], +] + +// Encoding the data +const encodedData = abiCoder.encode(types, data) +``` + +After encoding the data, it's necessary to format it as a `Uint8Array` array for smart contract interactions and blockchain transactions. In Solidity, the data type for byte arrays data is `bytes`. However, when working in a JavaScript environment, such as when using the `ethers.js` library, the equivalent data structure is a `Uint8Array`. + +The `ethers.js` library provides the [`getBytes`](https://docs.ethers.org/v6/api/utils/#getBytes) function to convert encoded hexadecimal strings into a `Uint8Array`: + +```javascript +return ethers.getBytes(encodedData) // Return the encoded data converted into a Uint8Array +``` + +### Decoding in Solidity + +The encoded data can be decoded using the `abi.decode` function. To decode the data, you'll need to handle the decoding in your `fulfillRequest` function: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +contract DataDecoder { + // Example of a structure to hold the complex data + struct Metadata { + string description; + bool awesome; + } + + struct ComplexData { + uint256 id; + Metadata metadata; + } + + // ... other contract functions (including the send request function) + + // Fulfill function (callback function) + function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override { + // Decode the response + ComplexData memory metadata = abi.decode(response, (ComplexData)); + // ... rest of the function + } +} +``` diff --git a/src/content/chainlink-functions/tutorials/api-custom-response.mdx b/src/content/chainlink-functions/tutorials/api-custom-response.mdx deleted file mode 100644 index 4fb5aa7ca2f..00000000000 --- a/src/content/chainlink-functions/tutorials/api-custom-response.mdx +++ /dev/null @@ -1,231 +0,0 @@ ---- -section: chainlinkFunctions -date: Last Modified -title: "Return Custom Data Types" ---- - -import { Aside } from "@components" -import ChainlinkFunctions from "@features/chainlink-functions/common/ChainlinkFunctions.astro" - -In this tutorial, you send a request to a Decentralized Oracle Network to call the [Cryptocompare GET /data/pricemultifull API](https://min-api.cryptocompare.com/documentation?key=Price&cat=multipleSymbolsFullPriceEndpoint). After [OCR](/chainlink-functions/resources/architecture) completes offchain 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. - - - - - - - -## 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 [Examine the code](#examine-the-code) section. - -You can locate the scripts used in this tutorial in the [_examples/3-custom-response_ directory](https://github.com/smartcontractkit/smart-contract-examples/tree/main/functions-examples/examples/3-custom-response). - -To run the example: - -1. Open the file `request.js`, which is located in the `3-custom-response` folder. -1. Replace the consumer contract address and the subscription ID with your own values. - - ```javascript - const consumerAddress = "0x8dFf78B7EE3128D00E90611FBeD20A71397064D9" // REPLACE this with your Functions consumer address - const subscriptionId = 3 // REPLACE this with your subscription ID - ``` - -1. Make a request: - - ```shell - node examples/3-custom-response/request.js - ``` - - The script runs your function in a sandbox environment before making an onchain transaction: - - ```text - $ node examples/3-custom-response/request.js - secp256k1 unavailable, reverting to browser version - Start simulation... - Performing simulation with the following versions: - deno 1.36.3 (release, aarch64-apple-darwin) - v8 11.6.189.12 - typescript 5.1.6 - - - Simulation result { - capturedTerminalOutput: 'HTTP GET Request to https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD\n' + - 'ETH price is: 1632.14 USD. 24h Volume is 57645.66 USD. Market: Kraken\n', - responseBytesHexstring: '0x7b227072696365223a22313633322e3134222c22766f6c756d65223a2235373634352e3636222c226c6173744d61726b6574223a224b72616b656e227d' - } - ✅ Decoded response to string: {"price":"1632.14","volume":"57645.66","lastMarket":"Kraken"} - - Estimate request costs... - Duplicate definition of Transfer (Transfer(address,address,uint256,bytes), Transfer(address,address,uint256)) - Fulfillment cost estimated to 0.000000000000215 LINK - - Make request... - - ✅ Functions request sent! Transaction hash 0x99b70944262ff2f0f8c4c98eaa9927c3519267522fd82d5442a5cfcc6cf913d4. Waiting for a response... - See your request in the explorer https://mumbai.polygonscan.com/tx/0x99b70944262ff2f0f8c4c98eaa9927c3519267522fd82d5442a5cfcc6cf913d4 - - ✅ Request 0x121db06a58c4646ca9282520b6a526d83bf84831647bb2c7fc99e36cbd343aa4 fulfilled with code: 0. Cost is 0.00003809801507364 LINK. Complete response: { - requestId: '0x121db06a58c4646ca9282520b6a526d83bf84831647bb2c7fc99e36cbd343aa4', - subscriptionId: 3, - totalCostInJuels: 38098015073640n, - responseBytesHexstring: '0x7b227072696365223a22313633322e3634222c22766f6c756d65223a2235373637332e3336222c226c6173744d61726b6574223a224269747374616d70227d', - errorString: '', - returnDataBytesHexstring: '0x', - fulfillmentCode: 0 - } - - ✅ Decoded response to string: {"price":"1632.64","volume":"57673.36","lastMarket":"Bitstamp"} - ``` - - The output of the example gives you the following information: - - - Your request is first run on a sandbox environment to ensure it is correctly configured. - - The fulfillment costs are estimated before making the request. - - Your request was successfully sent to Chainlink Functions. The transaction in this example is [0x99b70944262ff2f0f8c4c98eaa9927c3519267522fd82d5442a5cfcc6cf913d4](https://mumbai.polygonscan.com/tx/0x99b70944262ff2f0f8c4c98eaa9927c3519267522fd82d5442a5cfcc6cf913d4) and the request ID is `0x121db06a58c4646ca9282520b6a526d83bf84831647bb2c7fc99e36cbd343aa4`. - - - The DON successfully fulfilled your request. The total cost was: `0.00003809801507364 LINK`. - - The consumer contract received a response in `bytes` with a value of `0x7b227072696365223a22313633322e3634222c22766f6c756d65223a2235373637332e3336222c226c6173744d61726b6574223a224269747374616d70227d`. Decoding it off-chain to `string` gives you a result: - - `{"price":"1632.64","volume":"57673.36","lastMarket":"Bitstamp"}` - -## Examine the code - -### FunctionsConsumerExample.sol - - - -### JavaScript example - -#### source.js - -The Decentralized Oracle Network will run the [JavaScript code](https://github.com/smartcontractkit/smart-contract-examples/blob/main/functions-examples/examples/3-custom-response/source.js). The code is self-explanatory and has comments to help you understand all the steps. - - - -This JavaScript source code uses [Functions.makeHttpRequest](/chainlink-functions/api-reference/javascript-source#http-requests) to make HTTP requests. To request the `ETH/USD` price, the source code calls the `https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD` URL. If you read the [Functions.makeHttpRequest](/chainlink-functions/api-reference/javascript-source#http-requests) documentation, you see that you must provide the following parameters: - -- `url`: `https://min-api.cryptocompare.com/data/pricemultifull` -- `params`: The query parameters object: - - ``` - { - fsyms: fromSymbol, - tsyms: toSymbol - } - ``` - -To check the expected API response, you can directly paste the following URL in your browser `https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD` or run the `curl` command in your terminal: - -```bash -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: - -{/* prettier-ignore */} -```json -{ - "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`. - -The main steps of the scripts are: - -- Fetch `fromSymbol` and `toSymbol` from `args`. -- Construct the HTTP object `cryptoCompareRequest` using `Functions.makeHttpRequest`. -- Make the HTTP call. -- Read the asset price, daily volume, and market from the response. -- Construct a JSON object. - - ```javascript - 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 encoding `string` to `bytes`. -- Return the result as a [buffer](https://nodejs.org/api/buffer.html#buffer) using the `Functions.string` helper function. **Note**: Read this [article](https://www.freecodecamp.org/news/do-you-want-a-better-understanding-of-buffer-in-node-js-check-this-out-2e29de2968e8/) if you are new to Javascript Buffers and want to understand why they are important. - -#### request.js - -This explanation focuses on the [request.js](https://github.com/smartcontractkit/smart-contract-examples/blob/main/functions-examples/examples/3-custom-response/request.js) script and shows how to use the [Chainlink Functions NPM package](https://github.com/smartcontractkit/functions-toolkit) in your own JavaScript/TypeScript project to send requests to a DON. The code is self-explanatory and has comments to help you understand all the steps. - -The script imports: - -- [path](https://nodejs.org/docs/latest/api/path.html) and [fs](https://nodejs.org/api/fs.html) : Used to read the [source file](https://github.com/smartcontractkit/smart-contract-examples/blob/main/functions-examples/examples/3-custom-response/source.js). -- [ethers](https://docs.ethers.org/v5/): Ethers.js library, enables the script to interact with the blockchain. -- `@chainlink/functions-toolkit`: Chainlink Functions NPM package. All its utilities are documented in the [NPM README](https://github.com/smartcontractkit/functions-toolkit/blob/main/README.md). -- `@chainlink/env-enc`: A tool for loading and storing encrypted environment variables. Read the [official documentation](https://www.npmjs.com/package/@chainlink/env-enc) to learn more. -- `../abi/functionsClient.json`: The abi of the contract your script will interact with. **Note**: The script was tested with this [FunctionsConsumerExample contract](https://remix.ethereum.org/#url=https://docs.chain.link/samples/ChainlinkFunctions/FunctionsConsumerExample.sol). - -The script has two hardcoded values that you have to change using your own Functions consumer contract and subscription ID: - -```javascript -const consumerAddress = "0x8dFf78B7EE3128D00E90611FBeD20A71397064D9" // REPLACE this with your Functions consumer address -const subscriptionId = 3 // REPLACE this with your subscription ID -``` - -The primary function that the script executes is `makeRequestMumbai`. This function consists of five main parts: - -1. Definition of necessary identifiers: - - - `routerAddress`: Chainlink Functions router address on Polygon Mumbai. - - `donId`: Identifier of the DON that will fulfill your requests on Polygon Mumbai. - - `explorerUrl`: Block explorer url of Polygon Mumbai. - - `source`: The source code must be a string object. That's why we use `fs.readFileSync` to read `source.js` and then call `toString()` to get the content as a `string` object. - - `args`: During the execution of your function, These arguments are passed to the source code. The `args` value is `["ETH", "USD"]`, which fetches the current `ETH/USD` price. You can adapt `args` to fetch another asset price. See the [CryptoCompare API docs](https://min-api.cryptocompare.com/documentation?key=Price&cat=multipleSymbolsFullPriceEndpoint) to get the list of supported symbols. - - `gasLimit`: Maximum gas that Chainlink Functions can use when transmitting the response to your contract. - - Initialization of ethers `signer` and `provider` objects. The signer is used to make transactions on the blockchain, and the provider reads data from the blockchain. - -1. Simulating your request in a local sandbox environment: - - - Use `simulateScript` from the Chainlink Functions NPM package. - - Read the `response` of the simulation. If successful, use the Functions NPM package `decodeResult` function and `ReturnType` enum to decode the response to the expected returned type (`ReturnType.string` in this example). - -1. Estimating the costs: - - - Initialize a `SubscriptionManager` from the Functions NPM package, then call the `estimateFunctionsRequestCost`. - - The response is returned in Juels (1 LINK = 10\*\*18 Juels). Use the `ethers.utils.formatEther` utility function to convert the output to LINK. - -1. Making a Chainlink Functions request: - - - Initialize your functions consumer contract using the contract address, abi, and ethers signer. - - Call the `sendRequest` function of your consumer contract. - -1. Waiting for the response: - - - Initialize a `ResponseListener` from the Functions NPM package and then call the `listenForResponseFromTransaction` function to wait for a response. By default, this function waits for five minutes. - - Upon reception of the response, use the Functions NPM package `decodeResult` function and `ReturnType` enum to decode the response to the expected returned type (`ReturnType.string` in this example). diff --git a/src/content/chainlink-functions/tutorials/importing-packages.mdx b/src/content/chainlink-functions/tutorials/importing-packages.mdx index e7ccf527398..98c0909f2bd 100644 --- a/src/content/chainlink-functions/tutorials/importing-packages.mdx +++ b/src/content/chainlink-functions/tutorials/importing-packages.mdx @@ -115,7 +115,7 @@ To run the example: - Your request was successfully sent to Chainlink Functions. The transaction in this example is `0xed3d0419189c012ce852b37b51d47bdcd80f06a4749b4c01a81a3f5fb06139e3` and the request ID is `0xdfb161de5a6ad34e58bb115dd07651a11d4cf4739652f509ecad78a1bf506e82`. - The DON successfully fulfilled your request. The total cost was: `0.200435783655508510 LINK`. - - The consumer contract received a response in `bytes` with a value of `0xa8a7bec42edc16cf549f69e734161f2f2550a1057bb4060611a8043253ee61ef`. Decoding it offchain to `int256` gives you a result: `4367193987453`. + - The consumer contract received a response in `bytes` with a value of `0x000000000000000000000000000000000000000000000000000003f8d10bd97d`. Decoding it offchain to `int256` gives you a result: `4367193987453`. ## Examine the code @@ -195,7 +195,7 @@ The primary function that the script executes is `makeRequestMumbai`. This funct - `donId`: Identifier of the DON that will fulfill your requests on Polygon Mumbai. - `explorerUrl`: Block explorer url of Polygon Mumbai. - `source`: The source code must be a string object. That's why we use `fs.readFileSync` to read `source.js` and then call `toString()` to get the content as a `string` object. - - `args`: During the execution of your function, These arguments are passed to the source code. + - `args`: During the execution of your function, these arguments are passed to the source code. - `gasLimit`: Maximum gas that Chainlink Functions can use when transmitting the response to your contract. - Initialization of ethers `signer` and `provider` objects. The signer is used to make transactions on the blockchain, and the provider reads data from the blockchain. diff --git a/src/content/chainlink-functions/tutorials/index.mdx b/src/content/chainlink-functions/tutorials/index.mdx index 56155cdb6b3..72aa3870f95 100644 --- a/src/content/chainlink-functions/tutorials/index.mdx +++ b/src/content/chainlink-functions/tutorials/index.mdx @@ -17,14 +17,13 @@ import { Aside } from "@components" - [Simple Computation](/chainlink-functions/tutorials/simple-computation) - [Call an API](/chainlink-functions/tutorials/api-query-parameters) -- [Return Custom Data Types](/chainlink-functions/tutorials/api-custom-response) - [POST Data to an API](/chainlink-functions/tutorials/api-post-data) - [Using DON-hosted Secrets in Requests](/chainlink-functions/tutorials/api-use-secrets) +- [Using Imports with Functions](/chainlink-functions/tutorials/importing-packages) +- [Use ABI encoding and decoding](/chainlink-functions/tutorials/abi-decoding) - [Using User-hosted (gist) Secrets in Requests](/chainlink-functions/tutorials/api-use-secrets-gist) - [Using User-hosted Secrets in Requests](/chainlink-functions/tutorials/api-use-secrets-offchain) - [Call Multiple Data Sources](/chainlink-functions/tutorials/api-multiple-calls) - [Encode request data offchain](/chainlink-functions/tutorials/encode-request-offchain) - [Automate your Functions (Time-based Automation)](/chainlink-functions/tutorials/automate-functions) - [Automate your Functions (Custom Logic Automation)](/chainlink-functions/tutorials/automate-functions-custom-logic) -- [Using Imports with Functions](/chainlink-functions/tutorials/importing-packages) -- [Use ABI encoding and decoding](/chainlink-functions/tutorials/abi-decoding)