Post Actions
This guide will explain Post Actions and how to implement them.
Post Actions are contracts that extend the Lens Protocol functionality by allowing Accounts to execute custom actions over or through Posts.
Post Action Interface
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.
Building an Post Action
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.
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. }
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)); (bool pinned) = abi.decode(data, (bool)); // We decode to make sure the data has the correct format. 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)); (bool pinned) = abi.decode(data, (bool)); // We decode to make sure the data has the correct format. emit Lens_PostAction_Executed(feed, postId, data); return ""; }}