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.
Check your Chainlink node is running
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
-
Access the shell of your Chainlink node container:
docker exec -it chainlink /bin/bash
chainlink@1d095e4ceb09:~$
-
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.
-
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
-
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
-
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
-
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.
-
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.
-
Open contract 0x447Fd5eC2D383091C22B8549cb231a3bAD6d3fAf to display the factory in the Sepolia block explorer.
-
Click the Contract tab. Then, click Write Contract to display the write transactions on the factory.
-
Click the Connect to Web3 button to connect your wallet.
-
Click the
deployNewOperatorAndForwarder
function to expand it and then click the Write button to run the function. Metamask prompts you to confirm the transaction. -
Click View your transaction. Etherscan will open a new tab. Wait for the transaction to be successful.
-
On the Transaction Details page, click Logs to display the list of transaction events. Notice the
OperatorCreated
andAuthorizedForwarderCreated
events. -
Right-click on each contract address and open it in a new tab.
-
At this point, you should have one tab displaying the operator contract and one tab displaying the forwarder contract.
-
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.
Whitelist the Chainlink node EOAs
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
-
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. ReplacechainId
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
-
Exit the shell of your Chainlink node:
exit
-
Stop your Chainlink node:
docker stop chainlink && docker rm chainlink
-
Update your environment file. If you followed Running a Chainlink Node locally guide then add
ETH_USE_FORWARDERS=true
andFEATURE_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.
-
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
-
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] Off-chain reporting disabled chainlink/application.go:373 logger=1.10.0@aeb8c80 2022-12-22T16:18:04.709Z [DEBUG] Off-chain reporting v2 disabled chainlink/application.go:422 logger=1.10.0@aeb8c80 Enter key store password:
-
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.
-
Open the Chainlink Operator UI.
-
On the Jobs tab, click New Job.
-
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. 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"
-
-
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. 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"
-
-
After you create the jobs, record the two job IDs.
Test the transaction-sending strategy
Create API requests
-
Open APIConsumerForwarder.sol in the Remix IDE.
-
Note that
setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789)
is configured for Sepolia. -
On the Compiler tab, click the Compile button for
APIConsumerForwarder.sol
. -
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.
-
Click Deploy. MetaMask prompts you to confirm the transaction.
-
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. -
After you fund the contract, create a request. Input your operator contract address and the job ID for the
Get > Uint256
job into therequestEthereumPrice
request method without dashes. The job ID is theexternalJobID
parameter, which you can find on your job’s definition page in the Node Operators UI. -
Click the transact button for the
requestEthereumPrice
function and approve the transaction in Metamask. TherequestEthereumPrice
function asks the node to retrieveuint256
data specifically from https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD. -
After the transaction processes, open the Runs page in the Node Operators UI. You can see the details for the completed the job run.
-
In Remix, click the
currentPrice
variable to see the current price updated on your consumer contract. -
Input your operator contract address and the job ID for the
Get > String
job into therequestFirstId
request method without dashes. The job ID is theexternalJobID
parameter, which you can find on your job’s definition page in the Node Operators UI. -
Click the transact button for the
requestFirstId
function and approve the transaction in Metamask. TherequestFirstId
function asks the node to retrieve the firstid
from https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=10. -
After the transaction processes, you can see the details for the complete the job run the Runs page in the Node Operators UI.
-
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