Follow Rules
This guide explains how to use Follow Rules and how to implement custom ones.
Follow Rules allow accounts to add requirements or constraints that will be applied when another account tries to Follow them.
Using Follow Rules
Users have the option to apply Follow Rules to their accounts. These rules dictates the conditions under which another account can Follow them.
For Custom Graphs, these rules work in combination with Graph Rules set on the given graph.
This section presumes you are familiar with the process of following an Account on Lens.
Built-in Follow Rules
Simple Payment Follow Rule
The SimplePaymentFollowRule is a built-in rule that can be applied to an Account within the context of a specific Graph. It requires an ERC-20 payment from any Account attempting to follow it in order for the operation to succeed.
A 1.5% Lens treasury fee is deducted from the payment before the remaining amount is transferred to the designated recipient.
Token Gated Follow Rule
The TokenGatedFollowRule is a built-in rule that can be applied to an Account within the context of a specific Graph. It requires any other Account attempting to follow it to hold a certain balance of a token (both fungible and non-fungible are supported).
Update Follow Rules
You MUST be authenticated as Account Owner or Account Manager of the Account you intend to update the Follow Rules for.
First, prepare the update request with the new Follow Rules configuration.
- TypeScript
- GraphQL
- React
Use the updateAccountFollowRules action to update the Follow Rules for the logged-in Account.
- TypeScript
- GraphQL
- React
Then, handle the result using the adapter for the library of your choice:
See the Transaction Lifecycle guide for more information on how to determine the status of the transaction.
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:
IFollowRule.sol
interface IFollowRule { function configure(bytes32 configSalt, address account, KeyValue[] calldata ruleParams) external;
function processFollow( bytes32 configSalt, address originalMsgSender, address followerAccount, address accountToFollow, KeyValue[] calldata primitiveParams, KeyValue[] calldata ruleParams ) external;}
Each function of this interface must assume to be invoked by the Graph contract where the rule is applied. In other words, assume the msg.sender will be the Graph contract.
A Lens dependency package with all relevant interfaces will be available soon.
First, implement the configure function. This function initializes any required state for the rule to work correctly for a specific account on a specific graph configuration.
It receives three parameters:
configSalt: A 32-byte value allowing the same rule contract to be used multiple times with different configurations for the same account within the same graph. The triple (Graph Address, Account Address, Configuration Salt) identifies a unique rule configuration instance.
account: The address of the account for which this rule is being configured for.
ruleParams: An array of key-value pairs that can be used to pass any custom configuration parameters to the rule. Each key is bytes32, we put the hash of the parameter name there, and each value is bytes, we set the ABI-encoded parameter value there. Given that ruleParams is an array, this allows the rule to define which parameters are optional and which are required, acting accordingly when any of them are not present.
The configure function can be called multiple times by the same Graph, passing the same account and configSalt, to update an existing rule configuration (i.e., reconfigure it).
In our example, we need to configure which Graph should be checked to see if the accountToFollow is already following the followerAccount. We'll decode an address parameter representing this target Graph.
Let's define a storage mapping to store this configuration:
contract AlreadyFollowedFollowRule is IFollowRule { mapping(address graph => mapping(bytes32 configSalt => mapping(address account => address graph))) _graphToCheck;}
The configuration parameters are stored using the Graph contract address (msg.sender), the configSalt, and the account as keys. This allows the same rule contract to be reused across different Graphs and Accounts as several times.
Now let's code the configure function, decoding the target graph address from ruleParams:
contract AlreadyFollowedFollowRule is IFollowRule { mapping(address graph => mapping(bytes32 configSalt => mapping(address account => address graph))) _graphToCheck;
function configure(bytes32 configSalt, address account, KeyValue[] calldata ruleParams) external override { address graph; for (uint256 i = 0; i < ruleParams.length; i++) { if (ruleParams[i].key == keccak256("lens.param.graph")) { graph = abi.decode(ruleParams[i].value, (address)); break; } }
// A call to check if the address holds a valid Graph IGraph(graph).isFollowing(address(this), address(account));
_graphToCheck[msg.sender][configSalt][account] = graph; }}
Here, we made the graph a required parameter, given that when it is not present it will be address(0) and then revert on the isFollowing call.
Next, implement the processFollow function. This function is invoked by the Graph contract every time a Follow is executed, so then the rule logic can be applied to shape under which conditions this operation can succeed.
The function receives:
configSalt: Identifies the specific rule configuration instance.
originalMsgSender: The address that invoked the follow function in the Graph contract.
followerAccount: The account taking the role of follower.
accountToFollow: The account that is being followed, which is the one the rule was configured for.
primitiveParams: Additional parameters that were passed to the Graph's follow function call.
ruleParams: Additional parameters specific to this rule execution.
For our example, we need to:
Retrieve the configured graph to check.
Check if accountToFollow is already following followerAccount on that graph.
Revert if the accountToFollow is not already following the followerAccount on that 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);
Let's see how each step looks like in code:
contract AlreadyFollowedFollowRule is IFollowRule { mapping(address graph => mapping(bytes32 configSalt => mapping(address account => address graph))) _graphToCheck;
// ...
function processFollow( bytes32 configSalt, address originalMsgSender, address followerAccount, address accountToFollow, KeyValue[] calldata primitiveParams, KeyValue[] calldata ruleParams ) external view override { // Retrieve the configured graph to check IGraph graphToCheck = _graphToCheck[msg.sender][configSalt][accountToFollow];
// Check if `accountToFollow` is already following `followerAccount` on that graph bool isFollowedBack = graphToCheck.isFollowing(accountToFollow, followerAccount);
// Revert if the `accountToFollow` is not already following the `followerAccount` on that graph require(isFollowedBack); }}
Now the AlreadyFollowedFollowRule is ready to be applied to any account under any Graph that supports Follow Rules. See the full code below:
contract AlreadyFollowedFollowRule is IFollowRule { mapping(address graph => mapping(bytes32 configSalt => mapping(address account => address graph))) _graphToCheck;
function configure(bytes32 configSalt, address account, KeyValue[] calldata ruleParams) external override { address graph; for (uint256 i = 0; i < ruleParams.length; i++) { if (ruleParams[i].key == keccak256("lens.param.graph")) { graph = abi.decode(ruleParams[i].value, (address)); break; } }
// A call to check if the address holds a valid Graph IGraph(graph).isFollowing(address(this), address(account));
_graphToCheck[msg.sender][configSalt][account] = graph; }
function processFollow( bytes32 configSalt, address originalMsgSender, address followerAccount, address accountToFollow, KeyValue[] calldata primitiveParams, KeyValue[] calldata ruleParams ) external view override { // Retrieve the configured graph to check IGraph graphToCheck = _graphToCheck[msg.sender][configSalt][accountToFollow];
// Check if `accountToFollow` is already following `followerAccount` on that graph bool isFollowedBack = graphToCheck.isFollowing(accountToFollow, followerAccount);
// Revert if the `accountToFollow` is not already following the `followerAccount` on that graph require(isFollowedBack); }}
Stay tuned for API integration of rules and more guides!