Feed Rules
This guide explains how to use Feed Rules and how to implement custom ones.
Feed Rules allow administrators to add requirements or constraints when creating content on a Feed.
Using Feed Rules
Lens provides four built-in Feed rules:
SimplePaymentFeedRule - Requires an ERC-20 payment to post on the Feed.
TokenGatedFeedRule - Requires an account to hold a certain token to interact with the Feed.
RestrictedSignersFeedRule - Requires a signature from a trusted signer to post on the Feed. More details coming soon.
GroupGatedFeedRule - Requires an account to be a member of a certain Group to post on the Feed.
It is also possible to use custom Feed Rules to extend the functionality of your Feed.
Create a Feed with Rules
As part of creating Custom Feeds, you can pass a rules object that defines the required rules and/or an anyOf set, where satisfying any one rule allows posting on the Feed. These rules can be built-in or custom.
This section presumes you are familiar with the process of creating a Feed on Lens.
- TypeScript
- GraphQL
- React
Update a Feed Rules
More details on this coming soon.
Building a Feed Rule
What follows is an example of how to build a rule based on an early iteration. It's provided for illustrative purposes only. Updated documentation on the final implementation will be provided soon.
Let's illustrate the process with an example. We will build the GroupGatedFeedRule described above, a rule that requires accounts to be a member of a certain Group in order to Create a Post on the Feed.
To build a custom Feed Rule, you must implement the following IFeedRule interface:
interface IFeedRule { function configure(bytes calldata data) external;
function processCreatePost( uint256 postId, CreatePostParams calldata postParams, bytes calldata data ) external returns (bool);
function processEditPost( uint256 postId, EditPostParams calldata editPostParams, bytes calldata data ) external returns (bool);
function processPostRulesChanged( uint256 postId, RuleConfiguration[] calldata newPostRules, bytes calldata data ) external returns (bool);}
Each function of this interface must assume to be invoked by the Feed contract.
A Lens dependency package with all relevant interfaces will be available soon.
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 Feed in order to update the rule configuration (i.e. reconfigure it).
It receives bytes that will be decoded into the required rule configuration parameters.
In our example, we need to decode an address parameter, which will represent the Group contract where the account trying to post must belong to. Let's define a storage mapping to store this configuration:
contract GroupGatedFeedRule is IFeedRule { mapping(address => address) internal _groupGate;}
Now let's code the configure function itself, decoding the address parameter and storing it in the mapping:
contract GroupGatedFeedRule is IFeedRule { mapping(address => address) internal _groupGate;
function configure(bytes calldata data) external override { _groupGate[msg.sender] = abi.decode(data, (address)); }}
The configuration is stored in the mapping using the Feed contract address as the key, which is the msg.sender. So the same rule can be reused by different Feeds.
Next, implement the processCreatePost function. This function is invoked by the Feed contract every time a Post is trying to be created, so then our custom logic can be applied to shape under which conditions this operation can succeed.
The function receives the ID the Feed assigned to the Post (postId), the parameters of the Post (postParams, including things like author and contentURI), 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 Create Post operation, we will return true.
Groups follow the IGroup interface, which has the following function to query the membership ID of any account, returning zero if the account is not a member of the Group:
function getMembershipId(address account) external view returns (uint256);
Now let's code the processCreatePost function taking all the described above into account:
contract GroupGatedFeedRule is IFeedRule { mapping(address => address) internal _groupGate;
// . . .
function processCreatePost( uint256 postId, CreatePostParams calldata postParams, bytes calldata data ) external override returns (bool) { require(IGroup(_groupGate[msg.sender]).getMembershipId(postParams.author) != 0); return true; }
Next, implement the processEditPost function. This function is invoked by the Feed contract every time a Post is trying to be edited, so then our custom logic can be applied to shape under which conditions this operation can succeed.
The function receives the ID the Feed assigned to the Post (postId), the parameters of the Post to edit (editPostParams), 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 not being applied to the Edit Post operation, we must implement the function to comply with the interface but just return false.
contract GroupGatedFeedRule is IFeedRule {
// . . .
function processEditPost( uint256 postId, EditPostParams calldata editPostParams, bytes calldata data ) external override returns (bool) { return false; }
Finally, implement the processPostRulesChanged function. This function is invoked by the Feed contract every time an account makes a change on the Post Rules of a Post it authors, so then our rule can define if this change must be accepted or not.
The function receives the ID the Feed assigned to the Post which rules are being changed (postId), the configuration of the rules being changed (newPostRules), 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, we do not want to apply the restriction to the Post Rules Changes operation, so we must implement the function to comply with the interface but just return false.
contract GroupGatedFeedRule is IFeedRule {
// . . .
function processPostRulesChanged( uint256 postId, RuleConfiguration[] calldata newPostRules, bytes calldata data ) external override returns (bool) { return false; }}
Now the GroupGatedFeedRule is ready to be applied into any Feed. See the full code below:
contract GroupGatedFeedRule is IFeedRule { mapping(address => address) internal _groupGate;
function configure(bytes calldata data) external override { _groupGate[msg.sender] = abi.decode(data, (address)); }
function processCreatePost( uint256 postId, CreatePostParams calldata postParams, bytes calldata data ) external override returns (bool) { require(IGroup(_groupGate[msg.sender]).getMembershipId(postParams.author) != 0); return true; }
function processEditPost( uint256 postId, EditPostParams calldata editPostParams, bytes calldata data ) external override returns (bool) { return false; }
function processPostRulesChanged( uint256 postId, RuleConfiguration[] calldata newPostRules, bytes calldata data ) external override returns (bool) { return false; }}
Stay tuned for API integration of rules and more guides!