Secure Upkeeps Using the Forwarder

This tutorial explains how to use the Forwarder to add additional security to your Automation upkeeps. To learn how other Chainlink Automation contracts work, click here.

What is a Forwarder? When is it used?

Each registered upkeep under the Chainlink Automation network has its own unique Forwarder contract. The Forwarder address becomes available only after upkeep registration, as we deploy a new forwarder for each upkeep. The Forwarder contract is the intermediary between the Automation Registry and your Upkeep contract, so you make it the msg.Sender for your upkeep.

If you don't use the forwarder address, your contract's performUpkeep function is open and callable by anyone. If your contract is without risk of accepting unintentional external data, you don't need to use the forwarder address.

Securing your upkeep

If your upkeep's perform function needs to be permissioned, please consider setting msg.sender as your forwarder address at the top of your performUpkeep function.

To make this work you will need to:

  • Create forwarder as a mutable address variable on your contract that only you can update. forwarder is a unique value that cannot change for your upkeep.
  • Create a setForwarder function in your contract so you can update the forwarder address.
  • Register your upkeep and then retrieve its forwarder address from the Chainlink Automation App or programmatically.
  • Call the setForwarder function, passing the forwarder address as an input argument.

Finding the forwarder address

After you register an upkeep, its forwarder address is available in the Chainlink Automation App. Alternatively, you can fetch it programmatically using registry.getForwarder(upkeepID) from the Registry interface.

For time-based upkeeps, do not use the listed forwarder address. Instead, use the Upkeep address shown in the Chainlink Automation App:

Upkeep address in Chainlink Automation App

The Upkeep address is shown in the middle card of the Details section for your upkeep.

Code example

The code sample below uses the Forwarder:

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

/**
 * @dev Example contract which uses the Forwarder
 *
 * @notice important to implement {AutomationCompatibleInterface}
 */

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

import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/interfaces/AutomationCompatibleInterface.sol";
import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol";

contract CounterwForwarder is AutomationCompatibleInterface, OwnerIsCreator {
    uint256 public counter; // counter counts the number of upkeeps performed
    uint256 public interval; // interval specifies the time between upkeeps
    uint256 public lastTimeStamp; // lastTimeStamp tracks the last upkeep performed
    address public s_forwarderAddress;

    constructor(uint256 updateInterval) {
        interval = updateInterval;
    }

    function checkUpkeep(
        bytes calldata /*checkData*/
    ) external override returns (bool, bytes memory) {
        bool needsUpkeep = (block.timestamp - lastTimeStamp) > interval;
        return (needsUpkeep, bytes(""));
    }

    function performUpkeep(bytes calldata /*performData*/) external override {
        require(
            msg.sender == s_forwarderAddress,
            "This address does not have permission to call performUpkeep"
        );
        lastTimeStamp = block.timestamp;
        counter = counter + 1;
    }

    /// @notice Set the address that `performUpkeep` is called from
    /// @dev Only callable by the owner
    /// @param forwarderAddress the address to set
    function setForwarderAddress(address forwarderAddress) external onlyOwner {
        s_forwarderAddress = forwarderAddress;
    }
}

What's next

Get the latest Chainlink content straight to your inbox.