Integrate Modules
If you want to integrate a module in your dApp, you have all the information you need to do this without even speaking to the module creator.
Understanding the module metadata
We talked about the Module Metadata Standards, which talks about what information the module will expose to allow the integrators to know how to set them up and also allow consumers to execute them.
You can retrieve Module Metadata of registered modules by:
const result = await client.modules.fetchMetadata({
implementation: '0x345Cc3A3F9127DE2C69819C2E07bB748dE6E45ee'
});
if (result === null) {
// specified address is not registered
return
}
// use result
query {
moduleMetadata(request: { implementation: "0x345Cc3A3F9127DE2C69819C2E07bB748dE6E45ee" }) {
metadata {
name
title
description
authors
initializeCalldataABI
initializeResultDataABI
processCalldataABI
attributes {
type
key
value
}
}
moduleType
verified
signlessApproved
sponsoredApproved
}
}
The ModuleMetadataResult
object contains important informations about a given module.
type ModuleMetadataResultFragment = {
moduleType: ModuleType;
signlessApproved: boolean;
sponsoredApproved: boolean;
verified: boolean;
metadata: {
authors: string[];
description: string;
initializeCalldataABI: string;
initializeResultDataABI: string | null;
name: string;
processCalldataABI: string;
title: string;
attributes: MetadataAttribute;
};
};
type MetadataAttribute = {
type: MetadataAttributeType;
key: string;
value: string;
}
enum ModuleType {
Follow = 'FOLLOW',
OpenAction = 'OPEN_ACTION',
Reference = 'REFERENCE',
}
moduleType
specify the type of the module betweenFOLLOW
,OPEN_ACTION
,REFERENCE
signlessApproved
tells you if you can execute its logic via Lens Profile Manager. Each module type belong to their workflow so:- for Open Action module via the
actOnOpenAction
mutation - for Follow module via the
follow
mutation - for Reference module via
commentOnchain
in the case of comments
- for Open Action module via the
sponsoredApproved
tells you if tx gas costs can be subsidized by the Lens API via thebroadcastOnChain
mutationverified
tells you the module has been verified in https://github.com/lens-protocol/verified-modules/tree/master and has been approved safe for usage by the Lens team.metadata
contains the information the author provided via the Module Metadata Standard.
Of the metadata
fields thename
, title
, authors
, description
, attributes
provides useful information to developers on the module capabilities, and a way to contact the author(s).
For all operational needs the main thing you care about here as a consumer are the ABIs. ABIs are ways to decode and encode data so the chain can understand it.
These values are strings containing Solidity JSON ABI which describe how to encode/decode simple byte arrays (buffers of data).
initializeCalldataABI
: describes how to encode the initialization data when setting up the module.initializeResultDataABI
: describes how to decode the initialization result to so know contract specific outcome at initialization time. This is optional and some modules can leverage it to return you some more informations.processCalldataABI
: describes how to encode the execution data when the module logic is ultimately executed.
To aid the process of encoding/decoding data we provided some helper functions as part of the @lens-protocol/client
package which will be used in the following examples.
import { encodeData, decodeData } from '@lens-protocol/client';
Using custom modules in your app
Every module type has an Unknown<type>ModuleSettings
object:
UnknownOpenActionModuleSettings
UnknownFollowModuleSettings
UnknownReferenceModuleSettings
which is returned by the API as part of the relevant objects.
This object holds information on the data used to set up this module.
In the following, we use Open Actions as example but Follow modules, and Reference modules adopt the same consuming process.
Consuming a custom modules
If a publication is linked to a custom Open Action module and you want to integrate that module in your dApp, you will get information you care about in the correspondingUnknownOpenActionModuleSettings
which is returned as part of the openActionModules
array in the publication object.
The example below fetches it from a single publication query, but this is included anywhere a publication is brought back.
const post = await client.publication.fetch({
forId: '0x32-0x1d',
});
query {
publication(request: {
forId: "0x32-0x1d"
}) {
... on Post {
openActionModules {
... on UnknownOpenActionModuleSettings {
initializeCalldata
initializeResultData
verified
signlessApproved
sponsoredApproved
type
contract {
address
chainId
}
collectNft
}
}
}
}
}
You can use the @lens-protocol/client
type guards to help you find and narrow down to desired settings.
import {isUnknownOpenActionModuleSettings, UnknownOpenActionModuleSettingsFragment } from '@lens-protocol/client';
const openActionContract = '0xc0ffee254729296a45a3885639AC7E10F9d54979';
const settings = publication.openActionModules.find(
(module): module is UnknownOpenActionModuleSettingsFragment =>
isUnknownOpenActionModuleSettings(module) && module.contract.address === openActionContract,
);
The UnknownOpenActionModuleSettings
object looks like this:
type UnknownOpenActionModuleSettings = {
type: OpenActionModuleType;
collectNft: EvmAddress | null;
initializeCalldata: string | null;
initializeResultData: string | null;
signlessApproved: boolean;
sponsoredApproved: boolean;
verified: boolean;
contract: NetworkAddress;
};
{
"initializeCalldata": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d1f8a6d6584a1672d2817368783b9a2a36ae361000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000006aaf7c8516d0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000566d63f1cc7f45bfc9b2bdc785ffcc6f858f0997000000000000000000000000f87b6343c172720ac9cc7d1c9465d63454a8ef3000000000000000000000000007b722856369f6b923e1f276abca58dd3d15243d0000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d51446455596779615975777a6e4b646131797236716255514e32676334567659684b5a3773765344417061730000000000000000000000",
"initializeResultData": "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000006aaf7c8516d0c00000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000566d63f1cc7f45bfc9b2bdc785ffcc6f858f0997000000000000000000000000f87b6343c172720ac9cc7d1c9465d63454a8ef3000000000000000000000000007b722856369f6b923e1f276abca58dd3d15243d0000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d51446455596779615975777a6e4b646131797236716255514e32676334567659684b5a3773765344417061730000000000000000000000",
"verified": false,
"signlessApproved": false,
"sponsoredApproved": false,
"type": "UnknownOpenActionModule",
"contract": {
"address": "0x23Bace2E9571B7A8598c3314e5f0d8C12DBc674A",
"chainId": 80001
},
"collectNft": null
}
initializeCalldata
: contains the data as bytes used to initialize the Open Action module on this Publication. It can be decoded with the modulemetadata.initializeCalldataABI
.initializeResultData
: contains the data returned the initialization process. It can be decoded with the modulemetadata.initializeResultDataABI
.verified
,signlessApproved
, andsponsoredApproved
are the same you can retrieve from the module metadata and reported here for convenience.
With the ABIs retrieved from the corresponging Module Metadata you can decode initializeCalldata
and initializeResultData
like so:
import { decodeData } from '@lens-protocol/client';
const settings = ... // retrieved from a publication like explained before
const result = await client.modules.fetchMetadata({
implementation: openActionContract
});
// decode init data
const initData = decodeData(
JSON.parse(result.metadata.initializeCalldataABI),
settings.initializeCalldata
);
// decode init result if present
const initResult = decodeData(
JSON.parse(result.metadata.initializeResultDataABI),
settings.initializeResultData
);
Depending on the Open Action used you can use these informations to inform the user about the specific configuration of the Open Action for the current publication.
Processing a custom module
The above explains how you consume an Open Action custom modules. In the following we will show how to process the Open Action logic.
First of all you need to assemble the process calldata
.
import { encodeData } from '@lens-protocol/client';
const openActionContract = '0xc0ffee254729296a45a3885639AC7E10F9d54979';
const result = await client.modules.fetchMetadata({
implementation: openActionContract
});
const calldata = encodeData(
JSON.parse(result.metadata.processCalldataABI),
[ /* data according to ABI spec */ ]
)
Then you can process the Open Action:
const result = await client.publication.actions.actOn({
actOn: {
unknownOpenAction: {
address: openActionContract,
data: calldata
}
},
for: post.id,
});
// continue as usual
mutation {
actOnOpenAction(request: {
for: "0x32-0x1d",
actOn: {
unknownOpenAction: {
address: "0xc0ffee254729296a45a3885639AC7E10F9d54979",
data: "0x0000000000000000000000000000000001"
}
}
}) {
... on RelaySuccess {
txHash
txId
}
... on LensProfileManagerRelayError {
reason
}
}
}
The example above assumes that your profile has Lens Profile Manager enabled and that the given Open Action module has signlessApproved
set to true
.
If you don't want to use a signless experience you can also process the Open Action by creating typed data and broadcasting it manually like explained in the Open Actions section.
You can find an e2e example with all the steps above here.
As said before we focused on Open Action module but the same mechanics applies to Follow and Reference modules in their respective use cases.
Attaching a custom module
With the above information, you can consume and process a custom module. You understand how to decode and encode the data with the ABIs and how it holds together. You now may want to allow your users to be able to use the module for their own content/profile. As before we will focus on Open Action module in the examples.
The first step requires to assemble the initialization calldata
.
import { encodeData } from '@lens-protocol/client';
const openActionContract = '0xc0ffee254729296a45a3885639AC7E10F9d54979';
const result = await client.modules.fetchMetadata({
implementation: openActionContract
});
const calldata = encodeData(
JSON.parse(result.metadata.initializeCalldataABI),
[ /* data according to ABI spec */ ]
)
The you can create the publication with the desired Open Action.
import { encodeData } from '@lens-protocol/client';
const result = await client.publication.postOnchain({
contentURI: 'ipfs://Qm...', // or arweave
openActionModules: [
{
unknownOpenAction: {
address: openActionContract,
data: calldata
}
}
]
});
// continue as usual
mutation {
postOnchain(request: {
contentURI: "ar://ID",
openActionModules: [
{
unknownOpenAction: {
address: "0xc0ffee254729296a45a3885639AC7E10F9d54979",
data: "0x0000000000000000000000000000000002"
}
}
]
}) {
... on RelaySuccess {
txHash
txId
}
... on LensProfileManagerRelayError {
reason
}
}
}
The example above assumes that your profile has Lens Profile Manager enabled and that the given Open Action module has signlessApproved
set to true
.
If you don't want to use a signless experience you can also create the publication by creating typed data and broadcasting it manually like explained in the Create a post section.
The same mechanics applies to custom Reference and Follow modules in their respective use cases.
Updated 5 months ago