NEW

Attend SmartCon 2023 to explore the future of Web3. Sign up now.

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

  3. Check out the tutorials branch of the Chainlink Functions Starter Kit. You can locate this tutorial in the /tutorials/1-simple-computation directory.

    git checkout tutorials

Tutorial

This tutorial is configured to get the average (geometric mean) from a list of numbers. For a detailed explanation of the code example, read the Explanation section.

  • Open config.js. Note that the args value is ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], so the source code will calculate the average (geometric mean) of "1,2,3,4,5,6,7,8,9,10". You can change the args value to compute another set's average. Read the request config explanation for more information about the request config file.
  • Open source.js to analyze the JavaScript source code. Read the source code explanation for a more detailed description of the source code.

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, helping you fix any issues before submitting 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/1-simple-computation/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__
calculate geometric mean of 1,2,3,4,5,6,7,8,9,10
geometric mean is: 4.53

__Output from sandboxed source code__
Output represented as a hex string: 0x00000000000000000000000000000000000000000000000000000000000001c5
Decoded as a uint256: 453

__Simulated On-Chain Response__
Response returned to client contract represented as a hex string: 0x00000000000000000000000000000000000000000000000000000000000001c5
Decoded as a uint256: 453

Gas used by sendRequest: 351895
Gas used by client callback function: 75017

Reading the output of the example above, you can note that the geometric mean was correctly computed: 4.53. Because Solidity does not support decimals, we move the decimal point so that the value looks like an integer 453 before returning the bytes encoded value 0x00000000000000000000000000000000000000000000000000000000000001c5 in the callback. Note: Read the source code explanation for a more detailed explanation.

Request

Send a request to the Decentralized Oracle Network to compute the average (geometric mean). Run the functions-request task with the subid (subscription ID) and contract parameters. This task will pass the functions JavaScript source code and any arguments and secrets when calling the FunctionsConsumer's executeRequest function. Read the functionsConsumer section for more details about the consumer 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/1-simple-computation/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.000472360505038512 MATIC, which (using mainnet value) is $0.0005261541947256613

If the request's callback uses all 100,000 gas, this request will charge the subscription:
0.200148503810857266 LINK

Continue? Enter (y) Yes / (n) No
y
Simulating Functions request locally...

__Console log messages from sandboxed code__
calculate geometric mean of 1,2,3,4,5,6,7,8,9,10
geometric mean is: 4.53

__Output from sandboxed source code__
Output represented as a hex string: 0x00000000000000000000000000000000000000000000000000000000000001c5
Decoded as a uint256: 453

⣾ Request 0xa89547c0270c1a5135ec7a47b9110db495facd811493832f54cc4c0b729b7786 has been initiated. Waiting for fulfillment from the Decentralized Oracle Network...

ℹ Transaction confirmed, see https://mumbai.polygonscan.com/tx/0xa35ac46ec20a258cd9ffcc4a4396b998e3a60343a936009fb5fc76550c780d7f for more details.

Actual amount billed to subscription #443:
┌──────────────────────┬────────────────────────────┐
│         Type         │           Amount           │
├──────────────────────┼────────────────────────────┤
│  Transmission cost:  │  0.00006493686812304 LINK  │
│      Base fee:       │          0.2 LINK          │
│                      │                            │
│     Total cost:      │  0.20006493686812304 LINK  │
└──────────────────────┴────────────────────────────┘


✔ Request 0xa89547c0270c1a5135ec7a47b9110db495facd811493832f54cc4c0b729b7786 fulfilled! Data has been written on-chain.

Response returned to client contract represented as a hex string: 0x00000000000000000000000000000000000000000000000000000000000001c5
Decoded as a uint256: 453

The output of the example gives you the following information:

  • The executeRequest function was successfully called in the FunctionsConsumer contract. The transaction in this example is 0xa35ac46ec20a258cd9ffcc4a4396b998e3a60343a936009fb5fc76550c780d7f.
  • The request ID is 0xa89547c0270c1a5135ec7a47b9110db495facd811493832f54cc4c0b729b7786.
  • The DON successfully fulfilled your request. The total cost was: 0.20006493686812304 LINK.
  • The consumer contract received a response in bytes with a value of 0x00000000000000000000000000000000000000000000000000000000000001c5. Decoding it off-chain to uint256 give you a result: 453.

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/1-simple-computation/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: 0x00000000000000000000000000000000000000000000000000000000000001c5
Decoded as a uint256: 453

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 and latestError are encoded as dynamically sized byte array bytes, 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 callback

    event 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 Functionslibrary 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 in latestRequestId.

        bytes32 assignedReqID = sendRequest(req, subscriptionId, gasLimit);
        latestRequestId = assignedReqID;
    • 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 latestResponse and latestError before emitting the OCRResponse 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. That's why we use fs.readFileSync to read source.js and then call toString() to get the content as a string object.
  • args: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]: These arguments are passed to the source code. This example computes the average(geometric mean) of 1,2,3,4,5,6,7,8,9,10.
  • expectedReturnType: ReturnType.uint256: The response received by the DON is encoded in bytes. Because the average (geometric mean) is a uint256, define ReturnType.uint256 to inform users how to decode the response received by the DON.

source.js

Read the JavaScript code section for a detailed explanation of how to write compatible JavaScript source code. The code is self-explanatory and has comments to help you understand all the steps. 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.
  • 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. Note: Read this article if you are new to Javascript Buffers and want to understand why they are important.

It is important to note that the custom source code you want to execute in a Functions request can use vanilla Node.js features, but cannot use any require statements or imported modules other than the built-in modules buffer, crypto, querystring, string_decoder, url, and util.

Stay updated on the latest Chainlink news