Follow Rules

This guide will explain Follow Rules and how to implement them.


Follow Rules allow accounts to add requirements or constraints that will be applied when another account tries to Follow them.

See the Rules concept section to learn more about them.

Existing Follow Rules

TokenGatedFollowRule

This rule can be applied to an account, within a context of an specific Graph, to require any other account trying to Follow it to hold a certain balance of a token (both fungible and non-fungible are supported) in order to succeed on that operation.

The configuration requires to input the address of the token, the token standard used by the provided token (ERC-20, ERC-721, and ERC-1155 are supported), and the amount of tokens required to pass the gate. In case of using the ERC-1155 token standard, an additional token type ID is expected to be provided.

SimplePaymentFollowRule

This rule can be applied to an account, within a context of an specific Graph, to require an ERC-20 payment from any other account trying to Follow it in order to succeed on that operation.

The configuration requires the ERC-20 token address, the amount of tokens required for the payment, and the recipient address where the tokens will be sent.

Building a Follow Rule

Let's illustrate the process with an example. We will build a custom Follow Rule that once applied it will only accept Follows from accounts that you previously Followed in some Graph (particularly, the same Graph where the rule is applied can be used, so only the accounts that you Follow will be able to Follow you back).

To build a custom Follow Rule, you must implement the following IFollowRule interface:

interface IFollowRule {    function configure(        address account,        bytes calldata data    ) external;
    function processFollow(        address followerAccount,        address accountToFollow,        uint256 followId,        bytes calldata data    ) external returns (bool);}

Each function of this interface must assume to be invoked by the Graph contract.

A Lens dependency package with all relevant interfaces will be available soon.

1

Implement the Configure Function

First, implement the configure function. This function has the purpose of initializing any required state for the rule to work properly.

The configure function can be called multiple times by the same Graph, with the same account parameter, in order to update the rule configuration (i.e. reconfigure it).

It receives the account for which the rule is being configured for and some data bytes that will be decoded into any extra required rule configuration parameters.

In our example, we need to decode the data bytes into an address parameter, which will represent the Graph where we need to check if the account attempting the Follow is already being Followed by the account trying to be Followed. Let's define a storage mapping to store this configuration:

contract AlreadyFollowedFollowRule is IFollowRule {
  mapping(address => mapping(address => address)) internal _graphToCheck;}

Now let's code the configure function itself, decoding the address parameter and storing it in the mapping:

contract AlreadyFollowedFollowRule is IFollowRule {
  mapping(address => mapping(address => address)) internal _graphToCheck;
  function configure(     address account,     bytes calldata data  ) external override {      _graphToCheck[msg.sender][account] = abi.decode(data, (address));  }}

The configuration is stored in the mapping using the Graph contract address as the key, which is the msg.sender, and the account for which the rule is being configured for. So the same rule can be reused by different Graphs and accounts.

2

Implement the Process Follow function

Next, implement the processFollow function. This function is invoked by the Graph contract every time a Follow is executed, so then our custom logic can be applied to shape under which conditions this operation can succeed.

The function receives the account executing the follow (followerAccount), the account being followed (accountToFollow), the followId (only applies if Follows are tokenized, so you can use some specific Follow Token to Follow), and some data in case the rule requires additional information to work.

The function must revert in case of not meeting the requirements imposed by the rule. Otherwise, it must return a boolean that indicates if the rule is applying a restriction over this operation or not. In our example, given that a restriction is indeed being applied to the Follow operation, we will return true.

We first get the configured Graph where to perform the check:

contract AlreadyFollowedFollowRule is IFollowRule {
    mapping(address => mapping(address => address)) internal _graphToCheck;
    // . . .
    function processFollow(       address followerAccount,       address accountToFollow,       uint256 followId,       bytes calldata data      ) external override returns (bool)    {        // We get the configured Graph        IGraph graph = IGraph(_graphToCheck[msg.sender][accountToFollow]);    }}

Next we require that followerAccount is being followed by accountToFollow in the configured Graph.

The IGraph interface contains this function in order to check whether an account is following another account or not:

function isFollowing(        address followerAccount,        address targetAccount    ) external view returns (bool);

So, let's add the requirement check:

contract AlreadyFollowedFollowRule is IFollowRule {
    mapping(address => mapping(address => address)) internal _graphToCheck;
    // . . .
    function processFollow(       address followerAccount,       address accountToFollow,       uint256 followId,       bytes calldata data      ) external override returns (bool)    {        IGraph graph = IGraph(_graphToCheck[msg.sender][accountToFollow]);        // Require that `followerAccount` is being followed        // by `accountToFollow` in the configured Graph        require(graph.isFollowing(accountToFollow, followerAccount));    }}

Finally, we return true as the rule is being applied to the Follow operation:

contract AlreadyFollowedFollowRule is IFollowRule {
    mapping(address => mapping(address => address)) internal _graphToCheck;
    // . . .
    function processFollow(       address followerAccount,       address accountToFollow,       uint256 followId,       bytes calldata data      ) external override returns (bool)    {        IGraph graph = IGraph(_graphToCheck[msg.sender][accountToFollow]);        require(graph.isFollowing(accountToFollow, followerAccount));        // We return true to signal that the rule is being        // applied when executing a Follow        return true;    }}

Now the AlreadyFollowedFollowRule is ready to be applied into any account under any Graph. See the full code below:

contract AlreadyFollowedFollowRule is IFollowRule {
    mapping(address => mapping(address => address)) internal _graphToCheck;
    function configure(      address account,      bytes calldata data    ) external override {        _graphToCheck[msg.sender][account] = abi.decode(data, (address));    }
    function processFollow(       address followerAccount,       address accountToFollow,       uint256 followId,       bytes calldata data      ) external override returns (bool)    {        // We get the configured Graph        IGraph graph = IGraph(_graphToCheck[msg.sender][accountToFollow]);        // Require that `followerAccount` is being followed        // by `accountToFollow` in the configured Graph        require(graph.isFollowing(accountToFollow, followerAccount));        // We return true to signal that the rule is being        // applied when executing a Follow        return true;    }}

Stay tuned for API integration of rules and more guides!