Automate your Functions
This tutorial shows you how to use Chainlink Automation to automate your Chainlink Functions. Automation is essential when you want to trigger the same function regularly, such as fetching weather data daily or fetching an asset price on every block.
Read the API multiple calls tutorial before you follow the steps in this example. This tutorial uses the same example but with an important difference:
- You will deploy AutomatedFunctionsConsumer.sol instead of FunctionsConsumer.sol.
AutomatedFunctionsConsumer.sol
is a Chainlink Functions Consumer contract that is Chainlink Automation compatible. After you deploy and set up your contract, Chainlink Automation triggers your function according to a time schedule.
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 this tutorial.
-
Make sure to understand the API multiple calls guide.
-
Make sure your subscription has enough LINK to pay for your requests. See the Get Subscription Details page to learn how to check your subscription balance. If your subscription runs out of LINK, follow the Fund a Subscription guide.
-
Check out the correct branch before you try this tutorial: Each tutorial is stored in a separate branch of the Chainlink Functions Starter Kit repository.
git checkout tutorial-6
-
Get a free API key from CoinMarketCap.
-
Open your
.env
file. -
Add a line to the
.env
file with theCOINMARKETCAP_API_KEY=
variable and set it to your API key. For example:COINMARKETCAP_API_KEY="78143127-fe7e-d5fe-878f-143notarealkey"
-
Save your
.env
file.
Tutorial
This tutorial is configured to get the median BTC/USD
price from multiple data sources according to a time schedule. For a detailed explanation of the code example, read the Explanation section.
- Open
Functions-request-config.js
. Note theargs
value is["1", "bitcoin", "btc-bitcoin"]
. These arguments are BTC IDs at CoinMarketCap, CoinGecko, and Coinpaprika. You can adaptargs
to fetch other asset prices. See the API docs for CoinMarketCap, CoinGecko, and CoinPaprika for details. For more information about the request, read the request config section. - Open
Functions-request-source.js
to analyze the JavaScript source code. Read the source code explanation for a more detailed explanation of the request source 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 functions to the Decentralized Oracle Network.
Run the functions-simulate
task to run the source code locally and make sure Functions-request-config.js
and Functions-request-source.js
are correctly written:
npx hardhat functions-simulate
Example:
$ npx hardhat functions-simulate
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__
Median Bitcoin price: $24821.05
__Output from sandboxed source code__
Output represented as a hex string: 0x000000000000000000000000000000000000000000000000000000000025dfb9
Decoded as a uint256: 2482105
__Simulated On-Chain Response__
Response returned to client contract represented as a hex string: 0x000000000000000000000000000000000000000000000000000000000025dfb9
Decoded as a uint256: 2482105
Gas used by sendRequest: 392690
Gas used by client callback function: 75029
Reading the output of the example above, you can note that the BTC/USD
median price is: 24821.05 USD. Because Solidity does not support decimals, we move the decimal point so that the value looks like the integer 2482105
before returning the bytes
encoded value 0x000000000000000000000000000000000000000000000000000000000025dfb9
in the callback. Read the source code explanation for a more detailed explanation.
Deploy an Automation Consumer contract
After running the simulator and confirming that your Function runs without issues, run the functions-deploy-auto-client
command. This command does the following:
- Deploy the AutomatedFunctionsConsumer.sol contract. You can set the interval of executions when deploying the contract.
- Add the deployed contract to your subscription.
- Simulate the request that is stored in your deployed contract.
- Store the request, which includes the source code, encrypted secrets, and arguments in the contract storage. Note: The stored request is sent to the DON according to the provided time interval.
In your terminal, run the functions-deploy-auto-client
command:
npx hardhat functions-deploy-auto-client --network REPLACE_NETWORK --subid REPLACE_SUBSCRIPTION_ID --interval REPLACE_INTERVAL_SECONDS
Example:
$ npx hardhat functions-deploy-auto-client --network mumbai --subid 10 --interval 60
secp256k1 unavailable, reverting to browser version
Deploying AutomatedFunctionsConsumer contract to mumbai
__Compiling Contracts__
Nothing to compile
Waiting 1 block for transaction 0xe008c95a9e2643ad41e0b7edc9d6b59862a58fdde7db5beff411f4ddd43efdec to be confirmed...
Adding consumer contract address 0x7a2499dd81D40d12104Af556440099611E675E02 to subscription 10
Waiting 2 blocks for transaction 0x0f989ae76f66e6f68eaa60563140dea66558e47c6ab66d55c11093ff25213ecb to be confirmed...
Added consumer contract address 0x7a2499dd81D40d12104Af556440099611E675E02 to subscription 10
3 authorized consumer contracts for subscription 10:
[
'0xED9eeB56CEA17aFe7F6299da446aF0963bE82701',
'0xf23c01Ac45682295C9369a5Cd7dd7E3961C14d5c',
'0x7a2499dd81D40d12104Af556440099611E675E02'
]
Setting the Functions request in AutomatedFunctionsConsumer contract 0x7a2499dd81D40d12104Af556440099611E675E02 on mumbai
Simulating Functions request locally...
__Console log messages from sandboxed code__
Median Bitcoin price: $24438.00
__Output from sandboxed source code__
Output represented as a hex string: 0x0000000000000000000000000000000000000000000000000000000000254a18
Decoded as a uint256: 2443800
Setting Functions request
Waiting 2 block for transaction 0xfb0e8658e702c5783570c7f87f8fc86cda8d15988769fa491fe3b1f4dc4843cf to be confirmed...
Set new Functions request in AutomatedFunctionsConsumer contract 0x7a2499dd81D40d12104Af556440099611E675E02 on mumbai
AutomatedFunctionsConsumer contract deployed to 0x7a2499dd81D40d12104Af556440099611E675E02 on mumbai
In the example above, you deployed a Chainlink Functions consumer contract and configured it to get the median bitcoin price every 60 seconds.
Note: You can change the request’s parameters to build a new request or update other parameters, such as the time interval or the subscription ID to which your contract is linked. To do so, run the functions-set-auto-request
:
npx hardhat functions-set-auto-request --contract REPLACE_YOUR_CONTRACT --network REPLACE_NETWORK --subid REPLACE_SUBSCRIPTION_ID --interval REPLACE_INTERVAL_SECONDS --gaslimit REPLACE_GAS_LIMITS
The --interval
and --gaslimit
flags are optional.
As an example, you can use this command to set a new interval of one hour:
npx hardhat functions-set-auto-request --contract 0x7a2499dd81D40d12104Af556440099611E675E02 --network mumbai --subid 10 --interval 3600
Configure Chainlink Automation
The consumer contract that you deployed is designed to be used with a custom logic upkeep. Follow the instructions in the Registering an Upkeep guide to register your deployed contract using the Chainlink Automation App. Use the following upkeep settings:
- Trigger: Custom logic
- Target contract address: The address of the Chainlink Functions consumer contract that you deployed
- Gas limit: 700000
- Starting balance (LINK): 1
You can leave the other settings at their default values for the example in this tutorial.
Chainlink Automation will trigger sending the request according to your provided time interval.
Check Result
Go to the Chainlink Automation App and connect to Polygon Mumbai. Your upkeep will be listed under My upkeeps:
Click on your upkeep to fetch de details:
As you can see in the History table, the upkeep is running every minute.
On your terminal, 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
Example:
$ npx hardhat functions-read --contract 0x7a2499dd81D40d12104Af556440099611E675E02 --network mumbai
secp256k1 unavailable, reverting to browser version
Reading data from Functions client contract 0x7a2499dd81D40d12104Af556440099611E675E02 on network mumbai
On-chain response represented as a hex string: 0x0000000000000000000000000000000000000000000000000000000000246310
Decoded as a uint256: 2384656
Explanation
AutomatedFunctionsConsumer.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 "./dev/functions/FunctionsClient.sol";
To create a Chainlink Automation contract, your contract must import AutomationCompatible.sol
import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";
Import ConfirmedOwner from the @chainlink/contracts NPM package. This contract includes functions to set up the owner, transfer ownership, and a function modifier onlyOwner
that restricts certain functions to the contract owner.
Your contract must inherit FunctionsClient
and ConfirmedOwner
contracts, and implement the AutomationCompatibleInterface
interface.
contract AutomatedFunctionsConsumer is FunctionsClient, ConfirmedOwner, AutomationCompatibleInterface
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 request sent to Chainlink Functions is defined as a state variable. The contract owner stores this request, which is regularly sent to Chainlink Functions whenever Chainlink Automation triggers it.
bytes public requestCBOR
The latest request ID, latest received response, and latest received error (if any) are defined as state variables. Note that 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;
The subscription ID (the subscription account your contract is linked to) and the fulfillment gas limit (Maximum amount of gas used to fulfill a Chainlink Function request) are defined as state variables. Only the contract owner can modify these variables.
uint64 public subscriptionId;
uint32 public fulfillGasLimit;
The update interval (time interval in seconds of triggering a Chainlink Function request) and the timestamp of the last request are defined as state variables. They are used to check if the time interval has been reached so that Chainlink Automation can trigger a new request.
uint256 public updateInterval;
uint256 public lastUpkeepTimeStamp;
Define upkeepCounter
and responseCounter
to keep track of the number of requests (triggered by Chainlink Automation) and the number of fulfilled requests (once the request has been fulfilled by Chainlink Functions).
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, your Chainlink functions ID, fulfillment gas limit, and update interval when you deploy the contract:
constructor(
address oracle,
uint64 _subscriptionId,
uint32 _fulfillGasLimit,
uint256 _updateInterval
) FunctionsClient(oracle) ConfirmedOwner(msg.sender)
You can change the oracle address at any time by calling the updateOracleAddress
function.
To store a request in the requestCBOR
state variable, the contract owner has to:
-
Call the
generateRequest
function. It uses theFunctions
library to initialize the request and add any passed encrypted secrets or arguments. Finally, it returns an encoded request. You can read the API Reference for Initializing a request, adding secrets, and adding arguments. Note: This call is done off-chain to save gas when calling thesetRequest
function.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); return req.encodeCBOR();
-
Call the
setRequest
function and pass the subscription ID, fulfillment gas limit, update interval, and the encoded request (returned bygenerateRequest
).setRequest
updates theupdateInterval
,subscriptionId
,fulfillGasLimit
, andrequestCBOR
state variables.
checkUpkeep
and performUpkeep
functions are used by Chainlink Automation:
-
checkUpkeep
: Returns a boolean which determines whether Chainlink Automation can trigger theperformUpkeep
function. The boolean is set to true whenever the time interval is met.upkeepNeeded = (block.timestamp - lastUpkeepTimeStamp) > updateInterval;
-
performUpkeep
: Sends the request to the oracle by calling theFunctionsClient
sendRequest
function. You can read the API reference for sending a request. Also, it updateslastUpkeepTimeStamp
with the current timestamp, incrementsupkeepCounter
, and setslatestRequestId
to the last request ID.lastUpkeepTimeStamp = block.timestamp; upkeepCounter = upkeepCounter + 1; ... bytes32 requestId = s_oracle.sendRequest( subscriptionId, requestCBOR, fulfillGasLimit ); ... latestRequestId = assignedReqID;
fulfillRequest
is invoked by Chainlink Functions 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
and increments responseCounter
before emitting the OCRResponse
event.
latestResponse = response;
latestError = err;
responseCounter = responseCounter + 1;
emit OCRResponse(requestId, response, err);
Functions-request-config.js
See the explanation for the Call Multiple Data Sources tutorial.
Functions-request-source.js
See the explanation for the Call Multiple Data Sources tutorial.