Post Rules

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


Post Rules allow authors to add requirements or constraints that will be applied when another account tries to interact (reply, repost, or quote) with their Posts.

See the Rules concept section to learn more about them.

Existing Post Rules

FollowersOnlyPostRule

This rule can be applied to a Post to require to be a Follower of the Post's author in order to be allowed to interact with it.

The configuration requires to input the address of the Feed where the follower check should be performed, and three boolean values, one per type of interaction (reply, repost, or quote), to indicate if the restriction should be applied to that type of interaction or not.

Building a Post Rule

Let's illustrate the process with an example. We will build a custom Post Rule that only allows a limited amount of replies to a Post.

To build a custom Post Rule, you must implement the following IPostRule interface:

interface IPostRule {    function configure(        uint256 postId,        bytes calldata data    ) external;
    function processQuote(        uint256 rootPostId,        uint256 quotedPostId,        uint256 postId,        bytes calldata data    ) external returns (bool);
    function processReply(        uint256 rootPostId,        uint256 repliedPostId,        uint256 postId,        bytes calldata data    ) external returns (bool);
    function processRepost(        uint256 rootPostId,        uint256 repostedPostId,        uint256 postId,        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.

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 Feed, with the same postId parameter, in order to update the rule configuration (i.e. reconfigure it).

It receives the ID of the Post (postId) for which the rule is being configured for and some data bytes that will be decoded into any extra required rule configuration parameters.

Reply posts and reposts cannot have rules. Only root posts, like normal original posts and quotes, can have rules. Reply posts inherit the rules from their root post.

In our example, we need to decode the data bytes into an integer parameter, which will represent the amount of maximum replies allowed on the Post configuring this rule. Let's define a storage mapping to store this configuration:

contract LimitedRepliesPostRule is IPostRule {    mapping(address => mapping(uint256 => uint256)) internal _repliesLimit;}

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

contract LimitedRepliesPostRule is IPostRule {    mapping(address => mapping(uint256 => uint256)) internal _repliesLimit;
    function configure(uint256 postId, bytes calldata data) external override {        _repliesLimit[msg.sender][postId] = abi.decode(data, (uint256));    }}

The configuration is stored in the mapping using the Feed contract address as the key, which is the msg.sender, and the postId for which the rule is being configured for. So the same rule can be reused by different Feeds and Posts.

2

Implement the Process Reply function

Next, implement the processReply function. This function is invoked by the Feed contract every time a Reply to 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 of the Root Post (rootPostId), which is the original Post where all replies ultimately leads to and the one where the rule is applied, the ID of the Replied Post (repliedPostId), which is the ID of the Post that is being directly replied (rootPostId and repliedPostId will match in case of first level replies), the ID of the new Reply Post being created (postId), 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 Reply operation, we will return true.

We will need an additional storage mapping to keep track of the amount of replies that have been created for a specific Post:

contract LimitedRepliesPostRule is IPostRule {
    // ...
    mapping(address => mapping(uint256 => uint256)) internal _repliesCounter;
    // ...}

Now we can start implementing the processReply by incrementing the replies counter.

Remember that reply posts and reposts cannot have rules. Reply posts inherit the rules from their root post.

contract LimitedRepliesPostRule is IPostRule {    mapping(address => mapping(uint256 => uint256)) internal _repliesLimit;    mapping(address => mapping(uint256 => uint256)) internal _repliesCounter;
    // . . .
    function processReply(        uint256 rootPostId,        uint256 repliedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        _repliesCounter[msg.sender][rootPostId]++;    }}

To finish the implementation of this function, we require the replies counter to not exceed the configured replies limit, then we return true as the rule is being applied to the Reply operation:

contract LimitedRepliesPostRule is IPostRule {    mapping(address => mapping(uint256 => uint256)) internal _repliesLimit;    mapping(address => mapping(uint256 => uint256)) internal _repliesCounter;
    // . . .
    function processReply(        uint256 rootPostId,        uint256 repliedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        _repliesCounter[msg.sender][rootPostId]++;        require(_repliesCounter[msg.sender][rootPostId] <= _repliesLimit[msg.sender][rootPostId]);        return true;    }}

3

Implement the Process Quote function

Next, implement the processQuote function. This function is invoked by the Feed contract every time a Quote to 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 of the Root Post (rootPostId), which is the Quoted Post's Root Post, where the rule was configured, the ID of the Quoted Post (quotedPostId), which is the ID of the Post that is being directly quoted (rootPostId and quotedPostId will match in case of a root post being quoted), the ID of the Quote Post being created (postId), 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 Quote operation, we must implement the function to comply with the interface but just return false.

contract LimitedRepliesPostRule is IPostRule {
    // . . .
    function processQuote(        uint256 rootPostId,        uint256 quotedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        return false;    }}

4

Implement the Process Repost function

Finally, implement the processRepost function. This function is invoked by the Feed contract every time a Repost to 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 of the Root Post (rootPostId), which is the Repostd Post's Root Post, where the rule was configured, the ID of the Reposted Post (repostedPostId), which is the ID of the Post that is being directly Reposted (rootPostId and repostedPostId will match in case of a root post being Reposted), the ID of the Repost Post being created (postId), 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 Repost operation, we must implement the function to comply with the interface but just return false.

contract LimitedRepliesPostRule is IPostRule {
    // . . .
    function processRepost(        uint256 rootPostId,        uint256 repostedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        return false;    }}

Now the LimitedRepliesPostRule is ready to be applied into any Post under any Feed. See the full code below:

contract LimitedRepliesPostRule is IPostRule {    mapping(address => mapping(uint256 => uint256)) internal _repliesLimit;    mapping(address => mapping(uint256 => uint256)) internal _repliesCounter;
    function configure(uint256 postId, bytes calldata data) external override {        _repliesLimit[msg.sender][postId] = abi.decode(data, (uint256));    }
    function processReply(        uint256 rootPostId,        uint256 repliedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        _repliesCounter[msg.sender][rootPostId]++;        require(_repliesCounter[msg.sender][rootPostId] <= _repliesLimit[msg.sender][rootPostId]);        return true;    }
    function processQuote(        uint256 rootPostId,        uint256 quotedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        return false;    }
    function processRepost(        uint256 rootPostId,        uint256 repostedPostId,        uint256 postId,        bytes calldata data    ) external view override returns (bool) {        return false;    }}

Stay tuned for API integration of rules and more guides!