Help & Support

Group Rules

This guide explains how to use Group Rules and how to implement custom ones.

Group Rules allow administrators to add requirements or constraints for joining or leaving a Group.

Lens provides four built-in Group rules:

  • SimplePaymentGroupRule - Requires an ERC-20 payment to join the Group.

  • TokenGatedGroupRule - Requires an account to hold a certain token to join the Group.

  • MembershipApprovalGroupRule - Requires approval by owner or administrators of the Group to join.

  • BanMemberGroupRule - Prevents an account from joining the Group.

It is also possible to use custom Group Rules to extend the functionality of your Group.

Using Group Rules

This section presumes you are familiar with the process of creating a Group on Lens.

Simple Payment Group Rule

This rule requires an ERC-20 payment to join a Group. Configuration includes the ERC-20 token address, the payment amount, and the recipient address.

Simple Payment Group Rule
import { bigDecimal, evmAddress, uri } from "@lens-protocol/client";import { createGroup } from "@lens-protocol/client/actions";
const result = await createGroup(sessionClient, {  metadataUri: uri("lens://4f91c…"),  rules: {    required: [      {        simplePaymentRule: {          cost: {            currency: evmAddress("0x5678…"),            value: bigDecimal("10.42"),          },          recipient: evmAddress("0x9012…"),        },      },    ],  },});

Token Gated Group Rule

This rule requires holding a certain balance of a token (fungible or non-fungible) to join a Group.

Configuration includes the token address, the token standard (ERC-20, ERC-721, or ERC-1155), and the required token amount. For ERC-1155 tokens, an additional token type ID is required.

Token Gated Group Rule
import {  bigDecimal,  evmAddress,  uri,  TokenStandard,} from "@lens-protocol/client";import { createGroup } from "@lens-protocol/client/actions";
const result = await createGroup(sessionClient, {  metadataUri: uri("lens://4f91c…"),  rules: {    required: [      {        tokenGatedRule: {          token: {            currency: evmAddress("0x1234…"),            standard: TokenStandard.Erc721,            value: bigDecimal("1"),          },        },      },    ],  },});

Membership Approval Group Rule

This rule requires approval by the owner or administrators to join a Group.

Membership Approval Group Rule
import {  bigDecimal,  evmAddress,  uri,  TokenStandard,} from "@lens-protocol/client";import { createGroup } from "@lens-protocol/client/actions";
const result = await createGroup(sessionClient, {  metadataUri: uri("lens://4f91c…"),  rules: {    required: [{ membershipApprovalRule: { enable: true } }],  },});

See the Membership Approvals guide for more information on how to handle membership approval.

Ban Member Group Rule

This rule prevents an account from joining a Group.

Ban Member Group Rule
import { evmAddress, uri } from "@lens-protocol/client";import { createGroup } from "@lens-protocol/client/actions";
const result = await createGroup(sessionClient, {  metadataUri: uri("lens://4f91c…"),  rules: {    required: [      {        banAccountRule: {          enable: true,        },      },    ],  },});

See the Banned Accounts guide for more information on how to handle banned accounts.

Custom Group Rules

You can also use custom rules by specifying the rule contract address, when it applies, and the configuration parameters as key-value pairs.

Custom Group Rule
import {  blockchainData,  evmAddress,  GroupRuleExecuteOn,  uri,} from "@lens-protocol/client";import { createGroup } from "@lens-protocol/client/actions";
const result = await createGroup(sessionClient, {  metadataUri: uri("lens://4f91c…"),  rules: {    required: [      {        unknownRule: {          address: evmAddress("0x1234…"),          executeOn: [GroupRuleExecuteOn.Joining],          params: [            {              raw: {                // 32 bytes key (e.g., keccak(name))                key: blockchainData("0xac5f04…"),                // an ABI encoded value                value: blockchainData("0x00"),              },            },          ],        },      },    ],  },});

Combining Rules

Additionally, multiple rules can be combined:

Combining Rules
import {  bigDecimal,  evmAddress,  TokenStandard,  uri,} from "@lens-protocol/client";import { createGroup } from "@lens-protocol/client/actions";
const result = await createGroup(sessionClient, {  metadataUri: uri("lens://4f91c…"),  rules: {    required: [      {        simplePaymentRule: {          cost: {            currency: evmAddress("0x5678…"),            value: bigDecimal("10.42"),          },          recipient: evmAddress("0x9012…"),        },      },    ],    anyOf: [      {        tokenGatedRule: {          token: {            currency: evmAddress("0x1234…"),            standard: TokenStandard.Erc721,            value: bigDecimal("1"),          },        },      },      {        tokenGatedRule: {          token: {            currency: evmAddress("0x3456…"),            standard: TokenStandard.Erc721,            value: bigDecimal("1"),          },        },      },    ],  },});

Update a Group Rules

To update a Group rules configuration, follow these steps.

You MUST be authenticated as a Builder, Account Manager, or Account Owner and be either the owner or an admin of the Group to update its rules.

1

Identify Current Rules

First, inspect the group.rules field to know the current rules configuration.

type GroupRules = {  required: GroupRule;  anyOf: GroupRule;};

Keep note of the Rule IDs you might want to remove.

2

Update the Rules Configuration

Next, update the rules configuration of the Group as follows.

Use the updateGroupRules action to update the rules configuration of a given group.

import { bigDecimal, evmAddress, TokenStandard } from "@lens-protocol/client";import { updateGroupRules } from "@lens-protocol/client/action";
const result = await updateGroupRules(sessionClient, {  group: group.address,  toAdd: {    required: [      {        tokenGatedRule: {          token: {            currency: evmAddress("0x1234…"),            standard: TokenStandard.Erc721,            value: bigDecimal("1"),          },        },      },    ],  },});

3

Handle Result

Then, handle the result using the adapter for the library of your choice:

import { handleOperationWith } from "@lens-protocol/client/viem";
// …
const result = await updateGroupRules({} as SessionClient, {  group: group.address,  // …}).andThen(handleOperationWith(walletClient));

See the Transaction Lifecycle guide for more information on how to determine the status of the transaction.

Building a Group 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 a custom Group Rule that requires accounts to have more than a certain amount of Followers in order to join the Group.

To build a custom Group Rule, you must implement the following IGroupRule interface:

interface IGroupRule {  function configure(bytes calldata data) external;
  function processJoining(    address account,    uint256 membershipId,    bytes calldata data  ) external returns (bool);
  function processRemoval(    address account,    uint256 membershipId,    bytes calldata data  ) external returns (bool);}

Each function of this interface must assume to be invoked by the Group 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 Group 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 two parameters: an address, representing the Graph where to perform the amount of followers check, and an integer, which will represent the minimum amount of Followers an account must have in order to become a member of the Group. Let's define a storage mapping to store this configuration:

contract FollowerCountGatedGroupRule is IGroupRule {    mapping(address => address) internal _graphToCheck;    mapping(address => uint256) internal _minFollowers;}

Now let's code the configure function itself, decoding the parameters and putting them into the storage:

contract FollowerCountGatedGroupRule is IGroupRule {    mapping(address => address) internal _graphToCheck;    mapping(address => uint256) internal _minFollowers;
    function configure(bytes calldata data) external {        (address graphToCheck, uint256 minFollowers) = abi.decode(data, (address, uint256));        _graphToCheck[msg.sender] = graphToCheck;        _minFollowers[msg.sender] = minFollowers;    }}

Both configuration parameters are stored in mappings that are using the Group contract address as the key, which is the msg.sender. In this way, the same rule can be reused by different Group contracts.

2

Implement the Process Joining function

Next, implement the processJoining function. This function is invoked by the Group contract every time someone tries to join the Group, so then our custom logic can be applied to shape under which conditions the operation can succeed.

The function receives the account attempting to join the Group, the membershipId (which mostly applies for Groups with tokenized memberships, so then you can join using an specific membership token), 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 joining process, we will return true.

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

contract FollowerCountGatedGroupRule is IGroupRule {    mapping(address => address) internal _graphToCheck;    mapping(address => uint256) internal _minFollowers;
    // . . .
    function processJoining(        address account,        uint256 membershipId,        bytes calldata data    ) external returns (bool) {        // We get the Graph where to check the followers count        IGraph graph = IGraph(_graphToCheck[msg.sender]);    }}

Next we require that account has at least the amount of followers required by the rule configuration.

The IGraph interface contains this function in order to check the amount of followers of a given account:

function getFollowersCount(address account) external view returns (uint256);

So, let's add the requirement check:

contract FollowerCountGatedGroupRule is IGroupRule {    mapping(address => address) internal _graphToCheck;    mapping(address => uint256) internal _minFollowers;
    // . . .
    function processJoining(        address account,        uint256 membershipId,        bytes calldata data    ) external returns (bool) {        IGraph graph = IGraph(_graphToCheck[msg.sender]);        // We check if the account has the min required amount of followers        require(graph.getFollowersCount(account) >= _minFollowers[msg.sender]);    }}

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

contract FollowerCountGatedGroupRule is IGroupRule {    mapping(address => address) internal _graphToCheck;    mapping(address => uint256) internal _minFollowers;
    // . . .
    function processJoining(        address account,        uint256 membershipId,        bytes calldata data    ) external returns (bool) {        IGraph graph = IGraph(_graphToCheck[msg.sender]);        require(graph.getFollowersCount(account) >= _minFollowers[msg.sender]);        // We return true to signal the rule applies to the joining operation        return true;    }}

3

Implement the Process Removal function

Finally, implement the processRemoval function. This function is invoked by the Group contract every time a Group member is trying to be removed, so then our rule can define if this operation must succeed or not.

Note that Group members can leave a Group by themselves at any time, without restriction. This function is called when a member is trying to be removed by another account, for example, by an administrator of the Group.

The function receives the account trying to be removed of the Group, the membershipId (so maybe the decision can be based on it, e.g. first ten members cannot be removed), 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 removal operation, so we must implement the function to comply with the interface but just return false.

contract FollowerCountGatedGroupRule is IGroupRule {
    // . . .
    function processRemoval(        address account,        uint256 membershipId,        bytes calldata data    ) external returns (bool) {        // We return false to signal the rule does not apply to the removal operation        return false;    }}

Now the FollowerCountGatedGroupRule is ready to be applied into any Group. See the full code below:

contract FollowerCountGatedGroupRule is IGroupRule {    mapping(address => address) internal _graphToCheck;    mapping(address => uint256) internal _minFollowers;
    function configure(bytes calldata data) external {        (address graphToCheck, uint256 minFollowers) = abi.decode(data, (address, uint256));        _graphToCheck[msg.sender] = graphToCheck;        _minFollowers[msg.sender] = minFollowers;    }
    function processJoining(        address account,        uint256 membershipId,        bytes calldata data    ) external returns (bool) {        // We get the Graph where to check the followers count        IGraph graph = IGraph(_graphToCheck[msg.sender]);        // We check if the account has the min required amount of followers        require(graph.getFollowersCount(account) >= _minFollowers[msg.sender]);        // We return true to signal the rule applies to the joining operation        return true;    }
    function processRemoval(        address account,        uint256 membershipId,        bytes calldata data    ) external returns (bool) {        // We return false to signal the rule does not apply to the removal operation        return false;    }}

Stay tuned for API integration of rules and more guides!