Reference Modules
Learn how to discover the supported Reference Modules
Reference Modules are smart contracts that encapsulate the Reference policies for a Lens Publication.
Discover Modules
If you're interested, you can delve into the available Reference Modules for the protocol.
These modules encompass both known supported modules, which come with detailed information, and unknown supported modules, which provide basic contract information.
- JavaScript SDK
- API
You can use the client.modules.supportedReferenceModules method to list all the supported Reference Modules.
import { LensClient, development } from '@lens-protocol/client';
const client = new LensClient({ environment: development,});
const page = await client.modules.supportedReferenceModules({ 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 have the module address, you can find more information about it through its Module Metadata, if available.
Unknown Modules
Unknown Reference Modules are typically created by the community and are not fully modelled by the Lens API GraphQL schema, hence the term unknown.
To use an Unknown Reference Module, you should be well-versed in the use of built-in Refence policies.
The following sections assume that you already know the address of the Unknown Reference Module you're interested in:
constants.ts
export const referenceContract = "0x83E5aA2E3f46D3fad010d2cbC5F6Cb90Ec440aA5";
Get Module Metadata
To effectively utilize Unknown Reference Modules, it's advisable to retrieve the metadata of any registered module you intend to support, in advance.
You can accomplish this at runtime in your app as follows:
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.
Alternatively, you can manually fetch the metadata once and store the relevant details in your application, associating them with the corresponding module address.
You can preemptively JSON parse the metadata.initializeCalldataABI, metadata.initializeResultDataABI, and metadata.processCalldataABI fields for later use.
constants.ts
export const referenceContract = "0x83E5aA2E3f46D3fad010d2cbC5F6Cb90Ec440aA5";
export const initializeCalldataABI: ModuleParam[] = [ /* ... */];
export const initializeResultDataABI: ModuleParam[] = [ /* ... */];
export const processCalldataABI: ModuleParam[] = [ /* ... */];
ModuleParam is a type available in @lens-protocol/react-web, @lens-protocol/react-native, and @lens-protocol/client packages.
In the subsequent sections, we'll proceed under the assumption that you've opted for the constants file approach. However, fetching the metadata at runtime is also a viable option.
Initialize Module
In this section we'll create a Post that make use of the Unknown Reference Module of choice. However, the same principles apply to Comments and Quotes as well.
Familiarity with Content Creation is required.
First, you need to encode the initialization data for the Unknown Reference Module. This can be done using the initializeCalldataABI from the Module Metadata. The specifics of this data will depend on the chosen Unknown Reference Module.
Next, create the Post with the Unknown Reference Module of choice and the encoded initialization data. Some modules might not require initialization data.
- React SDK
- JavaScript SDK
- API
React SDK
import { ReferencePolicyType, useCreatePost } from "@lens-protocol/react-web";
import { referenceContract } from "./constants";
// ...
const { execute, loading, error } = useCreatePost();
// ...
const result = await execute({ metadata: "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", reference: { type: ReferencePolicyType.UNKNOWN, address: referenceContract, data: calldata, },});
// continue as usual
If the Unknown Reference Module requires ERC-20 tokens at initialization time, it's the integrator's responsibility to determine whether an approve transaction is necessary. In such cases, the _spender should be the address of the Unknown Reference Module, and the _value should be the amount of tokens that need to be pre-approved, as per the initialization data.
That's it—you created a publication with an Unknown Reference Module.
Process Module
In this section, we'll reference a Publication that uses the Unknown Reference Module of choice.
Familiarity with Referencing Content is required.
You need to follow these steps:
The first step is to identify a publication that uses the Unknown Reference Module of interest.
- React SDK
- JavaScript SDK
Create a type guard specifically for the Unknown Reference Module you're interested in. Use this type guard to identify the UnknownReferencePolicy within the publication.
Next, you can decode initializeCalldata and initializeResultData using the ABIs retrieved from the corresponding Module Metadata.
- React SDK
- JavaScript SDK
decodeData available in @lens-protocol/react-web, @lens-protocol/react-native packages.
import { decodeData } from "@lens-protocol/react-web";
import { initializeCalldataABI, initializeResultDataABI } from "./constants";
// ...
// decode init dataconst initData = decodeData(initializeCalldataABI, policy.initializeCalldata);
// decode init result, if presentconst initResult = decodeData( initializeResultDataABI, policy.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 Reference Module.
Next, you encode the processing data for the Unknown Reference Module. This can be done using the processCalldataABI from the Module Metadata. The specifics of this data will depend on the chosen Unknown Reference Module.
- React SDK
- JavaScript SDK
encodeData available in @lens-protocol/react-web, @lens-protocol/react-native packages.
import { encodeData } from "@lens-protocol/react-web";
import { processCalldataABI } from "./constants";
const calldata = encodeData(processCalldataABI, [ /* data according to Unknown Reference Module process spec */]);
Finally, create the reference publication using the encoded processing data from the previous step.
- React SDK
- JavaScript SDK
Use the useCreateComment, useCreateMirror, or useCreateQuote hooks to create the desired reference publication with the processing data.
The React SDK retrieves the Module Metadata of the parent publication's Unknown Reference Module. It then uses the signlessApproved and sponsoredApproved values to determine the user experience.
The hooks prioritize the Signless Experience if it's enabled and the module is signlessApproved. Otherwise, they default to a signed experience.
If the module is not sponsoredApproved, the hooks will initiate a Self-funded Transaction flow.
If the Unknown Reference Module requires ERC-20 tokens at processing time, it's the integrator's responsibility to determine whether an approve transaction is necessary. In such cases, the _spender should be the address of the Unknown Reference Module, and the _value should be the amount of tokens that need to be pre-approved, as per the initialization data.
That's it—you referenced a publication that uses an Unknown Reference Module.
Comments and Quotes, in turn, can have their own Reference policies, potentially utilizing an Unknown Reference Module too. In the example below, we create a Comment that employs the same Unknown Reference Module as its parent publication.
Example
import { encodeData, ReferencePolicyType, useCreateComment,} from "@lens-protocol/react-web";
import { initializeCalldataABI, processCalldataABI, referenceContract,} from "./constants";
// ...
const { execute, loading, error } = useCreateComment();
// ...
const result = await execute({ commentOn: publication.id, metadata: "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
commentOnReferenceCalldata: encodeData(initializeCalldataABI, [ /* data according to Unknown Reference Module initialization spec */ ]),
reference: { type: ReferencePolicyType.UNKNOWN, address: referenceContract, data: encodeData(processCalldataABI, [ /* data according to Unknown Reference Module process spec */ ]),, },});
// continue as usual
Create Reference Module
In this guide, we'll use the existing FollowerOnlyReferenceModule as an example to demonstrate the process of creating a custom Reference Module. This particular module permits users to reference (through commenting, mirroring, or quoting) a publication only if they are following the publication's author.
After reading this guide, see Registering a Module to learn how to register your module in the protocol.
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, navigate to the contracts/modules/reference directory and open the file named FollowerOnlyReferenceModule.sol. For now, we've omitted the implementation details to focus on the interface.
contracts/modules/reference/FollowerOnlyReferenceModule.sol
pragma solidity ^0.8.10;
import {IReferenceModule} from 'contracts/interfaces/IReferenceModule.sol';import {HubRestricted} from 'contracts/base/HubRestricted.sol';import {Types} from 'contracts/libraries/constants/Types.sol';import {FollowValidationLib} from 'contracts/modules/libraries/FollowValidationLib.sol';import {LensModuleMetadata} from 'contracts/modules/LensModuleMetadata.sol';
contract FollowerOnlyReferenceModule is LensModuleMetadata, HubRestricted, IReferenceModule { function supportsInterface(bytes4 interfaceID) public pure override returns (bool) { return interfaceID == type(IReferenceModule).interfaceId || super.supportsInterface(interfaceID); }
constructor( address hub, address moduleOwner ) HubRestricted(hub) LensModuleMetadata(moduleOwner) {}
function initializeReferenceModule( uint256 profileId, uint256 pubId, address transactionExecutor, bytes calldata data ) external pure returns (bytes memory) { // implementation goes here }
function processComment( Types.ProcessCommentParams calldata processCommentParams ) external view override returns (bytes memory) { // implementation goes here }
function processQuote( Types.ProcessQuoteParams calldata processQuoteParams ) external view override returns (bytes memory) { // implementation goes here }
function processMirror( Types.ProcessMirrorParams calldata processMirrorParams ) external view override returns (bytes memory) { // implementation goes here }}
The IReferenceModule interface consists of four functions, each with a specific purpose:
initializeReferenceModule is invoked when a publication is created with this module attached as its reference module.
processComment is triggered when a user attempts to comment on a publication that uses this module as its reference module.
processQuote is triggered when a user attempts to quote a publication that uses this module as its reference module.
processMirror is triggered when a user attempts to mirror a publication that uses this module as its reference module.
Initialization
In this reference module, there's no need for any setup during the initialization phase, hence the implementation appears as follows.
/** * @notice Initializes data for the given publication being published with this Reference module. * @custom:permissions LensHub. * * @param profileId The token ID of the profile publishing the publication. * @param pubId The associated publication's LensHub publication ID. * @param transactionExecutor The address of the transaction executor (e.g. for any funds to transferFrom). * @param data Arbitrary data passed from the user to be decoded by the Reference Module during initialization. * * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by * indexers or UIs. */function initializeReferenceModule( uint256 profileId, uint256 pubId, address transactionExecutor, bytes calldata data) external pure returns (bytes memory) { return '';}
However, the initializeReferenceModule function is the place where you would set up the initial state or perform any necessary actions when the module is attached to a publication.
Processing
The processing phase is triggered when a user attempts to comment, quote, or mirror a publication that uses this module as its reference module. Each type of action can have different logic. In this example, all actions share the same logic; they must confirm that the user is following the author of the publication.
First, we create a function to validate the following between two profile Ids:
function _performFollowerOnlyCheck( uint256 followerProfileId, uint256 followedProfileId) internal view returns (bytes memory) { FollowValidationLib.validateIsFollowingOrSelf(HUB, followerProfileId, followedProfileId); return '';}
Next, we invoke this function in each of the processing methods. For illustrative purposes, we're only demonstrating one of them below:
/** * @notice Processes a comment being published. This includes any module logic like transferring tokens, * checking for conditions (e.g. token-gated), etc. * @custom:permissions LensHub. * * @param processCommentParams The parameters for processing a comment. * * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by * indexers or UIs. */function processComment( Types.ProcessCommentParams calldata processCommentParams) external view override returns (bytes memory) { return _performFollowerOnlyCheck({ followerProfileId: processCommentParams.profileId, followedProfileId: processCommentParams.pointedProfileId });}
You can find the complete implementation of the IReferenceModule interface, annotated with detailed comments for your understanding, in the Lens Protocol smart contracts repository.
Recap
Congratulations! You've successfully created a reference module. You can view the complete implementation of the FollowerOnlyReferenceModule in the Lens Protocol smart contracts repository.