Forwarder tutorial

In this tutorial, you will configure your Chainlink node with a simple transaction-sending strategy on the Sepolia testnet:

  • Your node has two externally owned accounts (EOA).
  • Your node has two direct request jobs. One job returns uint256, and the other returns string.
  • Each job uses a different EOA.
  • You use a forwarder contract to fulfill requests with two EOAs that look like a single address.

On your terminal, check that your Chainlink node is running:

 docker ps -a -f name=chainlink

 CONTAINER ID   IMAGE                            COMMAND               CREATED         STATUS                   PORTS                    NAMES
 feff39f340d6   smartcontract/chainlink:1.12.0   "chainlink local n"   4 minutes ago   Up 4 minutes (healthy)   0.0.0.0:6688->6688/tcp   chainlink

Your Chainlink Operator Interface is accessible on http://localhost:6688. If using a VPS, you can create a SSH tunnel to your node for 6688:localhost:6688 to enable connectivity to the GUI. Typically this is done with ssh -i $KEY $USER@$REMOTE-IP -L 6688:localhost:6688 -N. A SSH tunnel is recommended over opening up ports specific to the Chainlink node to be public facing. See the Security and Operation Best Practices page for more details on how to secure your node.

If you don't have a running Chainlink node, follow the Running a Chainlink Node Locally guide.

Set up multiple EOAs

  1. Access the shell of your Chainlink node container:

    docker exec -it chainlink /bin/bash
    
    chainlink@1d095e4ceb09:~$
    
  2. You can now log in by running:

    chainlink admin login
    

    You will be prompted to enter your API email and password. If successful, the prompt will appear again.

  3. Check the number of available EOA:

    chainlink keys eth list
    

    You should see one EOA:

    šŸ”‘ ETH keys
    -------------------------------------------------------------------------------------------------
    Address:           0x71a1Eb6534054E75F0D6fD0A3B0A336228DD5cFc
    EVM Chain ID:      11155111
    Next Nonce:        0
    ETH:               0.000000000000000000
    LINK:              0
    Disabled:          false
    Created:           2023-03-02 09:28:26.872791 +0000 UTC
    Updated:           2023-03-02 09:28:26.872791 +0000 UTC
    Max Gas Price Wei: 115792089237316195423570985008687907853269984665640564039457584007913129639935chainlink@22480bec8986
    
  4. Create a new EOA for your Chainlink node:

    chainlink keys eth create
    
    ETH key created.
    
    šŸ”‘ New key
    -------------------------------------------------------------------------------------------------
    Address:           0x259c49E65644a020C2A642260a4ffB0CD862cb24
    EVM Chain ID:      11155111
    Next Nonce:        0
    ETH:               0.000000000000000000
    LINK:              0
    Disabled:          false
    Created:           2023-03-02 11:36:48.717074 +0000 UTC
    Updated:           2023-03-02 11:36:48.717074 +0000 UTC
    Max Gas Price Wei: 115792089237316195423570985008687907853269984665640564039457584007913129639935
    
  5. At this point, there are two EOAs:

    chainlink keys eth list
    
    šŸ”‘ ETH keys
    -------------------------------------------------------------------------------------------------
    Address:           0x259c49E65644a020C2A642260a4ffB0CD862cb24
    EVM Chain ID:      11155111
    Next Nonce:        0
    ETH:               0.000000000000000000
    LINK:              0
    Disabled:          false
    Created:           2023-03-02 11:36:48.717074 +0000 UTC
    Updated:           2023-03-02 11:36:48.717074 +0000 UTC
    Max Gas Price Wei: 115792089237316195423570985008687907853269984665640564039457584007913129639935
    -------------------------------------------------------------------------------------------------
    Address:           0x71a1Eb6534054E75F0D6fD0A3B0A336228DD5cFc
    EVM Chain ID:      11155111
    Next Nonce:        0
    ETH:               0.000000000000000000
    LINK:              0
    Disabled:          false
    Created:           2023-03-02 09:28:26.872791 +0000 UTC
    Updated:           2023-03-02 09:28:26.872791 +0000 UTC
    Max Gas Price Wei: 115792089237316195423570985008687907853269984665640564039457584007913129639935chainlink@22480bec8986
    
  6. Fund the two addresses with 0.5 Sepolia ETH each. You can obtain testnet ETH from the faucets listed on the Link Token Contracts page.

  7. Note the two addresses, as you will need them later.

Deploy operator and forwarder

Use the operator factory to deploy both the forwarder and the operator contracts. You can find the factory address for each network on the addresses page.

  1. Open contract 0x447Fd5eC2D383091C22B8549cb231a3bAD6d3fAf to display the factory in the Sepolia block explorer.

  2. Click the Contract tab. Then, click Write Contract to display the write transactions on the factory.

  3. Click the Connect to Web3 button to connect your wallet.

  4. Click the deployNewOperatorAndForwarder function to expand it and then click the Write button to run the function. Metamask prompts you to confirm the transaction.

  5. Click View your transaction. Etherscan will open a new tab. Wait for the transaction to be successful.

  6. On the Transaction Details page, click Logs to display the list of transaction events. Notice the OperatorCreated and AuthorizedForwarderCreated events.

  7. Right-click on each contract address and open it in a new tab.

  8. At this point, you should have one tab displaying the operator contract and one tab displaying the forwarder contract.

  9. Record the operator and forwarder addresses. You will need them later.

Access control setup

As explained in the forwarder page:

  • The owner of a forwarder contract is an operator contract. The owner of the operator contract is a more secure address, such as a hardware wallet or a multisig wallet. Therefore, node operators can manage a set of forwarder contracts through an operator contract using a secure account such as hardware or a multisig wallet.
  • Forwarder contracts distinguish between owners and authorized senders. Authorized senders are hot wallets (Chainlink nodes' EOAs).

For this example to run, you will have to:

  • Allow the forwarder contract to call the operator's fulfillOracleRequest2 function by calling the setauthorizedsenders function on the operator contract. Specify the forwarder address as a parameter.
  • Allow the two Chainlink node EOAs to call the forwarder's forward function. Because the operator contract owns the forwarder contract, call acceptAuthorizedReceivers on the operator contract. Specify the forwarder contract address and the two Chainlink node EOAs as parameters. This call makes the operator contract accept ownership of the forwarder contract and authorizes the Chainlink node EOAs to call the forwarder contract by calling setauthorizedsenders.

Whitelist the forwarder

In the blockchain explorer, view the operator contract and call the setAuthorizedSenders method with the address of your forwarder contract. The parameter is an array. For example, ["0xA3f07D6773514480b918C2742b027b3acD9E44fA"]. Metamask prompts you to confirm the transaction.

In the blockchain explorer, view the operator contract and call the acceptAuthorizedReceivers method with the following parameters:

  • targets: Specify an array of forwarder addresses. For example, ["0xA3f07D6773514480b918C2742b027b3acD9E44fA"]
  • sender: Specify an array with the two Chainlink node EOAs. For example, ["0x259c49E65644a020C2A642260a4ffB0CD862cb24","0x71a1Eb6534054E75F0D6fD0A3B0A336228DD5cFc"].

MetaMask prompts you to confirm the transaction.

Activate the forwarder

  1. In the shell of your Chainlink node, enable the forwarder using the Chainlink CLI. Replace forwarderAddress with the forwarder address that was created by the factory. Replace chainId with the EVM chain ID. (11155111 for Sepolia):

    chainlink forwarders track --address forwarderAddress --evmChainID chainId
    

    In this example, the command is:

    chainlink forwarders track --address 0xA3f07D6773514480b918C2742b027b3acD9E44fA --evmChainID 11155111
    
    Forwarder created
    ------------------------------------------------------
    ID:         1
    Address:    0xA3f07D6773514480b918C2742b027b3acD9E44fA
    Chain ID:   11155111
    Created At: 2023-03-02T11:41:43Zchainlink@22480bec8986
    
  2. Exit the shell of your Chainlink node:

    exit
    
  3. Stop your Chainlink node:

    docker stop chainlink && docker rm chainlink
    
  4. Update your environment file. If you followed Running a Chainlink Node locally guide then add ETH_USE_FORWARDERS=true and FEATURE_LOG_POLLER=true to your environment file:

    echo "ETH_USE_FORWARDERS=true
    FEATURE_LOG_POLLER=true" >> ~/.chainlink-sepolia/.env
    
    • ETH_USE_FORWARDERS enables sending transactions through forwarder contracts.
    • FEATURE_LOG_POLLER enables polling forwarder contracts logs to detect any changes to the authorized senders.
  5. Start the Chainlink node:

    cd ~/.chainlink-sepolia && docker run --platform linux/x86_64/v8 --name chainlink  -v ~/.chainlink-sepolia:/chainlink -it --env-file=.env -p 6688:6688 smartcontract/chainlink:1.12.0 local n
    
  6. You will be prompted to enter the key store password:

    2022-12-22T16:18:04.706Z [WARN]  P2P_LISTEN_PORT was not set, listening on random port 59763. A new random port will be generated on every boot, for stability it is recommended to set P2P_LISTEN_PORT to a fixed value in your environment config/p2p_v1_config.go:84       logger=1.10.0@aeb8c80.GeneralConfig p2pPort=59763
    2022-12-22T16:18:04.708Z [DEBUG] Offchain reporting disabled                       chainlink/application.go:373     logger=1.10.0@aeb8c80
    2022-12-22T16:18:04.709Z [DEBUG] Offchain reporting v2 disabled                    chainlink/application.go:422     logger=1.10.0@aeb8c80
    Enter key store password:
    
  7. Enter the key store password and wait for your Chainlink node to start.

Create directRequest jobs

This section is similar to the Fulfilling Requests guide.

  1. Open the Chainlink Operator UI.

  2. On the Jobs tab, click New Job.

  3. Create the uint256 job. Replace:

    • YOUR_OPERATOR_CONTRACT_ADDRESS with the address of your deployed operator contract address.

    • EOA_ADDRESS with the first Chainlink node EOA.

      # THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
      # THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
      # DO NOT USE THIS CODE IN PRODUCTION.
      
      # evmChainID for Sepolia Testnet
      evmChainID = "11155111"
      contractAddress = "YOUR_OPERATOR_CONTRACT_ADDRESS"
      forwardingAllowed = true
      maxTaskDuration = "0s"
      minIncomingConfirmations = 0
      name = "Get > Uint256"
      observationSource = """
          decode_log   [type="ethabidecodelog"
                        abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)"
                        data="$(jobRun.logData)"
                        topics="$(jobRun.logTopics)"]
      
          decode_cbor  [type="cborparse" data="$(decode_log.data)"]
          fetch        [type="http" method=GET url="$(decode_cbor.get)" allowUnrestrictedNetworkAccess="true"]
          parse        [type="jsonparse" path="$(decode_cbor.path)" data="$(fetch)"]
      
          multiply     [type="multiply" input="$(parse)" times="$(decode_cbor.times)"]
      
          encode_data  [type="ethabiencode" abi="(bytes32 requestId, uint256 value)" data="{ \\"requestId\\": $(decode_log.requestId), \\"value\\": $(multiply) }"]
          encode_tx    [type="ethabiencode"
                        abi="fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes calldata data)"
                        data="{\\"requestId\\": $(decode_log.requestId), \\"payment\\":   $(decode_log.payment), \\"callbackAddress\\": $(decode_log.callbackAddr), \\"callbackFunctionId\\": $(decode_log.callbackFunctionId), \\"expiration\\": $(decode_log.cancelExpiration), \\"data\\": $(encode_data)}"
                        ]
          submit_tx    [type="ethtx" to="YOUR_OPERATOR_CONTRACT_ADDRESS" data="$(encode_tx)" from="[\\"EOA_ADDRESS\\"]"]
      
          decode_log -> decode_cbor -> fetch -> parse -> multiply -> encode_data -> encode_tx -> submit_tx
      """
      schemaVersion = 1
      type = "directrequest"
      
  4. Create the string job. Replace:

    • YOUR_OPERATOR_CONTRACT_ADDRESS with the address of your deployed operator contract address.

    • EOA_ADDRESS with the second Chainlink node EOA.

      # THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
      # THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
      # DO NOT USE THIS CODE IN PRODUCTION.
      
      # evmChainID for Sepolia Testnet
      evmChainID = "11155111"
      contractAddress = "YOUR_OPERATOR_CONTRACT_ADDRESS"
      forwardingAllowed = true
      maxTaskDuration = "0s"
      minIncomingConfirmations = 0
      name = "Get > String"
      observationSource = """
          decode_log   [type="ethabidecodelog"
                        abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)"
                        data="$(jobRun.logData)"
                        topics="$(jobRun.logTopics)"]
      
          decode_cbor  [type="cborparse" data="$(decode_log.data)"]
          fetch        [type="http" method=GET url="$(decode_cbor.get)" allowUnrestrictedNetworkAccess="true"]
          parse        [type="jsonparse" path="$(decode_cbor.path)" data="$(fetch)"]
          encode_data  [type="ethabiencode" abi="(bytes32 requestId, string value)" data="{ \\"requestId\\": $(decode_log.requestId), \\"value\\": $(parse) }"]
          encode_tx    [type="ethabiencode"
                        abi="fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes calldata data)"
                        data="{\\"requestId\\": $(decode_log.requestId), \\"payment\\":   $(decode_log.payment), \\"callbackAddress\\": $(decode_log.callbackAddr), \\"callbackFunctionId\\": $(decode_log.callbackFunctionId), \\"expiration\\": $(decode_log.cancelExpiration), \\"data\\": $(encode_data)}"
                        ]
          submit_tx    [type="ethtx" to="YOUR_OPERATOR_CONTRACT_ADDRESS" data="$(encode_tx)" from="[\\"EOA_ADDRESS\\"]"]
      
          decode_log -> decode_cbor -> fetch -> parse -> encode_data -> encode_tx -> submit_tx
      """
      schemaVersion = 1
      type = "directrequest"
      
  5. After you create the jobs, record the two job IDs.

Test the transaction-sending strategy

Create API requests

  1. Open APIConsumerForwarder.sol in the Remix IDE.

  2. Note that _setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789) is configured for Sepolia.

  3. On the Compiler tab, click the Compile button for APIConsumerForwarder.sol.

  4. On the Deploy and Run tab, configure the following settings:

    • Select Injected Provider as your environment. Make sure your metamask is connected to Sepolia.
    • Select APIConsumerForwarder from the Contract menu.
  5. Click Deploy. MetaMask prompts you to confirm the transaction.

  6. Fund the contract by sending LINK to the contract's address. See the Fund Your Contracts page for instructions. The address for the ATestnetConsumer contract is on the list of your deployed contracts in Remix. You can fund your contract with 1 LINK.

  7. After you fund the contract, create a request. Input your operator contract address and the job ID for the Get > Uint256 job into the requestEthereumPrice request method without dashes. The job ID is the externalJobID parameter, which you can find on your job's definition page in the Node Operators UI.

  8. Click the transact button for the requestEthereumPrice function and approve the transaction in Metamask. The requestEthereumPrice function asks the node to retrieve uint256 data specifically from https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD.

  9. After the transaction processes, open the Runs page in the Node Operators UI. You can see the details for the completed the job run.

  10. In Remix, click the currentPrice variable to see the current price updated on your consumer contract.

  11. Input your operator contract address and the job ID for the Get > String job into the requestFirstId request method without dashes. The job ID is the externalJobID parameter, which you can find on your job's definition page in the Node Operators UI.

  12. Click the transact button for the requestFirstId function and approve the transaction in Metamask. The requestFirstId function asks the node to retrieve the first id from https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=10.

  13. After the transaction processes, you can see the details for the complete the job run the Runs page in the Node Operators UI.

  14. In Remix, click the id variable to see the current price updated on your consumer contract.

Check the forwarder

Confirm the following information:

  • The Chainlink node submitted the callbacks to the forwarder contract.
  • Each callback used a different account.
  • The forwarder contract forwarded the callbacks to the operator contract.

Open your forwarder contract in the block explorer. Two forward transactions exist.

Click on both transaction hashes and note that the From addresses differ. In this example, 0x259c49E65644a020C2A642260a4ffB0CD862cb24 is the EOA used in the uint256 job, and 0x71a1Eb6534054E75F0D6fD0A3B0A336228DD5cFc is the EOA used in the string job:

Then click on the Logs tab to display the list of events. Notice the OracleResponse events. The operator contract emitted them when the Forward contract called it

Get the latest Chainlink content straight to your inbox.