Keeper Guide

This is a high level guide on how you can become a keeper or build your own keeper bot!

Overview

There are two main options for how you can become a keeper.

  1. Fork and run the official Mero keeper bot.

  2. Build your own keeper bot.

The former is a quick and easy way to get started! And you can have your keeper up and running within an hour.

The latter is more involved, and will require some technical programming skills. But can result in a more competitive keeper implementation capable of capturing a larger fee share.

Both options are outlined below.

Fork the Mero Keeper

Mero has built a keeper bot that you can fork and run yourself! For a guide on how to set this up you can visit the repository here to see the details.

Note

The keeper bot is still in active development, and the repository might not be public yet. If that is the case, please wait for announcements on the discord server on when this will be released.

Build your own Keeper

It’s not too difficult to build your own keeper bot. And has the benefit of being able to make custom improvements to be the first to execute actions.

Note

This guide will focus on building a Topup Action Keeper, as that is currently the only action live on Mero. Building keepers for other actions should be a similar process and this guide can be adapted for such a general purpose keeper.

There are two main steps to building a keeper bot.

  1. Identify executable positions

  2. Execute the positions

Identify executable positions

There are options for how to identify executable positions:

  • Get the data directly from on chain

  • Use the KeeperHelper

Get the data directly from on chain

For this option, the first step is to get a list of users who have an action currently registered at Mero. This can most easily queried from the view:

TopUpAction.usersWithPositions(uint256 cursor, uint256 howMany) external view returns (address[] memory users, uint256 nextCursor)

Gets a list of users that have an active position

  • cursor: The cursor for pagination (should start at 0 for first call).

  • howMany: Maximum number of users to return in this pagination request.

  • users: List of users that have an active position.

  • nextCursor: The cursor to use for the next pagination request.

Note

Uses cursor pagination.

Once you have a list of the users who have an action registered, you then need to get a list of the positions for each user. This can be most easily queried from the view:

TopUpAction.getUserPositions(address payer) external view returns (RecordMeta[] memory)

Returns a list of positions for the given payer.

  • payer: The position payer.

However, this view doesn’t return the full position data, but just the key identifying information. To get the full position data, for each position you can query this view:

TopUpAction.getPosition(address payer, bytes32 account, bytes32 protocol) public view returns (Record memory)

Get the record for a position

  • payer: Registered payer of the position.

  • account: Address holding the position.

  • protocol: Protocol where the position is held.

From this view you will now have a list of all active positions and the full details of all those positions.

The next step is to find out which of those positions are executable. For the Topup Action, an action is executable if the health factor is below the position threshold. To see if the this is the case, we first need the health factor for all positions. This can be queried from the view:

TopUpAction.getHealthFactor(bytes32 protocol, bytes32 account, bytes calldata extra) public view returns (uint256 healthFactor)

Get the health factor for a protocol and account.

  • protocol: Protocol for which to get the health factor.

  • account: Account for which to get the health factor.

  • extra: Extra data to be used by the topup handler.

  • healthFactor: The health factor of the position.

You can now check this against the target for each positions to see which ones are executable.

Use the `KeeperHelper`

The other option for identifying the executable positions is to use the KeeperHelper.

The KeeperHelper is a helper contract that that wraps many of the steps above into a single function call to make things simpler. The view below can be used to get the executable positions

TopUpKeeperHelper.getExecutableTopups(uint256 cursor, uint256 howMany) external view returns (TopupData[] memory topups, uint256 nextCursor)

Gets a list of topup positions that can be executed.

  • cursor: The cursor for pagination (should start at 0 for first call).

  • howMany: Maximum number of topups to return in this pagination request.

Execute the positions

One you have the list of executable positions, the next step is to execute them. This is done by calling the execute function on the TopUpKeeper contract.

TopUpAction.function execute(address payer, address account, address beneficiary, bytes32 protocol) external override returns (bool)

Top up a registered position on an external protocol (e.g. AAVE or Compound) if the position’s conditions are met. Pool and vault funds are rebalanced after withdrawal for top up

  • payer: Account that pays for the top up.

  • account: Account owning the position for top up.

  • beneficiary: Address of the keeper’s wallet for fee accrual.

  • protocol: Protocol of the top up position. Note: this is the name of the protocol represented as bytes32.

There is also an execute function that accepts an additional value maxWeiForGas:

TopUpAction.function execute(address payer, address account, address beneficiary, bytes32 protocol) external override returns (bool)

Top up a registered position on an external protocol (e.g. AAVE or Compound) if the position’s conditions are met. Pool and vault funds are rebalanced after withdrawal for top up

  • payer: Account that pays for the top up.

  • account: Account owning the position for top up.

  • beneficiary: Address of the keeper’s wallet for fee accrual.

  • protocol: Protocol of the top up position. Note: this is the name of the protocol represented as bytes32.

  • maxWeiForGas: The maximum extra amount of wei that the keeper is willing to pay for the gas.

Looping through each of the executable positions and calling one of these functions should do the trick!

Considerations

There are some additional considerations when building or using a keeper that are outlined below.

Staking MERO

For a keeper to execute an action, they have to first meet the requirement for the minimum amount of MERO staked. This is a measure in place to protect keepers against general purpose frontrunning bots.

To see how much MERO is required to execute a top up, you can call the Controller.getKeeperRequiredStakedMERO() view. And to see if an address meets that requirement, you can call the canKeeperExecuteAction(address keeper) view.

Ensure you have enough MERO staked on your keeper wallet to execute the top up.

Execution competition

It is likely at times that the topup you try to execute will also have been identified by another keeper. And that this keeper might also submit a transaction to execute this topup. If this is the case, and the other keepers transaction is executed before yours, then your transaction could revert.

There are a few ways to prevent this from happening. And this is not an exhaustive list:

  • Monitor the MEM pool to check if a transaction already exists to execute this topup. If it does, then don’t submit a transaction.

  • Monitor the MEM pool to check if a transaction already exists to execute this topup. If it does, then submit your transaction with a higher gas price.

  • Use MEV to front run other keepers.

  • Try to submit executions within the same block that the position becomes executable, before other keepers even have a chance to identify it.

Creatively and competively handling this problem is key to building a great and profitable keeper.