Collectable Publications
Learn the process of creating collectable Publications and the methods to collect them.
A collectable Publication is an on-chain Lens Publication that is configured with a Collect Action Module. This module, which implements the Open Action specification, allows users to mint new NFTs from a given Publication.
Collect Actions
The Lens Protocol includes two built-in Collect Action Modules:
Simple Collect Action Module
Multi-recipient Collect Action Module
The NFTs that are minted will reference the same Publication Metadata object as the original Publication. For more information on NFT-specific properties, refer to the Marketplace Metadata section.
This section assumes you are familiar with the Content Creation process. While we will use a Post to explain the concepts, the same principles apply to Comments and Quotes as well.
Simple Collect
The Simple Collect Action Module is one of the most versatile modules in the Lens Protocol. It enables you to create a collectable publication that could have one or more of the following features:
Only allows the author's followers to collect it
Requires collectors to pay a fee to collect
Specifies a percentage of the collect fee to be paid to referrers as a reward
Limits the total number of NFTs minted from the Publication
Enforces a time limit after which the Publication can no longer be collected
Below are some examples of the Simple Collect Action Module in action. You can mix and match the different options to create the collectable Publication that best suits your needs.
Limited Supply Collectable Available Only to Followers with an Expiry Date
Collectable with Fee and Referral Reward
Multi-Recipient Collect
The Multi-recipient Collect Action Module provides the same features as the Simple Collect Action Module, but with the added ability to specify multiple recipients for the collect fee.
Given that the primary objective of this module is to divide the collect fee among multiple recipients, the fee amount is always required.
We will focus our examples on showcasing how to leverage the multi-recipient capability of this module.
Collecting a Publication
Next, let's look at a collectable Publication from the perspective of a potential collector.
The examples below demonstrate this with a Post. However, the same principles apply to all Post, Comment, and Quote publications, regardless of the API request that returns them.
Read Collect Criteria
The initial step in implementing any Collect functionality is to understand the collect criteria of the target Publication. Such information can be used to inform the user about the requirements for collecting the Publication.
- React SDK
- Others
The resolveCollectPolicy helper function can be used to retrieve Collect Action Modules settings from the publication.openActionModules property and convert it into a developer-friendly CollectPolicy.
Available in @lens-protocol/react-web and @lens-protocol/react-native
Collecting with a Profile
If you're logged in with a Profile, you can collect a collectable Publication using a Sponsored Transaction and the Signless Experience, if it's enabled.
- React SDK
- JavaScript SDK
- API
Once you've determined that the target Publication is collectable, you can proceed to collect it using the useOpenAction hook.
Available in @lens-protocol/react-web and @lens-protocol/react-native
This hook prioritizes the Signless Experience when available; otherwise, it resorts to a signed experience.
CollectButton.tsx
import { OpenActionKind, useOpenAction } from "@lens-protocol/react-web";
// ...
const { execute } = useOpenAction({ action: { kind: OpenActionKind.COLLECT, },});
// ...
const result = await execute({ publication: post,});
if (result.isFailure()) { // error handling return;}
// continue ...
Next, the Result<T, E> object returned by the execute callback can be used to differentiate between successful requests and potential failures.
In addition to the standard error handling, the useFollow hook can yield two additional errors:
InsufficientAllowanceError: This error occurs when the user's wallet has not approved the Collect Action Module contract to access the required collect fee amount.
InsufficientFundsError: This error occurs when the user's wallet does not have sufficient funds to pay the collect fee.
CollectButton.tsx
if (result.isFailure()) { switch (result.error.name) { case 'InsufficientAllowanceError': window.alert( 'You must approve the contract to spend at least: +' formatAmount(result.error.requestedAmount) ); break;
case 'InsufficientFundsError': window.alert( 'You do not have enough funds to pay for this collect fee: '+ formatAmount(result.error.requestedAmount) ); break;
// handle other errors }
// eager return return;}
// success ...
To enhance the user experience, you can manage the InsufficientAllowanceError by prompting the user to approve the underlying Collect Action Module contract to spend the necessary amount.
On the other hand, when the result is successful, the collect operation is optimistically reflected on the target Publication, affecting the following properties:
post.operations.canCollect: This value is set to TriStateValue.No while the transaction is being processed to prevent multiple concurrent collect attempts.
post.operations.hasCollected.value: This value is set to true.
post.operations.hasCollected.isFinalisedOnchain: This value is set to false.
post.stats.collects: This counter is incremented by one.
Optionally, you can monitor the transaction's status for the full completion of the collect operation using the result.value object.
CollectButton.tsx
const completion = await result.value.waitForCompletion();
// handle mining/indexing errorsif (completion.isFailure()) { window.alert(completion.error.message); return;}
window.alert("Collect operation finalized on-chain");
Regardless of whether you wait for completion, the post object will be updated at the end of the process as follows:
post.operations.canCollect: This value is set to TriStateValue.Yes.
post.operations.hasCollected.isFinalisedOnchain: This value will be set to true.
However, if the collect operation fails, these changes will be reverted.
In summary, a possible implementation of <CollectButton> would look like this:
CollectButton.tsx
import { AnyPublication, OpenActionKind, useOpenAction,} from "@lens-protocol/react-web";
type CollectButtonProps = { /** * The Publication to collect */ publication: AnyPublication;};
export function CollectButton(props: CollectButtonProps) { const { execute, loading } = useOpenAction({ action: { kind: OpenActionKind.COLLECT, }, });
const collect = async () => { // execute the collect action const result = await execute({ publication: props.publication, });
// handle relaying errors if (result.isFailure()) { window.alert(result.error.message); return; }
window.alert("Publication collected"); };
return ( <button onClick={collect} disabled={loading}> Collect </button> );}
While only Post, Comment, and Quote, also known as Primary Publications, are directly collectable, the useOpenAction hook can be used with any Publication type, including Mirrors. This is why AnyPublication is used in the example above. The hook will automatically dereference and use the mirrored publication.
Collecting with a Wallet
You can also collect a collectable Publication using just a Wallet, provided you have authenticated with a Wallet-only flow.
Currently, collecting a publication with just a Wallet can only be done in a self-funded manner.
- React SDK
- Others
You should utilize the useOpenAction hook, but with the Self-Funded Transaction method.
The difference from the profile approach is minimal, so we will only highlight the relevant parts.
CollectButton.tsx
const { execute, loading } = useOpenAction({ action: { kind: OpenActionKind.COLLECT, },});
// ...
// execute the collect actionconst result = await execute({ publication: props.publication, sponsored: false});
// continue as usual
We utilized the sponsored flag to force a direct contract call to the LensHub contract. This means that the user's wallet will cover the gas cost for the corresponding transaction.
That's it—you've just learned how to create collectable Publications, and how to collect them using a Profile or a Wallet.
Additional Options
ERC-20 Approvals
When collecting a Publication with a collect fee, an ERC-20 Approval may be required for the operation to succeed.
An ERC-20 Approval transaction doesn't affect the user's ERC20 balance. Instead, it authorizes the approved address (in this case, a Collect Action module contract) to withdraw the specified amount at a later time through an additional transaction.
- React SDK
- Others
You can perform an ERC-20 Approval for a Publication with a collect fee either upfront or in response to an InsufficientAllowanceError when attempting to collect the Publication.
Start by using the useApproveModule hook on the target publication object.
CollectButton.tsx
import { useApproveModule } from "@lens-protocol/react-web";
// ...
const approve = useApproveModule();
// ...
const result = await approve.execute({ on: publication });
Next, you can use the Result<T, E> object returned by the function to distinguish between successful requests and potential failures.
CollectButton.tsx
if (result.isFailure()) { switch (result.error.name) { case 'InsufficientGasError': window.alert( `The user's wallet does not have enough MATIC to pay for the transaction` ); break;
case 'TransactionError': window.alert('There was an processing the transaction', result.error.message); break;
case 'PendingSigningRequestError': window.alert( 'There is a pending signing request in your wallet. ' + 'Approve it or discard it and try again.' ); break;
case 'WalletConnectionError': window.alert('There was an error connecting to your wallet', result.error.message); break;
case 'UserRejectedError': // the user decided to not sign, usually this is silently ignored by UIs break; }
// eager return return;}
// continue with the collect operation
Upon a successful result, the ERC-20 Approval transaction is completed, and you can proceed with the collect operation, as shown in the following recap example.
CollectButton.tsx
import { AnyPublication, OpenActionKind, useApproveModule, useOpenAction,} from "@lens-protocol/react-web";
// ...
const { approve } = useApproveModule();const { collect } = useOpenAction({ action: { kind: OpenActionKind.COLLECT, },});
// ...
const approveCollectModuleFor = async (publication: AnyPublication) => { const result = await approve({ on: publication });
if (result.isFailure()) { window.alert(result.error.message); return; }
// try again the collect operation return collect(profile);};
const collectPublication = async (publication: AnyPublication) => { const result = await collect({ publication });
if (result.isFailure()) { switch (result.error.name) { case "InsufficientAllowanceError": return approveCollectModuleFor(publication);
// other errors handling }
return; }
// successful collect window.alert("You have collected this publication");};
// ...
By default, the useApproveModule hook executes an ERC-20 Approval for the exact amount required. However, you can pre-approve an unlimited amount by passing the limit argument:
CollectButton.tsx
import { useApproveModule, TokenAllowanceLimit } from "@lens-protocol/react-web";
// ...
const { execute, error, loading } = useApproveModule({ limit: TokenAllowanceLimit.INFINITE,});
That's it—you now have a complete flow for collect a Publication with a collect fee.