Follow Module

Learn how to discover, utilize, and create new Follow Modules.


Discover Modules

You can delve into the various Follow Modules available for the protocol. These modules are categorized into two types: known supported modules, which come with detailed information, and unknown supported modules, which provide basic contract information.

You can use the client.modules.supportedFollowModules method to list all the supported Follow Modules.

import { LensClient, development } from '@lens-protocol/client';
const client = new LensClient({  environment: development,});
const page = await client.modules.supportedFollowModules({  includeUnknown: true,  onlyVerified: true});

The method returns a PaginatedResult<T> where T is KnownSupportedModuleFragment or UnknownSupportedModuleFragment. For more information on pagination, refer to this guide.

Once you know the module address, you can learn more about it via its Module Metadata (if present).


Unknown Follow Modules

To use an Unknown Follow Module, you should be well-versed in the use of built-in Follow policies. If you need a refresher, refer to our guide here.

The following sections assume that you already know the address of the Unknown Follow Module you're interested in:

const followContract = "0x83E5aA2E3f46D3fad010d2cbC5F6Cb90Ec440aA5";

Get Module Metadata

Often, you'll need the information contained in the Module Metadata to utilize the module.

In this section, we'll briefly show you how to do this. For more details, see Retrieve Module Metadata.

import { useLazyModuleMetadata } from "@lens-protocol/react-web";
// ...
const { execute } = useLazyModuleMetadata();
// ... in an async functionconst result = await execute({ implementation: followContract });
// handle retrieval errorsif (result.isFailure()) {  console.error(result.error.message);  return;}
const { metadata, sponsoredApproved, signlessApproved, verified } =  result.value;

Unverified modules, denoted by verified: false, have not undergone review by the Lens Protocol team. These modules may contain bugs or malicious code. Avoid integrating them into production unless you fully understand their functionality and risk associated. For more information, refer to the Verified Modules guide.

Set Up Profile

First, you need to encode the initialization data for the Unknown Follow Module. This can be done using the initializeCalldataABI from the Module Metadata. The specifics of this data will depend on the chosen Unkown Follow Module.

import { encodeData, ModuleParam } from "@lens-protocol/react-web";
//...
const abi = JSON.parse(metadata.initializeCalldataABI) as ModuleParam[];const calldata = encodeData(abi, [  /* data according to Follow Module initialization spec */]);

Next, you'll use the sponsoredApproved flag to determine the appropriate transaction flow:

Sponsored Follow Modules

If the chosen Unknown Follow Module is sponsored approved, you can utilize the Sponsored Transaction flow to set it as the Profile Follow Module.

The hook prioritizes the Signless Experience when available; if not, it resorts to a signed experience.

import {  FollowPolicyType,  useUpdateFollowPolicy,} from '@lens-protocol/react-web';
// ...
const { execute } = useUpdateFollowPolicy();
// ...
const result = await execute({  followPolicy: {    type: FollowPolicyType.UNKNOWN,    address: followContract,    data: calldata,  },});

Next, proceed as outlined in the Update Follow Policy section.

Self-Funded Follow Modules

If the chosen Unknown Follow Module is not sponsored approved, you'll need to use a Self-Funded Transaction to set it as the Profile Follow Module.

Using the Lens React SDK, you can simply set the sponsored flag to false. You can further streamline your code by passing the metadata.sponsoredApproved as shown in the example below.

AnyoneCanFollow.tsx
import {  FollowPolicyType,  useUpdateFollowPolicy,} from '@lens-protocol/react-web';
// ...
const { execute } = useUpdateFollowPolicy();
// ...
const result = await execute({  followPolicy: {    type: FollowPolicyType.UNKNOWN,    address: followContract,    data: calldata,    sponsored: metadata.sponsoredApproved  },});
// continue with result as usual

Follow a Profile

To support an Unknown Follow Module, you first need to identify a Profile that is using such a module.

With the help of the resolveFollowPolicy helper, you can distinguish between different types of Follow policies using the FollowPolicyType enum.

import {  FollowPolicyType,  resolveFollowPolicy,} from "@lens-protocol/react-web";
// ...
const followPolicy = resolveFollowPolicy(profile);
if (   // is an UnknownFollowPolicy  followPolicy.type === FollowPolicyType.UNKNOWN &&
  // it's implemented by the Follow Module we are interested in  followPolicy.contractAddress === followContract) {  // then followPolicy contains details about the Unknown Follow Module used}

The UnknownFollowPolicy object contains several pieces of information, including:

  • initializeCalldata: This contains the data used to initialize the Follow Module. It can be decoded with the module's metadata.initializeCalldataABI.

  • initializeResultData: This contains the data returned by the initialization process. It can be decoded with the module's metadata.initializeResultDataABI.

  • verified, signlessApproved, and sponsoredApproved: These are the same values you can retrieve from the module metadata and are reported here for convenience.

Next, using the ABIs obtained from the Module Metadata, you can decode initializeCalldata and initializeResultData as follows:

import { decodeData, ModuleParam } from "@lens-protocol/react-web";
// decode init dataconst initData = decodeData(  JSON.parse(metadata.initializeCalldataABI) as ModuleParam[],  settings.initializeCalldata);
// decode init result, if presentconst initResult = decodeData(  JSON.parse(metadata.initializeResultDataABI) as ModuleParam[],  settings.initializeResultData);

You can now use the decoded data to tailor the user experience of your application. The specifics of this data will depend on the chosen Unknown Follow Module.

The initializeResultData is only present if the specific Follow Module returns data during its initialization process.

To follow such a profile, you need to encode the processing data using the metadata.processCalldataABI ABI.

import { encodeData, ModuleParam } from "@lens-protocol/react-web";
//...
const abi = JSON.parse(metadata.initializeCalldataABI) as ModuleParam[];const calldata = encodeData(abi, [  /* data according to Follow Module initialization spec */]);

The sponsoredApproved attribute of the module will now determine the transaction flow:

Sponsored Follow Modules

If the chosen Unknown Follow Module is approved for sponsorship, you can use the Sponsored Transaction flow to set it as the Profile Follow Module.

The useUpdateFollowPolicy hook prioritizes the Signless Experience when possible. If not, it defaults to a signed experience.

Sponsored Follow
import { useFollow } from '@lens-protocol/react-web';
// ...const { execute } = useFollow();
// ...
const result = await execute({  profile,  data: calldata});

Next, proceed as outlined in the Follow section.

Self-Funded Follow Modules

If the chosen Unknown Follow Module is not approved for sponsorship, you'll need to use a Self-Funded Transaction to set it as the Profile Follow Module.

With the Lens React SDK, you can simply set the sponsored flag to false. You can further streamline your code by passing the followPolicy.sponsoredApproved as shown in the example below.

Self-funded Follow
import {  FollowPolicyType,  resolveFollowPolicy,  useFollow,} from '@lens-protocol/react-web';
// ...
const { execute } = useFollow();
// ...
const followPolicy = resolveFollowPolicy(profile);
// ...
const result = await execute({  profile,  data: calldata  sponsored: followPolicy.sponsoredApproved});
// continue with result as usual

Create Follow Module

We will use a hypothetical Follow Module as an example to illustrate the process of creating a custom Follow Module. This Follow Module allows users to follow the Profile using it, provided they include a secret code.

After reading this guide, see Registering a Module to learn how to register your module in the protocol.

This example is purely for instructional purposes. For the sake of simplicity, we'll assume that it's acceptable to have the code in plain text within the contract state.

The Basics

Make sure you have a copy of the Lens Protocol repository and your environment is set up correctly. For guidance, refer to our setup guide.

Next, create a new file named SecretCodeFollowModule.sol in the contracts/modules/follow directory. This file will house the code for our new module.

contracts/modules/follow/SecretCodeFollowModule.sol
pragma solidity 0.8.19;
import { IFollowModule } from 'contracts/interfaces/IFollowModule.sol';import { HubRestricted } from 'contracts/base/HubRestricted.sol';

contract SecretCodeFollowModule is HubRestricted, IFollowModule {  constructor(address hub) HubRestricted(hub) {}
  function initializeFollowModule(    uint256 profileId,    address transactionExecutor,    bytes calldata data  )    external    override    onlyHub    returns (bytes memory) {    // your implementation goes here  }
  function processFollow(    uint256 followerProfileId,    uint256 followerTokenId    address transactionExecutor,    uint256 targetProfileId,    bytes calldata data  ) external override {    // your implementation goes here  }}

The IFollowModule interface consists of two functions, each with a specific purpose:

  1. initializeFollowModule is invoked when a profile designates this module as its follow module.

  2. processFollow is triggered when a user tries to follow a profile that has this module set as its follow module.

Initialization

Here's how this module will function:

  1. It will allow profile owners to set a secret number as a passcode during the initialization of the follow module.

  2. It will only permit users to follow if they provide the correct passcode.

To meet the above criteria, we need some additional features. Firstly, we need a place to store the passcodes. Secondly, we need a method for profile owners to set these passcodes during initialization. Lastly, we need a way to verify that users attempting to follow have provided the correct passcode.

Let's add a new mapping called _passcodeByProfile above our constructor. This will store the passcodes. We'll also create a new error, PasscodeInvalid(), which we'll throw when users provide the wrong passcode:

...contract SecretCodeFollowModule is HubRestricted, IFollowModule {    error PasscodeInvalid();
    mapping(uint256 => uint256) internal _passcodeByProfile;
    constructor...

The mapping we just created will use profile IDs as keys and their respective passcodes as values. It's quite straightforward! Now, let's construct our initialization mechanism using the initializeFollowModule() function:

...function initializeFollowModule(  uint256 profileId,  address transactionExecutor,  bytes calldata data)  external  override  onlyHub  returns (bytes memory){  uint256 passcode = abi.decode(data, (uint256));  _passcodeByProfile[profileId] = passcode;  return data;}...

To clarify, we first decode the passcode from the arbitrary data (provided by the profile owner), then we assign it as the profile's passcode.

You might be curious about why this function returns a bytes memory parameter. Essentially, this represents any data that alters the state and should be emitted by an event. In our case, we'll simply pass the original data because it includes the passcode, which we're using to change the state.

Processing

We're almost there! The final step is to ensure that users provide the correct passcode when attempting to follow. As this function does not modify the state but merely reads from it, we can also restrict its visibility to view:

...function processFollow(  uint256 followerProfileId,  uint256 followerTokenId  address transactionExecutor,  uint256 targetProfileId,  bytes calldata data) external view override {  uint256 passcode = abi.decode(data, (uint256));  if (passcode != _passcodeByProfile[profileId]) {    revert PasscodeInvalid();  }}...

To recap, the first line of our function decodes the passcode from the arbitrary data (provided by the user attempting to follow). The second line halts the execution if the provided passcode is incorrect.

There's no need to designate this function as onlyHub because it doesn't alter any state.

As a best practice, it's advisable to limit the scope of functions as much as possible. In Solidity, functions that only read from the state should be marked as view, while those that neither read nor modify the state should be marked as pure. You might have noticed that followModuleTransferHook() could also be marked as pure, even though it doesn't serve any purpose in this case since the function is empty.

Recap

Congratulations! You've successfully created your own follow module. Let's review the complete SecretCodeFollowModule.sol file:

pragma solidity 0.8.19;
import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';import {HubRestricted} from 'contracts/base/HubRestricted.sol';
contract SecretCodeFollowModule is HubRestricted, IFollowModule {  error PasscodeInvalid();
  mapping(uint256 => uint256) internal _passcodeByProfile;
  constructor(address hub) HubRestricted(hub) {}
  function initializeFollowModule(    uint256 profileId,    address transactionExecutor,    bytes calldata data  )    external    override    onlyHub    returns (bytes memory)  {    uint256 passcode = abi.decode(data, (uint256));    _passcodeByProfile[profileId] = passcode;    return data;  }
  function processFollow(    uint256 followerProfileId,    uint256 followerTokenId    address transactionExecutor,    uint256 targetProfileId,    bytes calldata data  ) external view override {    uint256 passcode = abi.decode(data, (uint256));    if (passcode != _passcodeByProfile[profileId]) {      revert PasscodeInvalid();    }  }}