NEW

The Chainlink Hackathon kicks off April 28th! Register today to compete for $350K+ in prizes.

Adding Chainlink Functions to an Existing Project

The Chainlink Functions Starter Kit lets you run several example requests, but extra steps are required to add Chainlink Functions to your existing projects.

If you are new to Chainlink Functions, complete the steps in the Getting Started Guide to learn the basics. If you already have a project, you can skip to the Libraries and Dependencies section.

Using Chainlink Functions to your existing projects requires the following components:

  • A consumer contract: Use this contract to send requests to the Chainlink Functions decentralized oracle network (DON). The consumer contract imports the following dependencies:
  • A Chainlink Functions subscription: The subscription is used to pay for Chainlink Functions requests when they are fulfilled. You can create and fund subscriptions using the starter kit tools, but this guide shows you how to manage subscriptions programmatically.

Before you begin

This guide assumes you are using a Hardhat JavaScript project with Node.js, but you can modify it to work with other frameworks. If you already have a project, you can skip to the Libraries and Dependencies section.

You must have a wallet with testnet LINK and native tokens to run this example. This example uses Polygon Mumbai, but you can use any of the Supported Networks. You can get testnet LINK at faucets.chain.link and testnet MATIC at the Polygon Faucet. To learn how to get testnet funds for other networks, see the LINK Token Contracts page.

Configure Hardhat

If you don’t already have a Hardhat project, start one with the following steps:

  1. Install Node.js.

  2. Make a new project directory:

    mkdir new-functions-project && cd new-functions-project
  3. Create a new Node.js project:

    npm init
  4. Install Hardhat as a dev dependency:

    npm install --save-dev hardhat
  5. Install the dependency packages:

    npm install @chainlink/contracts @openzeppelin/contracts @openzeppelin/contracts-upgradeable eth-crypto dotenv axios vm2 is-http-url prompt-sync
  6. Create a new Hardhat project. For this example, create a JavaScript project and use the default configurations:

    npx hardhat
  7. Remove the Lock.sol contract that Hardhat creates by default. This contract is not necessary to compile it for this tutorial.

    rm ./contracts/Lock.sol

Modify hardhat.config.js to include the 0.8.7 compiler version and supported networks. See the Supported Networks page for a list of networks where Chainlink Functions DONs are available. This example uses Polygon Mumbai by default:

require("@nomiclabs/hardhat-ethers")
require("dotenv").config()

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  defaultNetwork: "mumbai",
  networks: {
    hardhat: {
      // // If you want to do some forking, uncomment this
      // forking: {
      //   url: MAINNET_RPC_URL
      // }
    },
    localhost: {},
    mumbai: {
      url: process.env.MUMBAI_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      saveDeployments: true,
    },
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      saveDeployments: true,
    },
  },
  solidity: {
    compilers: [
      {
        version: "0.8.7",
        settings: {
          optimizer: {
            enabled: true,
            runs: 1_000,
          },
        },
      },
      {
        version: "0.6.6",
        settings: {
          optimizer: {
            enabled: true,
            runs: 1_000,
          },
        },
      },
      {
        version: "0.4.24",
        settings: {
          optimizer: {
            enabled: true,
            runs: 1_000,
          },
        },
      },
    ],
  },
}

If you use this configuration with require('dotenv').config() to get your RPC URLs and private keys, you must also create a .env file with these variables set. Add your RPC URLs with your API keys and the private key to the wallet you will use to fund transactions on these testnets.

touch .env && open .env
# RPC URLs
MUMBAI_RPC_URL="https://polygon-mumbai.infura.io/v3/your_api_key"
SEPOLIA_RPC_URL="https://sepolia.infura.io/v3/your_api_key"

# Wallet private key
PRIVATE_KEY="your_private_key"

Run the compile command for Hardhat or your framework of choice to make sure everything is configured correctly. You will see several compile warnings, but everything is correct if the Solidity files compile successfully. For this example, you can run npx hardhat compile.

Get the dependency contracts and scripts

Get the required libraries from the Chainlink Functions Starter Kit. You can clone the repo and copy the folders into your existing project manually, or use a single command to get a tarball through the GitHub API and extract the folders you need to the correct location in your project.

  1. Open a terminal and change directories to the root of your project. Usually this is the folder with package.json.

  2. Run the curl command to download the latest tarball from the starter kit main branch and then run tar -xf with the --strip flag to extract only the files you need. The following command combines both steps:

    curl -L -o ../functions.tar.gz https://api.github.com/repos/smartcontractkit/functions-hardhat-starter-kit/tarball/main &&
    tar -xf ../functions.tar.gz --strip=1 --wildcards smartcontractkit-functions-hardhat-starter-kit-*/contracts/dev smartcontractkit-functions-hardhat-starter-kit-*/contracts/test smartcontractkit-functions-hardhat-starter-kit-*/FunctionsSandboxLibrary smartcontractkit-functions-hardhat-starter-kit-*/contracts/FunctionsConsumer.sol

When you are done, you should have the necessary dependencies in the following directories:

  • contracts/dev
  • contracts/test
  • FunctionsSandboxLibrary

Run the compile command to make sure all the dependencies are satisfied. Add missing dependencies or Hardhat configuration options as necessary. For Hardhat, run npx hardhat compile.

Configure on-chain resources

The on-chain resources are critical for Chainlink Functions to process your requests.

  1. Create a consumer contract
  2. Deploy a consumer contract

Create a consumer contract

Use the FunctionsConsumer.sol, which is already in your ./contracts folder. You can modify it to fit your needs and redeploy it later. Optionally, you can take an existing contract of your own and enable it to handle Chainlink Functions requests. Just make sure that it meets the requirements listed in this guide.

In general, a consumer contract requires several components:

  • 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 "./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) {
          if (secretsLocation == Functions.Location.Inline) {
             req.addInlineSecrets(secrets);
          } else {
            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);

Next, deploy the contract.

Deploy a consumer contract

You can deploy consumer contracts using the Chainlink Functions Starter Kit, but this example shows how to deploy the contract programmatically using a script.

  1. Create a script named deploy.js in the ./scripts folder of your project:

    touch ./scripts/deploy.js && open ./scripts/deploy.js
  2. Copy the following code into the script.

    async function main() {
      // The oracle address on Polygon Mumbai
      // See https://docs.chain.link/chainlink-functions/supported-networks
      // for a list of supported networks and addresses.
      const oracleAddress = "0xeA6721aC65BCeD841B8ec3fc5fEdeA6141a0aDE4"
    
      // Set your contract name.
      const contractName = "FunctionsConsumer"
      //const contractName = "MyFirstContract"
    
      const [deployer] = await ethers.getSigners()
    
      console.log("Deploying contracts with the account:", deployer.address)
    
      console.log("Account balance:", (await deployer.getBalance()).toString())
    
      const consumerContract = await ethers.getContractFactory(contractName)
    
      const deployedContract = await consumerContract.deploy(oracleAddress)
    
      console.log("Deployed Functions Consumer address:", deployedContract.address)
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error)
        process.exit(1)
      })
    
  3. Set the oracleAddress to the oracle address on the network that you want to use. Each network has a unique DON with a unique oracle address. See the Supported Networks page for a list of supported networks and oracle addresses. For this example, use the address for the oracle on Polygon Mumbai:

    const oracleAddress = "0xeA6721aC65BCeD841B8ec3fc5fEdeA6141a0aDE4"
  4. Set const contractName to the name of the contract that you want Hardhat to deploy. The ethers.getContractFactory(contractName); line creates a ContractFactory object using the contract that you define. For this example, use FunctionsConsumer.

    const contractName = "FunctionsConsumer"const consumerContract = await ethers.getContractFactory(contractName);
  5. Save the file and run the script to deploy your contract. Include the --network flag to use a network other than the default in the Hardhat config:

    npx hardhat run ./scripts/deploy.js --network your_network
  6. If your contract deployed successfully, you will see the deployed consumer address. Record this address to use later:

    Deployed Functions Consumer address: 0xE83188aDF26bd6c18076704fC8EC68CBda454f76

Next, create and fund your Chainlink Functions subscription.

Create and fund a subscription

You can use the Chainlink Functions Starter Kit to create and manage your subscriptions. See Managing Subscriptions for instructions.

This example shows how to create and manage subscriptions programmatically. You can create the subscription, fund the subscription, and authorize the consumer all in one script. If you have not already signed up for limited Beta access to Chainlink Functions, apply here to add your EVM account address to the Allow List.

  1. Ensure that the wallet address you are using to create the subscription has a sufficient LINK balance. You can get testnet LINK at faucets.chain.link. To get testnet funds for other networks, see the LINK Token Contracts page.

  2. Create a script named functions-sub.js in the ./scripts folder of your project:

    touch ./scripts/functions-sub.js && open ./scripts/functions-sub.js
  3. Copy the following code into the script:

    async function main() {
      // 1 LINK is sufficient for this example
      const linkAmount = "1"
      // Set your consumer contract address. This contract will
      // be added as an approved consumer of the subscription.
      const consumer = "your_consumer_address"
    
      // Network-specific configs
      // Polygon Mumbai LINK 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
      // See https://docs.chain.link/resources/link-token-contracts
      // to find the LINK token contract address for your network.
      const linkTokenAddress = "0x326C977E6efc84E512bB9C30f76E30c160eD06FB"
      // Polygon Mumbai billing registry: 0xEe9Bf52E5Ea228404bB54BCFbbDa8c21131b9039
      // See https://docs.chain.link/chainlink-functions/supported-networks
      // for a list of supported networks and registry addresses.
      const functionsBillingRegistryProxy = "0xEe9Bf52E5Ea228404bB54BCFbbDa8c21131b9039"
    
      const RegistryFactory = await ethers.getContractFactory(
        "contracts/dev/functions/FunctionsBillingRegistry.sol:FunctionsBillingRegistry"
      )
      const registry = await RegistryFactory.attach(functionsBillingRegistryProxy)
    
      const createSubscriptionTx = await registry.createSubscription()
      const createSubscriptionReceipt = await createSubscriptionTx.wait(1)
      const subscriptionId = createSubscriptionReceipt.events[0].args["subscriptionId"].toNumber()
      console.log(`Subscription created with ID: ${subscriptionId}`)
    
      //Get the amount to fund, and ensure the wallet has enough funds
      const juelsAmount = ethers.utils.parseUnits(linkAmount)
      const LinkTokenFactory = await ethers.getContractFactory("LinkToken")
      const linkToken = await LinkTokenFactory.attach(linkTokenAddress)
    
      const accounts = await ethers.getSigners()
      const signer = accounts[0]
    
      // Check for a sufficent LINK balance to fund the subscription
      const balance = await linkToken.balanceOf(signer.address)
      if (juelsAmount.gt(balance)) {
        throw Error(`Insufficent LINK balance`)
      }
    
      console.log(`Funding with ` + juelsAmount + ` Juels (1 LINK = 10^18 Juels)`)
      const fundTx = await linkToken.transferAndCall(
        functionsBillingRegistryProxy,
        juelsAmount,
        ethers.utils.defaultAbiCoder.encode(["uint64"], [subscriptionId])
      )
      await fundTx.wait(1)
      console.log(`Subscription ${subscriptionId} funded with ${juelsAmount} Juels (1 LINK = 10^18 Juels)`)
    
      //Authorize deployed contract to use new subscription
      console.log(`Adding consumer contract address ${consumer} to subscription ${subscriptionId}`)
      const addTx = await registry.addConsumer(subscriptionId, consumer)
      await addTx.wait(1)
      console.log(`Authorized consumer contract: ${consumer}`)
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error)
        process.exit(1)
      })
    
  4. Set const linkAmount with the amount of LINK you want to send to the subscription. You can retrieve extra funds later when you cancel the subscription.

    const linkAmount = "1"
  5. Set const consumer to the address of the consumer contract that you deployed:

    const consumer = "0xE83188aDF26bd6c18076704fC8EC68CBda454f76"
  6. Save the file and run the script. Include the --network flag to use a network other than the default in the Hardhat config:

    npx hardhat run scripts/functions-sub.js --network your_network
  7. If the script is successful, you the terminal prints your subscription ID. Record this ID to use for Chainlink Functions requests. You should see output similar to the following example:

    Subscription created with ID: 1337
    Duplicate definition of Transfer (Transfer(address,address,uint256,bytes), Transfer(address,address,uint256))
    Funding with 1000000000000000000 LINK
    Subscription 1337 funded with 1000000000000000000 LINK
    Adding consumer contract address 0xE83188aDF26bd6c18076704fC8EC68CBda454f76 to subscription 1337
    Authorized consumer contract: 0xE83188aDF26bd6c18076704fC8EC68CBda454f76

Now that the consumer contract is deployed and the subscription is created and funded with LINK, the on-chain resources are ready to handle your requests. Next, create JavaScript code that you want to run on the DON, configure arguments for the code, and create a script to send your request.

Send requests

After your on-chain resources are configured, you can send Chainlink Functions requests to the DON. This can be done from a Web3 application, script, another on-chain smart contract, or any other location capable of submitting requests to your consumer contract. This example uses Ethers v6 without Hardhat to show you how to generate a request, encrypt secrets, send your request, and read the fulfillment response.

Each request has the following components:

  • Source code: JavaScript code that will run on the DON.
  • Arguments: Optional arguments for the source code. The arguments that you need are defined in your source code. Depending on how you configured your source, you might not need any arguments at all.
  • Secrets: Optional secrets that your source code needs to access APIs or other interfaces. Secrets can be included inline with your request or hosted off-chain. See the Using Secrets in Requests and Using Off-chain Secrets tutorials for examples.
  • Secrets location: This tells the DON where to obtain your secrets.

Create a request script

If you already have source code or want to write your own source code, put it in a file in your project. Later, you can specify the path to this file before you submit your request.

For this example, download some example source code and create a script to assemble the required components. The script will read your source code, define arguments, encrypt secrets, and send requests to your consumer contract. This example script does not require Hardhat, so you can modify it to run in a browser using Ethers or another framework.

  1. Use curl to get the example source code. This code runs on each node in the DON and returns a response to your consumer contract. For this example, use the source from the Call an API tutorial. The following curl request creates a file named Functions-request-source.js with the source code:

    curl -o Functions-request-source.js https://raw.githubusercontent.com/dwightjl/functions-examples/main/Functions-request-source.js
  2. Get the example script and put it in the ./scripts directory:

    curl -o ./scripts/request.js https://raw.githubusercontent.com/dwightjl/functions-examples/main/scripts/request.js
  3. Edit the script and set your deployed consumer address in const consumerAddress:

    const consumerAddress = 0xe83188adf26bd6c18076704fc8ec68cbda454f76
  4. Set const subscriptionId to your Chainlink Functions subscription ID. This must be the same ID that your consumer contract is authorized to use. You can add a consumer contract to a subscription later if you need to.

    const subscriptionId = 1337
  5. Set const consumerAbiPath to the ABI file that you created when you ran npx hardhat compile. The ABI tells Ethers how to interact with your deployed contract. If you compiled and deployed FunctionsConsumer.sol, set a path like the following example:

    const consumerAbiPath = "./artifacts/contracts/FunctionsConsumer.sol/FunctionsConsumer.json"
  6. Set const source with the path to the request source code that you downloaded earlier:

    const source = ./Functions-request-source.js
  7. Set const args with an array of arguments for the DON to use when it runs your source. If you look at the Functions-request-source.js file, you can see that it requires two arguments. The args define which assets to retrieve from the data source URL.

    const args = ["ETH", "USD"]
  8. Set const oracleAddress to the oracle address on the network that you want to use. Each network has a unique DON with a unique oracle address. See the Supported Networks page for a list of supported networks and oracle addresses. For this example, use the address for the oracle on Polygon Mumbai:

    const oracleAddress = "0xeA6721aC65BCeD841B8ec3fc5fEdeA6141a0aDE4"
  9. Set const oracleAbiPath to the ABI for the oracle contract. If you downloaded and compiled the dependencies for deploying your consumer contract, the oracle ABI is already generated in ./artifacts/contracts/dev/functions/:

    const oracleAbiPath = "./artifacts/contracts/dev/functions/FunctionsOracle.sol/FunctionsOracle.json"
  10. Secrets are not required for this example, but if you need them for a future example you can specify them in const secrets. You can set inline secrets or off-chain secrets in an array of URLs. Both options are encrypted before you send them to the DON. Note that the encryption process for off-chain secrets is different from inline secrets. The script takes care of either type. You must also tell the DON where to look for your secrets with const secretsLocation. Set 0 for inline or 1 for off-chain.

  11. Save and close the script.

  12. Run the script to send the request to the DON. Because this script does not require Hardhat, you can use node.

    node ./scripts/request.js

If the script runs successfully, the script reads your consumer contract and prints the stored value that the DON returned.

Waiting for fulfillment...

Stored value is: 164144

Now you have the tools you need to build your own applications that use Chainlink Functions. Modify Functions-request-source.js and your input arguments to try out different capabilities. For more examples, see the Tutorials section.

What's next

Stay updated on the latest Chainlink news