Tutorial - Get started with Gelato

How to automate smart contract functions using Gelato

In this Tutorial, we will be automating a simple Counter.sol smart contract which has a function called increaseCount(uint256 amount) which allows a state variable called count to be incremented every 3 minutes.

Goal

Get Gelato to automate the execution of the increaseCount(uint256 amount) function on Counter.sol every 3 minutes.

Step 1: Write & deploy the Action smart contract you want to automate

An Action is a smart contract that Gelato should automatically execute a function on based on certain condition being fulfilled. It can be an already deployed smart contract or you can write and deploy a new one. Action smart contracts should have a certain require in the function that should be automated which regulates when they can be executed on-chain.

In this tutorial, our Action smart contract is the Counter.sol contract. It has a require that makes sure that the increaseCount() function can only be called every 3 minutes, which we will be referring to as the "condition".

If you write a smart contract that should be executed by Gelato, make sure you encode the condition that regulates WHEN the execution should occur (e.g. every 3 minutes) into the function of the action itself.

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
contract Counter {
uint256 public count;
uint256 public lastExecuted;
function increaseCount(uint256 amount) external {
require(
((block.timestamp - lastExecuted) > 180),
"Counter: increaseCount: Time not elapsed"
);
count += amount;
lastExecuted = block.timestamp;
}
}

The action function can be named whatever you like and can have arbitrary arguments. You can find an instance of this Counter.sol contract on Mainnet & Ropsten.

Step 2: Write and deploy a Resolver smart contract

A Resolver is a smart contract which tells Gelato a) when to execute the action smart contract (e.g. Counter.sol), b) which function to execute on the action (e.g. increaseCount(uint256)) and c) what inputs to use to execute that function (e.g. 100).

Here is an example of how a CounterResolver.sol contract might look like:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
interface ICounter {
function lastExecuted() external view returns (uint256);
function increaseCount(uint256 amount) external;
}
contract CounterResolver {
address public immutable COUNTER;
constructor(address _counter) {
COUNTER = _counter;
}
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 lastExecuted = ICounter(COUNTER).lastExecuted();
canExec = (block.timestamp - lastExecuted) > 180;
execPayload = abi.encodeWithSelector(
ICounter.increaseCount.selector,
uint256(100)
);
}
}

You can find an instance of this CounterResolver.sol contract on Mainnet & Ropsten.

In this case, the resolver checks that at least 180 seconds (3 minutes) have passed since the last execution. If so, the function will return true as the canExec boolean, indicating to Gelato that it should execute the incrementCount() function on the Counter.sol action by passing 100 as the amount which will increment the count by 100.

Resolvers are called off-chain by Gelato bots in order to understand a) if the action smart contract is executable and b) what data to use to execute it. The most important part of the resolver contract is that it has one function that Gelato bots should call that returns 2 return values:

  • bool canExec true if Gelato should execute, false if Gelato should not execute

  • bytes memory execPayload The function selector of the function to call on the action and the encoded data that should be used to call that function.

Pro Tip 1: In order to return the execPayload use solidities abi.encodeWithSelector or abiEncodeWithSignature functions

Step 3: Create a Task

This was everything that you needed to do in solidity. Now in order to task Gelato bots to automatically call your desired function on your action smart contract every time the canExec return value of your resolver returns true, you have to create a task on the PokeMe.sol smart contract. You can find the PokeMe contract on Mainnet, Polygon & Ropsten.

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
contract PokeMe {
...
function createTask(
address _execAddress,
bytes4 _execSelector,
address _resolverAddress,
bytes calldata _resolverData
) external;

Arguments:

  • _execAddress The address of your action contract (e.g. Counter.sol)

  • _execSelector The function selector of the function you want to call on the action, in this case increaseCount(uint256) => 0x46d4adf2

  • _resolverAddress The address of your resolver contract, e.g. CounterResolver.sol

  • _resolverData The function selector and encoded data of your resolver function, in this case checker() => 0xcf5303cf

Pro Tip 2: Use the getSelector() view function on the PokeMe.sol smart contract to get the _execSelector.

Pro Tip 3: Use a method like abi.encodeWithSelector(selector, input1, input2, ...) in order to get the _resolverData

For the Counter.sol example, the inputs of the createTask() function can look like this:

Step 4: Deposit Funds to pay for the execution

Transactions on e.g. Ethereum are not free. Gelato bots pay the transaction fee to miners and thus need to get their costs refunded + a small incentive added to make it worth operating the infrastructure. In general, on Ethereum mainnet, if a transaction would cost $10 doing it yourself, then it will cost roughly $11-12 using Gelato, the margin on top being the incentive that goes to bot operators.

For networks like Polygon, Gelato charges a fixed fee per transaction which you can check out by calling maxFee() on the respective TaskTreasury.sol smart contract.

In order to pay Gelato bots for every execution, you can simply deposit some ETH, USDC, DAI or any other ERC20 token on the TaskTreasury contract which will be automatically deducted with every execution that Gelato bots conduct on your behalf.

You can find the TaskTreasury contract on Mainnet, Polygon & Ropsten

In order to deposit ETH to pay for your transactions, call the depositFunds() function on the TaskTreasury.sol contract:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
contract TaskTreasury {
...
function depositFunds(
address _receiver,
address _token,
uint256 _amount
) external payable

Arguments:

  • _receiver Address whose balance you want to top up. In most cases, this should be your address (the same one which created the task)

  • _token Address of the token you want to deposit. For ETH use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE

  • _amount Amount of the _token that should be deposited in wei

Pro Tip 4: If you would like to deposit an ERC20 instead of ETH, please make sure to approve the TaskTreasury.sol contract beforehand with the respective amount.

An example how the inputs can look like:

Congratulations, you just created your first Task on Gelato 🎉🎉🎉Bots will now start monitoring your Resolver contract and if your Resolver function returns true, they will execute your Action contract.

If you have any problems with your task, feel free to reach out to us in Telegram or Discord, we are happy to dive into your code and help you out!