NEW

Chainlink Data Streams have officially launched on mainnet. Sign up for early access.

Back

Request Computation

This tutorial shows you how to run computations on the Chainlink Functions Decentralized Oracle Network (DON). The example code computes the geometric mean of numbers in a list. After OCR completes off-chain computation and aggregation, it returns the result to your smart contract.

Before you begin

  1. 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.

  2. Make sure your subscription has enough LINK to pay for your requests. You can check your subscription details (including the balance in LINK) in the Chainlink Functions frontend. If your subscription runs out of LINK, follow the Fund a Subscription guide.

  3. You can locate the scripts used in this tutorial in the examples/1-simple-computation directory.

Tutorial

This tutorial is configured to get the average (geometric mean) from a list of numbers 1,2,3,4,5,6,7,8,9,10. Read the Examine the code section for a detailed description of the code example.

To run the example:

  1. Open the file request.js, which is located in the 1-simple-computation folder.

  2. Replace the consumer contract address and the subscription ID with your own values.

    const consumerAddress = "0x8dFf78B7EE3128D00E90611FBeD20A71397064D9" // REPLACE this with your Functions consumer address
    const subscriptionId = 3 // REPLACE this with your subscription ID
  3. Make a request:

    node examples/1-simple-computation/request.js

    The script runs your function in a sandbox environment before making an on-chain transaction:

    $ node examples/1-simple-computation/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: 'calculate geometric mean of 1,2,3,4,5,6,7,8,9,10\ngeometric mean is: 4.53\n',
      responseBytesHexstring: '0x00000000000000000000000000000000000000000000000000000000000001c5'
    }
    ✅ Decoded response to uint256:  453n
    
    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 0x662988579b77c0ab2ca78e40a3ece7d150bd6afb26bbaf42b2c3e5f13a4f895b -  Request id is 0xfedbdc53de4c796cb44e693a3a34600d616b76ce7ea3902849e687a20a3d86fe. Waiting for a response...
    See your request in the explorer https://mumbai.polygonscan.com/tx/0x662988579b77c0ab2ca78e40a3ece7d150bd6afb26bbaf42b2c3e5f13a4f895b
    
    ✅ Request 0xfedbdc53de4c796cb44e693a3a34600d616b76ce7ea3902849e687a20a3d86fe fulfilled with code: 0. Cost is 0.000038017374812478 LINK. Complete response:  {
      requestId: '0xfedbdc53de4c796cb44e693a3a34600d616b76ce7ea3902849e687a20a3d86fe',
      subscriptionId: 3,
      totalCostInJuels: 38017374812478n,
      responseBytesHexstring: '0x00000000000000000000000000000000000000000000000000000000000001c5',
      errorString: '',
      returnDataBytesHexstring: '0x',
      fulfillmentCode: 0
    }
    
    ✅ Decoded response to uint256:  453n

    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 0x662988579b77c0ab2ca78e40a3ece7d150bd6afb26bbaf42b2c3e5f13a4f895b and the request ID is 0xfedbdc53de4c796cb44e693a3a34600d616b76ce7ea3902849e687a20a3d86fe.

    • The DON successfully fulfilled your request. The total cost was: 0.000038017374812478 LINK.

    • The consumer contract received a response in bytes with a value of 0x00000000000000000000000000000000000000000000000000000000000001c5. Decoding it off-chain to uint256 gives you a result: 453.

Examine the code

FunctionsConsumerExample.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/FunctionsClient.sol";
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/libraries/FunctionsRequest.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */
contract FunctionsConsumerExample is FunctionsClient, ConfirmedOwner {
    using FunctionsRequest for FunctionsRequest.Request;

    bytes32 public s_lastRequestId;
    bytes public s_lastResponse;
    bytes public s_lastError;

    error UnexpectedRequestID(bytes32 requestId);

    event Response(bytes32 indexed requestId, bytes response, bytes err);

    constructor(
        address router
    ) FunctionsClient(router) ConfirmedOwner(msg.sender) {}

    /**
     * @notice Send a simple request
     * @param source JavaScript source code
     * @param encryptedSecretsUrls Encrypted URLs where to fetch user secrets
     * @param donHostedSecretsSlotID Don hosted secrets slotId
     * @param donHostedSecretsVersion Don hosted secrets version
     * @param args List of arguments accessible from within the source code
     * @param bytesArgs Array of bytes arguments, represented as hex strings
     * @param subscriptionId Billing ID
     */
    function sendRequest(
        string memory source,
        bytes memory encryptedSecretsUrls,
        uint8 donHostedSecretsSlotID,
        uint64 donHostedSecretsVersion,
        string[] memory args,
        bytes[] memory bytesArgs,
        uint64 subscriptionId,
        uint32 gasLimit,
        bytes32 jobId
    ) external onlyOwner returns (bytes32 requestId) {
        FunctionsRequest.Request memory req;
        req.initializeRequestForInlineJavaScript(source);
        if (encryptedSecretsUrls.length > 0)
            req.addSecretsReference(encryptedSecretsUrls);
        else if (donHostedSecretsVersion > 0) {
            req.addDONHostedSecrets(
                donHostedSecretsSlotID,
                donHostedSecretsVersion
            );
        }
        if (args.length > 0) req.setArgs(args);
        if (bytesArgs.length > 0) req.setBytesArgs(bytesArgs);
        s_lastRequestId = _sendRequest(
            req.encodeCBOR(),
            subscriptionId,
            gasLimit,
            jobId
        );
        return s_lastRequestId;
    }

    /**
     * @notice Send a pre-encoded CBOR request
     * @param request CBOR-encoded request data
     * @param subscriptionId Billing ID
     * @param gasLimit The maximum amount of gas the request can consume
     * @param jobId ID of the job to be invoked
     * @return requestId The ID of the sent request
     */
    function sendRequestCBOR(
        bytes memory request,
        uint64 subscriptionId,
        uint32 gasLimit,
        bytes32 jobId
    ) external onlyOwner returns (bytes32 requestId) {
        s_lastRequestId = _sendRequest(
            request,
            subscriptionId,
            gasLimit,
            jobId
        );
        return s_lastRequestId;
    }

    /**
     * @notice Store latest result/error
     * @param requestId The request ID, returned by sendRequest()
     * @param response Aggregated response from the user code
     * @param err Aggregated error from the user code or from the execution pipeline
     * Either response or error parameter will be set, but never both
     */
    function fulfillRequest(
        bytes32 requestId,
        bytes memory response,
        bytes memory err
    ) internal override {
        if (s_lastRequestId != requestId) {
            revert UnexpectedRequestID(requestId);
        }
        s_lastResponse = response;
        s_lastError = err;
        emit Response(requestId, s_lastResponse, s_lastError);
    }
}
  • To write a Chainlink Functions consumer contract, your contract must import FunctionsClient.sol and FunctionsRequest.sol. You can read the API references: FunctionsClient and FunctionsRequest.

    These contracts are available in an NPM package, so you can import them from within your project.

    import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/FunctionsClient.sol";
    import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/libraries/FunctionsRequest.sol";
  • Use the FunctionsRequest.sol library to get all the functions needed for building a Chainlink Functions request.

    using FunctionsRequest for FunctionsRequest.Request;
  • The latest request id, latest received response, and latest received error (if any) are defined as state variables:

    bytes32 public s_lastRequestId;
    bytes public s_lastResponse;
    bytes public s_lastError;
  • We define the Response event that your smart contract will emit during the callback

    event Response(bytes32 indexed requestId, bytes response, bytes err);
  • Pass the router address for your network when you deploy the contract:

    constructor(address router) FunctionsClient(router)
  • The three remaining functions are:

    • sendRequest for sending a request. It receives the JavaScript source code, encrypted secretsUrls (in case the encrypted secrets are hosted by the user), DON hosted secrets slot id and version (in case the encrypted secrets are hosted by the DON), list of arguments to pass to the source code, subscription id, and callback gas limit as parameters. Then:

      • It uses the FunctionsRequestlibrary to initialize the request and add any passed encrypted secrets reference or arguments. You can read the API Reference for Initializing a request, adding user hosted secrets, adding DON hosted secrets, adding arguments, and adding bytes arguments.

        FunctionsRequest.Request memory req;
        req.initializeRequestForInlineJavaScript(source);
        if (encryptedSecretsUrls.length > 0)
            req.addSecretsReference(encryptedSecretsUrls);
        else if (donHostedSecretsVersion > 0) {
            req.addDONHostedSecrets(
                donHostedSecretsSlotID,
                donHostedSecretsVersion
            );
        }
        if (args.length > 0) req.setArgs(args);
        if (bytesArgs.length > 0) req.setBytesArgs(bytesArgs);
      • It sends the request to the router by calling the FunctionsClient sendRequest function. You can read the API reference for sending a request. Finally, it stores the request id in s_lastRequestId then return it.

        s_lastRequestId = _sendRequest(
            req.encodeCBOR(),
            subscriptionId,
            gasLimit,
            jobId
        );
        return s_lastRequestId;

        Note: _sendRequest accepts requests encoded in bytes. Therefore, you must encode it using encodeCBOR.

    • sendRequestCBOR for sending a request already encoded in bytes. It receives the request object encoded in bytes, subscription id, and callback gas limit as parameters. Then, it sends the request to the router by calling the FunctionsClient sendRequest function. Note: This function is helpful if you want to encode a request off-chain before sending it, saving gas when submitting the request.

  • fulfillRequest to be invoked during the callback. This function is defined in FunctionsClient as virtual (read fulfillRequest 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 in s_lastResponse and s_lastError before emitting the Response event.

    s_lastResponse = response;
    s_lastError = err;
    emit Response(requestId, s_lastResponse, s_lastError);

JavaScript example

source.js

The Decentralized Oracle Network will run the JavaScript code. The code is self-explanatory and has comments to help you understand all the steps. Note: It is important to note that the custom source code you want to execute in a Functions request can use vanilla Deno but cannot use any require statements or imported modules.

The main steps are:

  • Read the numbers provided as arguments in the args setting. Because args is an array of string, call parseInt to convert from string to number. Note: args contains string values that are injected into the JavaScript source code when the Decentralized Oracle Network executes your function. You can access theses values from your JavaScript code using the name args.

  • Calculate the average (geometric mean): First, compute the product of the numbers. Then, calculate the nth root of the product where n is the length of args.

  • Return the result as a buffer using the Functions.encodeUint256 helper function. Because Solidity doesn't support decimals, multiply the result by 100 and round the result to the nearest integer. There are other helper functions that you could use depending on the response type:

    • Functions.encodeUint256: Takes a positive JavaScript integer number and returns a Buffer of 32 bytes representing a uint256 type in Solidity.
    • Functions.encodeInt256: Takes a JavaScript integer number and returns a Buffer of 32 bytes representing an int256 type in Solidity.
    • Functions.encodeString: Takes a JavaScript string and returns a Buffer representing a string type in Solidity.

    Note: You are not required to use these encoding functions as long as the JavaScript code returns a Buffer representing the bytes array returned on-chain. Read this article if you are new to Javascript Buffers and want to understand why they are important.

request.js

This explanation focuses on the request.js script and shows how to use the Chainlink Functions NPM package 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 and fs : Used to read the source file.
  • ethers: 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.
  • @chainlink/env-enc: A tool for loading and storing encrypted environment variables. Read the official documentation 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.

The script has two hardcoded values that you have to change using your own Functions consumer contract and subscription ID:

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 can be broken into 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.
    • 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.
  2. 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.uint256 in this example).
  3. 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.
  4. Making a Chainlink Functions request:

    • Initialize your functions consumer contract using the contract address, abi, and ethers signer.
    • Make a static call to the sendRequest function of your consumer contract to return the request ID that Chainlink Functions will generate.
    • Call the sendRequest function of your consumer contract.
  5. Waiting for the response:

    • Initialize a ResponseListener from the Functions NPM package and then call the listenForResponse 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.uint256 in this example).

What's next

Stay updated on the latest Chainlink news