Help & Support

Post Actions

This guide will explain how to use Post Actions and how to implement custom ones.

Post Actions are contracts that extend the Lens Protocol functionality by allowing Accounts to execute actions on Posts.

Lens provides two built-in Post Actions:

  • SimpleCollectAction - Allows an Account to collect an NFT from a Post.

  • TippingPostAction - Allows an Account to tip the author of a Post.

It is also possible to create custom Post Actions to extend the functionality of the Lens Protocol.

Configuring Post Actions

You can configure one or more Post Actions when creating a Post.

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

Simple Collect Action

The SimpleCollectAction allows an user to collect an ERC-721 NFT from a given Post.

The NFT collection is eagerly deployed when the Post is created.

The Post author can configure the following parameters:

  • Collect limit (optional) – The maximum number of NFTs that can be minted.

  • End time (optional) – The deadline after which NFTs can no longer be collected.

  • Follower requirement (optional) – Whether the collector must be following the Post author on a specified Lens Graph (global or custom).

  • NFT immutability (optional) – If enabled, minted NFTs will retain a snapshot of the content URI at the time of minting. This ensures that any edits to the Post after minting will not affect previously minted NFTs. For this to be fully effective, the content URI itself should be immutable.

  • Price (optional) – The cost of the NFT. If set, the author can also specify:
    • Referral share (optional) – The percentage of the payment allocated to any referrers.

    • Revenue recipients (optional) – A list of addresses that will receive a share of the payment. This is after the referral share has been deducted.

import { dateTime, evmAddress, uri } from "@lens-protocol/client";
const result = await post(sessionClient, {  contentUri: uri("lens://4f91ca…"),  actions: [    {      simpleCollect: {        collectLimit: 100,        endsAt: dateTime("2032-12-22T00:00:00Z"),      },    },  ],});

Tipping Post Action

The TippingPostAction is enabled by default on any Post, so you don't need to configure it explicitly.

Custom Post Actions

Custom Post Action
import { blockchainData, evmAddress, uri } from "@lens-protocol/client";
const result = await post(sessionClient, {  contentUri: uri("lens://4f91ca…"),  actions: [    {      unknown: {        address: evmAddress("0x1234…"),        params: [          {            raw: {              // 32 bytes key (e.g., keccak(name))              key: blockchainData("0xac5f04…"),              // an ABI encoded value              value: blockchainData("0x00"),            },          },        ],      },    },  ],});

Executing Post Actions

To execute a Post Action, follow these steps.

You MUST be authenticated as Account Owner or Account Manager to execute a Post Action.

1

Inspect Post Actions

First, inspect the post.actions field to determine what Post Actions are available on a given Post.

Post Actions
for (const action of post.actions) {  switch (action.__typename) {    case "SimpleCollectAction":      // The Post has a Simple Collect Action      break;
    case "TippingPostAction":      // The Post has a Tipping Post Action      break;
    case "UnknownAction":      // The Post has a Custom Post Action      break;  }}

An example of each Post Action type is provided below.

{  "__typename": "SimpleCollectAction",  "amount": {    "__typename": "Erc20Amount",    "asset": {      "__typename": "Erc20",      "name": "Wrapped GHO",      "symbol": "wGHO",      "contract": {        "__typename": "NetworkAddress",        "address": "0x1234…",        "chainId": 37111      },      "decimals": 18    },    "value": "42.42"  },  "collectLimit": 100,  "endsAt": "2032-12-22T00:00:00Z",  "followerOnGraph": {    "__typename": "FollowerOn",    "globalGraph": true  },  "isImmutable": true,  "referralShare": 5, // 5%  "recipients": [    {      "__typename": "RecipientPercent",      "address": "0x5678…",      "percent": 30 // 30%    },    {      "__typename": "RecipientPercent",      "address": "0x9abc…",      "percent": 70 // 70%    }  ]}

2

Execute Post Action

Next, execute the desired Post Action.

Use the executePostAction action to execute any Post Action.

import { postId } from "@lens-protocol/client";import { executePostAction } from "@lens-protocol/client/actions";
const result = await executePostAction(sessionClient, {  post: postId("42"),  action: {    simpleCollect: {      selected: true,    },  },});
if (result.isErr()) {  return console.error(result.error);}

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 executePostAction(sessionClient, {  post: postId("42"),  action: {    // …  },}).andThen(handleOperationWith(walletClient));

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

That's it—you've successfully executed a Post Action.

Building a Post Action

What follows is an example of how to build a Post Action based on an early iteration. It's provided for illustrative purposes only. Updated documentation on the final implementation will be provided soon.

The Post Actions are defined by the IPostAction interface, which basically requires two functions, one to configure the action and another to execute it.

interface IPostAction {    event Lens_PostAction_Configured(      address indexed feed,      uint256 indexed postId,      bytes data    );
    event Lens_PostAction_Executed(      address indexed feed,      uint256 indexed postId,      bytes data    );
    function configure(      address feed,      uint256 postId,      bytes calldata data    ) external returns (bytes memory);
    function execute(      address feed,      uint256 postId,      bytes calldata data    ) external returns (bytes memory);}

Configuration

The configuration of the Action is done through the configure function, which purpose is to initialize any required state that the Action might require to work properly.

The function receives three parameters, the feed address and postId that together identifies the Post for which the Action is configured for, and the data bytes that will be decoded into any extra required custom configuration parameters that the Action could require.

The configure function returns bytes that in case the Action requires to return some custom information to the caller.

This function could be called by anyone, it will depend on the implementation of the Action and its purpose who is allowed to invoke it.

For example, there might be Actions that do not require initialization at all, so the configure function implementation will be empty, while other Actions might require the caller to match the author of the given Post.

Every time the configure function is called, the Action should emit a Lens_PostAction_Configured event matching the parameters of the call.

Execution

The Action execution is triggered by invoking the execute function.

The function receives three parameters, the feed address and postId that together identifies the Post for which the Action is executed on, and the data bytes that will be decoded into any extra required custom configuration parameters that the Action could require.

The execute function returns bytes that in case the Action requires to return some custom information to the caller.

Same as with the configure function, the execute function could be called by anyone, and it will depend on the implementation of the Action and its purpose who is allowed to invoke it.

For example, there might be Actions that can be executed by anyone, while other Actions might require the caller to match the Post's author, or to require some pre-condition that relates the caller with the Post's author in order to allow the execution.

Every time the execute function is called, the Action should emit a Lens_PostAction_Executed event matching the parameters of the call.

Example

Let's illustrate the process with an example. We will build a Pinning Action so an Author Account can signal to Pin or Unpin his Posts, then UIs supporting this action can display the Pinned posts in the Account's profile page.

1

Implement the Configure Function

First, implement the configure function. In our example, the Action does not require any state initialization.

We could implement the configure function with a simple return statement, but we will revert instead, so we don't even allow this function to be invoked successfully.

contract PinningPostAction is IPostAction {
    function configure(        address feed,        uint256 postId,        bytes calldata data    ) external pure override returns (bytes memory) {        revert(); // Configuration not needed for pinning.    }

2

Implement the Execute Function

Finally, implement the execute function.

We first require that the caller is the author of the Post trying to be pinned or unpinned.

To do this, we call use the following function present in the Feed's interface:

function getPostAuthor(uint256 postId) external view returns (address);

So, let's code this requirement:

contract PinningPostAction is IPostAction {
    // . . .
    function execute(        address feed,        uint256 postId,        bytes calldata data    ) external override returns (bytes memory) {        require(msg.sender == IFeed(feed).getPostAuthor(postId));    }}

Next, we use the data bytes to decode a boolean value representing if the Post should be Pinner or Unpinned. We do not use this boolean in our code, but we decode it anyways in order to ensure the data parameter contains indeed an encoded boolean, otherwise it will fail.

contract PinningPostAction is IPostAction {
    // . . .
    function execute(        address feed,        uint256 postId,        bytes calldata data    ) external override returns (bytes memory) {        require(msg.sender == IFeed(feed).getPostAuthor(postId));        (bool pinned) = abi.decode(data, (bool)); // We decode to make sure the data has the correct format.    }}

And to finish this implementation, we emit the Lens_PostAction_Executed event matching the parameters of the call and, given that we do not need to return anything to the caller, we just return empty bytes:

contract PinningPostAction is IPostAction {
    // . . .
    function execute(        address feed,        uint256 postId,        bytes calldata data    ) external override returns (bytes memory) {        require(msg.sender == IFeed(feed).getPostAuthor(postId));        // We decode to make sure the data has the correct format.        (bool pinned) = abi.decode(data, (bool));        emit Lens_PostAction_Executed(feed, postId, data);        return "";    }}

Now the PinningPostAction is ready to be used. See the full code below:

contract PinningPostAction is IPostAction {
    function configure(        address feed,        uint256 postId,        bytes calldata data    ) external pure override returns (bytes memory) {        revert(); // Configuration not needed for pinning.    }
    function execute(        address feed,        uint256 postId,        bytes calldata data    ) external override returns (bytes memory) {        require(msg.sender == IFeed(feed).getPostAuthor(postId));        // We decode to make sure the data has the correct format.        (bool pinned) = abi.decode(data, (bool));        emit Lens_PostAction_Executed(feed, postId, data);        return "";    }}