Post Actions

This guide explains 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.

Post Action Initialization

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 a 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.

    • Recipients – A list of addresses that will receive a share of the payment. This is after the referral share has been deducted.

For paid collects, a 1.5% Lens treasury fee is deducted from the total amount paid by the collector before calculating referral and recipient shares.

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

Lens includes a built-in TippingPostAction, which is enabled by default and allows an Account to tip the author of a Post using native GHO (GRASS on the Lens Testnet) or ERC20 token.

The TippingPostAction supports a referral scheme that lets you reward accounts or apps that helped surface the tipped Post.

A maximum of 20% of the tipped amount can be allocated to referrals, but only if they're explicitly included when the tip is sent.

A 1.5% Lens treasury fee is deducted from the total amount paid by the tipper before calculating referral and recipient shares.

Payment Source

Post Actions can use funds from either the Signer or the Lens Account to pay for tips or collects.

  • Signer refers to the Account Owner or Account Manager, depending on the authentication role.

  • Lens Account is the account you are currently logged in with.

Referral Fee Breakdown

Let’s say a user tips the author of a Post with 100 GHO using the TippingPostAction.

Here's how the amount is split:

  • 1.5 GHO (1.5%) is deducted for the Lens treasury fee

  • 98.5 GHO remains

The user includes two referral recipients, fully allocating the maximum 20% referral fee between them:

  • 0xc0ffee with a 30% share of the referral portion

  • 0xbeef with a 70% share of the referral portion

From the remaining 98.5 GHO:

  • 19.7 GHO (20%) is distributed as referrals:
    • 5.91 GHO to 0xc0ffee

    • 13.79 GHO to 0xbeef

  • 78.8 GHO is sent to the Post author

If the referral split adds up to less than 100% (e.g. a single referral with 50%), only the corresponding portion of the 20% referral fee will be used. The unused remainder goes to the Post author.

Custom Post Actions

You can create Posts with custom Post Actions by specifying the action's contract address and encoded parameters. The Lens SDK provides the encodeKeyValuePairs utility to help encode parameters based on the action's metadata.

Consider a post action with the following metadata:

{  "__typename": "UnknownPostAction",  "address": "0x1234…",  "metadata": {    "authors": ["[email protected]", "[email protected]"],    "configureParams": [      {        "__typename": "KeyValuePair",        "key": "0x6bbaf20e3c4b9cd2bcb0a17bbe156c6fdaaeda4a64626e09a17f2283321a6f72",        "name": "lens.param.foo",        "type": "string"      }    ],    "description": "This is a sample action.",    "executeParams": [],    "id": "123e4567-e89b...",    "name": "SampleAction",    "setDisabledParams": [],    "source": "https://github.com/example/repo"  }}

To create a post with this post action, follow these steps:

1

Encode Parameters

First, use the encodeKeyValuePairs utility for the wallet library of your choice to encode the parameters into a list of key value pairs.

import { evmAddress, uri } from "@lens-protocol/client";import { encodeKeyValuePairs } from "@lens-protocol/client/viem";
const encodedParams = encodeKeyValuePairs(  { "lens.param.foo": "bar" },  actionMetadata.configureParams,);

2

Create the Post

Then, create the post with the post action.

Use the encodedParams from the previous step as shown below.

import { evmAddress, uri } from "@lens-protocol/client";
const result = await post(sessionClient, {  contentUri: uri("lens://4f91ca…"),  actions: [    {      unknown: {        address: evmAddress("0x1234…"),        params: encodedParams,      },    },  ],});

Executing Post Actions

To execute a Post Action, follow these steps.

You MUST be authenticated as the 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 "UnknownAction":      // The Post has a Custom Post Action      break;  }}

An example of each Post Action type is provided below.

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

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);}

In case of Simple Collect Action and Tipping Post Action, you can also specify the payment source to be from the user's wallet by means of the PaymentSource enum.

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

You can also specify the referrals for the Simple Collect Action and Tipping Post Action in order to reward accounts or apps that helped surface the given post.

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,      referrals: [        {          address: "0x5678…",          percent: 30,        },        {          address: "0x9abc…",          percent: 70,        },      ],    },  },});
if (result.isErr()) {  return console.error(result.error);}

Custom actions may require execution parameters that need to be encoded before calling the action. You can execute a custom post action with the executePostAction method using the encoded parameters as shown in Encode Parameters.

import { evmAddress, postId } from "@lens-protocol/client";import { executePostAction } from "@lens-protocol/client/actions";
const result = await executePostAction(sessionClient, {  post: postId("42"),  action: {    unknown: {      address: evmAddress("0x1234…"),      params: encodedParams,    },  },});
if (result.isErr()) {  return console.error(result.error);}

3

Handle Result

Then, the result will indicate what steps to take next.

import { handleOperationWith } from "@lens-protocol/client/viem";
// …
const operation = await executePostAction(sessionClient, {  post: postId("42"),  action: {    // …  },});
if (operation.isErr()) {  return console.error(operation.error);}
switch (operation.value.__typename) {  case "InsufficientFunds":    // handle insufficient funds scenario    return console.log("Insufficient funds to perform the action");
  case "SignerErc20ApprovalRequired":    // handle ERC20 approval required scenario leveraging operation.value.amount: Erc20Amount    return console.log("Signer ERC20 approval required to perform the action");}
const result = await operation  .asyncAndThen(handleOperationWith(wallet))  .andThen(sessionClient.waitForTransaction);

See the ERC20 token approval guide for more information on how to handle ERC20 token approval.

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

Action Execution History

The Lens Protocol provides a method to track which accounts have executed specific actions on a Post.

Use the paginated fetchWhoExecutedActionOnPost action to get a list of accounts that executed a specific Action on a Post.

import { postId } from "@lens-protocol/client";import { fetchWhoExecutedActionOnPost } from "@lens-protocol/client/queries";
const result = await fetchWhoExecutedActionOnPost(client, {  post: postId("42"),});
if (result.isErr()) {  return console.error(result.error);}
// items: Array<PostExecutedActions>const { items, pageInfo } = result.value;

Configure Post Actions

You can add a Post Action to a Post at any time. For example, this can be useful to re-enable the Simple Collect Action on posts migrated from Lens Protocol v2 to v3.

You MUST be authenticated as the Account Owner or Account Manager of the Post's author account to configure Post Actions on it.

1

Prepare the Request

First, use the configurePostAction action to configure a Post Action on a Post.

import { evmAddress, postId } from "@lens-protocol/client";import { configurePostAction } from "@lens-protocol/client/actions";
const result = await configurePostAction(sessionClient, {  post: postId("42"),  params: {    simpleCollect: {      payToCollect: {        collectLimit: 100,        erc20: {          value: "100",          currency: evmAddress("0x5678…"),        },      },    },  },});

2

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

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

Enable/Disable Post Actions

You can enable or disable a Post Action on a Post at any time, provided that the Post Action has already been configured for that Post. Post Actions can be configured either during Post creation or later, as described in the Configure Post Actions section above.

You MUST be authenticated as the Account Owner or Account Manager of the Post's author account to enable or disable Post Actions.

1

Prepare the Request

First, enable or disable a specific Post Action invoking the corresponding enablePostAction or disablePostAction actions.

import { postId } from "@lens-protocol/client";import { enablePostAction } from "@lens-protocol/client/actions";
const result = await enablePostAction(sessionClient, {  post: postId("1234…"),  action: { simpleCollect: true },});

2

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 enablePostAction(sessionClient, {  post: postId("1234…"),  action: { simpleCollect: true },}).andThen(handleOperationWith(walletClient));

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

Building a Post Action

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

interface IPostAction {    function configure(address originalMsgSender, address feed, uint256 postId, KeyValue[] calldata params)        external        returns (bytes memory);
    function execute(address originalMsgSender, address feed, uint256 postId, KeyValue[] calldata params)        external        returns (bytes memory);
    function setDisabled(        address originalMsgSender,        address feed,        uint256 postId,        bool isDisabled,        KeyValue[] calldata params    ) external returns (bytes memory);}

But first, let's talk about the ActionHub

Before we dive into each of the functions from the IPostAction interface, we need to talk about the ActionHub.

The ActionHub is a special contract that acts as the entry point for every Action in the Lens Protocol.

The purpose of that is to help discovery of Actions, having a single point where all main Action-related events are emitted.

So, each function of the IPostAction interface must be only callable by ActionHub. For this, you can inherit base contracts like BasePostAction or OwnableMetadataBasedPostAction, which act as templates for your custom Post Action, applying the restrictions needed and including metadata and parameter details, so you do not need to worry about it.

With this context, you can now understand why the first param of every IPostAction function is originalMsgSender: this is the address that called the ActionHub originally, either to configure, execute, or disable an Action, given that msg.sender will always have the ActionHub address in the context of your Action contract.

Deployment

Post Action deployment requires both metadata creation (to describe your action's parameters and behavior) and smart contract deployment. This ensures UIs can discover and properly interact with your action.

1

Create Action Metadata

First, construct an Action Metadata object with the necessary content.

Use the @lens-protocol/metadata package to construct a valid ActionMetadata object:

Example
import { action } from "@lens-protocol/metadata";
const metadata = action({  name: "SimplePollVoteAction",  description:    "Allows users to cast a boolean vote (Yes/No) on a post with single-vote enforcement. Prevents double voting.",  authors: ["[email protected]"],  source: "https://github.com/lens-protocol/lens-post-action-boilerplate",  executeParams: [    {      key: "0xf1d961d1860db912f7c57ff7ec8e742cb92089b269e42c6fba52c85bcbdf21d8",      name: "lens.param.vote",      type: "bool",    },  ],});

2

Upload Metadata

Next, upload the Action Metadata object to a public URI.

import { storageClient } from "./storage-client";
const { uri } = await storageClient.uploadAsJson(metadata);
console.log(uri); // e.g., lens://4f91ca…

This example uses Grove storage to host the Metadata object. See the Lens Metadata Standards guide for more information on hosting Metadata objects.

3

Deploy Action Contract

Finally, deploy your Post Action smart contract with the metadata URI. The metadata URI is passed to the constructor and defines the action name, description, and parameter definitions for execution.

Constructor Example
constructor(    address actionHub,    address owner,    string memory metadataUri) OwnableMetadataBasedPostAction(actionHub, owner, metadataUri) {}

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 four parameters:

  • originalMsgSender: the address of the original msg.sender that invoked the ActionHub (as explained above).

  • feed: the address of the Feed where the Post the Action is being configured for belongs to.

  • postId: the ID of the Post for which the Action is configured for.

  • params: array of key-value pairs whose values can be decoded into any extra custom configuration parameters that the Action could require to work.

Return of the configure function is bytes - some Actions might want to return custom information to the caller.

This configure function could be called by anyone (through the ActionHub, as explained above), it will depend on the implementation of the Action and its purpose who is allowed to invoke it (based on originalMsgSender).

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.

Keep in mind that if no prior configuration is required, the configure function must still be implemented and must not revert.

Every time the configure function is called, the ActionHub will emit a Lens_ActionHub_PostAction_Configured event matching the parameters of the call (or Lens_ActionHub_PostAction_Reconfigured if the configuration was updated).

Execution

The execution of the Action is done through the execute function, which purpose is to perform the actual action logic that the Action implements.

The function receives four parameters:

  • originalMsgSender: the address of the original msg.sender that invoked the ActionHub (as explained above).

  • feed: the address of the Feed where the Post the Action is being executed for belongs to.

  • postId: the ID of the Post for which the Action is being executed.

  • params: array of key-value pairs whose values can be decoded into any extra custom execution parameters that the Action could require to work.

Return of the execute function is bytes - some Actions might want to return custom information to the caller.

This execute function could be called by anyone (through the ActionHub, as explained above), it will depend on the implementation of the Action and its purpose who is allowed to invoke it (based on originalMsgSender).

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

Every time the execute function is called, the ActionHub will emit a Lens_ActionHub_PostAction_Executed event matching the parameters of the call.

The ActionHub will not allow to invoke the execute function on a Post if the Action is disabled for it.

Disabling

The disabling of the Action is done through the setDisabled function, which purpose is to stop an Action to be executable for a given Post. The same function can be used to enable the Action back.

The function receives five parameters:

  • originalMsgSender: the address of the original msg.sender that invoked the ActionHub (as explained above).

  • feed: the address of the Feed where the Post the Action is being disabled/enabled for belongs to.

  • postId: the ID of the Post for which the Action is being disabled/enabled.

  • isDisabled: boolean indicating if the Action is being disabled or enabled.

  • params: array of key-value pairs whose values can be decoded into any extra custom disabling/enabling parameters that the Action could require to work.

Return of the setDisabled function is bytes - some Actions might want to return custom information to the caller.

This setDisabled function could be called by anyone (through the ActionHub, as explained above), it will depend on the implementation of the Action and its purpose who is allowed to invoke it (based on originalMsgSender).

Every time the setDisabled function is called, the ActionHub will emit a Lens_ActionHub_PostAction_Disabled event matching the parameters of the call.

Create a Custom Post Action

For developers starting with custom Post Actions, the Post Action boilerplate provides a complete foundation for building, deploying, and testing custom actions.

Included in the boilerplate are:

  • /contracts: A sample SimplePollVoteAction smart contract.

  • /deploy: Scripts for deploying the custom action with metadata.

  • /test: Examples of tests for your actions.

  • hardhat.config.ts: A Hardhat configuration file customized for Lens Chain.

Getting Started

Make sure you have the Node.js >= v20.

If you use nvm to manage your Node.js versions, you can run nvm use from within the project directory to switch to the correct Node.js version.

Enable Corepack, if it isn't already; this will allow you to use the correct Yarn version:

corepack enable

Then, follow these steps to get started:

1

Clone the Repository

Clone the boilerplate repository into a new project directory:

git clone https://github.com/lens-protocol/lens-post-action-boilerplate.git my-post-actioncd my-post-action

2

Install Dependencies

Install the project dependencies:

yarn install

3

Setup Environment

Create .env file from the .env.example template:

cp .env.example .env

and populate the PRIVATE_KEY environment variable:

.env
PRIVATE_KEY=0x…

with the private key of an account with Lens Chain tokens.

Use network faucets to obtain tokens for testing.

Usage

The project includes several yarn scripts designed to streamline your workflow:

  • yarn compile: Compiles the action contracts.

  • yarn deploy --script deploy-simple-poll-vote-action.ts --network lensTestnet: Deploys and verifies the custom action with metadata.

  • yarn test: Executes tests against local ZKsync node.

  • yarn clean: Removes build artifacts from the project.

  • yarn lint: Lints the Solidity code.

For detailed instructions on how to utilize these scripts, refer to the project's README.md file.

Example

Let's illustrate the process with an example. We will build a Simple Poll Vote Action so users can vote (e.g., Yes/No) on a Post representing a poll. This action allows any user to vote once per post.

1

Define the Event and State

First, we define an event PollVoted to signal when a vote occurs and a mapping _hasVoted to track addresses that have already voted on a specific post to prevent double voting.

The constructor receives the ActionHub address, contract owner, and metadata URI that describes the action's interface and parameters.

// SPDX-License-Identifier: MITpragma solidity ^0.8.20;
import { IPostAction } from "contracts/interfaces/IPostAction.sol";import { KeyValue } from "contracts/core/types/Types.sol";import { OwnableMetadataBasedPostAction } from "contracts/actions/post/base/OwnableMetadataBasedPostAction.sol";
contract SimplePollVoteAction is OwnableMetadataBasedPostAction {    event PollVoted(address indexed voter, uint256 indexed postId, bool vote);
    mapping(address feed => mapping(uint256 postId => mapping(address voter => bool hasVoted))) private _hasVoted;
    mapping(address feed => mapping(uint256 postId => mapping(bool vote => uint256 count))) private _voteCounts;
    constructor(        address actionHub,        address owner,        string memory metadataURI    ) OwnableMetadataBasedPostAction(actionHub, owner, metadataURI) {}

2

Implement the Configure Function

Next, implement the configure function. Although this simple poll action doesn't store complex configuration state on-chain (like the specific poll question), it's crucial for the post author to explicitly enable or "attach" this action to their post. This signals intent and allows UIs to discover that the post is intended as a poll.

We add a requirement that originalMsgSender (the address calling the ActionHub) must be the author of the postId on the given feed.

// ... inside SimplePollVoteAction ...
function _configure(    address originalMsgSender,    address feed,    uint256 postId,    KeyValue[] calldata params) internal override returns (bytes memory) {    require(        originalMsgSender == IFeed(feed).getPostAuthor(postId),        "Only author can configure"    );    // Any extra configuration logic could be added here (e.g. mapping each possible vote type to some string)    // Emitting an event Lens_ActionHub_PostAction_Configured happens automatically via ActionHub    return "";}

3

Implement the Execute Function

Implement the execute function. This is where the core voting logic resides.

First, we check if the originalMsgSender (the user initiating the action via the ActionHub) has already voted on this postId. If they have, the transaction reverts.

// ... inside SimplePollVoteAction ...
function _execute(    address originalMsgSender,    address feed,    uint256 postId,    KeyValue[] calldata params) external override returns (bytes memory) {    require(!_hasVoted[feed][postId][originalMsgSender], "Already voted");
    _hasVoted[feed][postId][originalMsgSender] = true;
    bool voteFound;    bool vote;    for (uint256 i = 0; i < params.length; i++) {      if (params[i].key == keccak256("lens.param.vote")) {          voteFound = true;          vote = abi.decode(params[i].value, (bool));          break;      }    }
    require(voteFound, "Vote not found in params");
    _voteCounts[feed][postId][vote]++;
    return abi.encode(vote);}

We can also add some getters for vote counts and then the SimplePollVoteAction is ready to be deployed and used.

See the full code below:

// SPDX-License-Identifier: MITpragma solidity ^0.8.20;
import { IPostAction } from "contracts/extensions/action/ActionHub.sol";import { KeyValue } from "contracts/core/types/Types.sol";import { BasePostAction } from "contracts/actions/post/base/BasePostAction.sol";import { IFeed } from "contracts/interfaces/IFeed.sol";
/** * @title SimplePollVoteAction * @notice A simple post action allowing users to cast a boolean vote (e.g., Yes/No) on a post. *         Prevents double voting. */contract SimplePollVoteAction is BasePostAction {    event PollVoted(address indexed voter, uint256 indexed postId, bool vote);
    // feed => postId => voter => hasVoted    mapping(address => mapping(uint256 => mapping(address => bool))) private _hasVoted;
    /**      * @notice Configures the SimplePollVote Action for a given post.      * @param originalMsgSender The address initiating the configuration via the ActionHub. Must be post author.      * @param feed The address of the feed contract where the post exists.      * @param postId The ID of the post being configured.      * @param params Not used      * @return bytes Empty bytes.     */    function _configure(        address originalMsgSender,        address feed,        uint256 postId,        KeyValue[] calldata params    ) internal override returns (bytes memory) {        require(            originalMsgSender == IFeed(feed).getPostAuthor(postId),            "Only author can configure"        );        // Any extra configuration logic could be added here (e.g. mapping each possible vote type to some string)        // Emitting an event Lens_ActionHub_PostAction_Configured happens automatically via ActionHub        return "";    }
    /**     * @notice Executes a vote on a given post.     * @param originalMsgSender The address initiating the vote via the ActionHub.     * @param feed The address of the feed contract where the post exists.     * @param postId The ID of the post being voted on.     * @param params Array of key-value pairs. Expected to contain at least one element,     *        where the `value` of the first element is the ABI-encoded boolean vote.     * @return bytes Empty bytes.     * Requirements:     * - The `originalMsgSender` must not have voted on this `postId` before.     * - `params` must not be empty and the first element's value must be abi-decodable as a boolean.     */    function _execute(        address originalMsgSender,        address feed,        uint256 postId,        KeyValue[] calldata params    ) external override returns (bytes memory) {        require(!_hasVoted[feed][postId][originalMsgSender], "Already voted");
        _hasVoted[feed][postId][originalMsgSender] = true;
        bool voteFound;        bool vote;        for (uint256 i = 0; i < params.length; i++) {          if (params[i].key == keccak256("lens.param.vote")) {              voteFound = true;              vote = abi.decode(params[i].value, (bool));              break;          }        }
        require(voteFound, "Vote not found in params");
        _voteCounts[feed][postId][vote]++;
        return abi.encode(vote);    }
    /**     * @notice Gets the vote counts for a specific post.     * @param feed The address of the feed contract where the post exists.     * @param postId The ID of the post to get vote counts for.     * @return (uint256, uint256) A tuple containing the counts for false and true votes respectively.     */    function getVoteCounts(address feed, uint256 postId) external view returns (uint256 ya, uint256 nay) {        return (_voteCounts[feed][postId][false], _voteCounts[feed][postId][true]);    }}