Account Actions
This guide will explain how to use Account Actions and how to implement custom ones.
Account Actions are contracts that extend the Lens Protocol functionality by allowing Accounts to execute actions on other Accounts.
Lens includes a built-in TippingPostAction, which is enabled by default and allows an Account to tip another Account.
Custom Account Actions can also be created but require explicit configuration.
Configuring Account Actions
Adding Account Actions
To configure an Account Action, follow these steps.
You MUST be authenticated as Account Owner or Account Manager of the Account you intend to configure an Account Action for.
- TypeScript
- GraphQL
First, use the configureAccountAction action to configure a new Account Action.
Custom Account Action
import { blockchainData, evmAddress } from "@lens-protocol/client";import { configureAccountAction } from "@lens-protocol/client/actions";
const result = await configureAccountAction(dessionClient, { action: { unknown: { address: evmAddress("0x1234…"), params: [ { raw: { // 32 bytes key (e.g., keccak(name)) key: blockchainData("0xac5f04…"), // an ABI encoded value value: blockchainData("0x00"), }, }, ], }, },});
- TypeScript
- GraphQL
- React
Then, handle the result using the adapter for the library of your choice:
See the Transaction Lifecycle guide for more information on how to determine the status of the transaction.
Executing Account Actions
To execute an Account Action, follow these steps.
You MUST be authenticated as Account Owner or Account Manager to execute an Account Action.
First, inspect the target account.actions field to determine what Account Actions are available on it.
Account Actions
for (const action of post.actions) { switch (action.__typename) { case "TippingAccountAction": // The Account has a Tipping Account Action enabled break;
case "UnknownAction": // The Account has a Custom Account Action break; }}
See some examples below.
Next, execute the desired Account Action.
- TypeScript
- GraphQL
- React
Use the executeAccountAction action to execute an Account Action.
- TypeScript
- GraphQL
- React
Then, handle the result using the adapter for the library of your choice:
See the Transaction Lifecycle guide for more information on how to determine the status of the transaction.
That's it—you've successfully executed an Account Action.
Building an Account Action
What follows is an example of how to build a Account Action based on an early iteration. It's provided for illustrative purposes only. Updated documentation on the final implementation will be provided soon.
The Account Actions are defined by the IAccountAction interface, which basically requires two functions, one to configure the action and another to execute it.
interface IAccountAction { event Lens_AccountAction_Configured(address indexed account, bytes data);
event Lens_AccountAction_Executed(address indexed account, bytes data);
function configure( address account, bytes calldata data ) external returns (bytes memory);
function execute( address account, 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 two parameters, the account address 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 account parameter passed-in.
Every time the configure function is called, the Action should emit a Lens_AccountAction_Configured event matching the parameters of the call.
Execution
The Action execution is triggered by invoking the execute function.
The function receives two parameters, the account address where the Action is being executed on and the data bytes that will be decoded into any extra required custom execution 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 account parameter passed-in, or to require some pre-condition that relates the caller with the account parameter in order to allow the execution.
Every time the execute function is called, the Action should emit a Lens_AccountAction_Executed event matching the parameters of the call.
Example
Let's illustrate the process with an example. We will build a Tipping Action between Accounts.
First, implement the configure function. Given that ERC-20 tokens allow to do transfer without prior approval of the recipient, it does not make much sense to require configuration for this Action.
We want Accounts to use this Action instead of transferring tokens directly to the recipient, so then Apps can notify the recipient that someone tipped them.
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 TippingAccountAction is IAccountAction {
function configure(address account, bytes calldata data) external pure override returns (bytes memory) { revert(); // Configuration not needed for tipping. }
Finally, implement the execute function. We use the data bytes to decode the ERC-20 token address and the tip amount to transfer, requiring the tip amount to be greater than zero.
contract TippingAccountAction is IAccountAction {
// . . .
function execute( address account, bytes calldata data ) external override returns (bytes memory) { (address erc20Token, uint256 tipAmount) = abi.decode(data, (address, uint256)); require(tipAmount > 0); }}
Next, we transfer the tip amount from the caller to the recipient Account:
contract TippingAccountAction is IAccountAction {
// . . .
function execute( address account, bytes calldata data ) external override returns (bytes memory) { (address erc20Token, uint256 tipAmount) = abi.decode(data, (address, uint256)); require(tipAmount > 0); IERC20(erc20Token).transferFrom(msg.sender, account, tipAmount); }}
And to finish this implementation, we emit the Lens_AccountAction_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 TippingAccountAction is IAccountAction {
// . . .
function execute( address account, bytes calldata data ) external override returns (bytes memory) { (address erc20Token, uint256 tipAmount) = abi.decode(data, (address, uint256)); require(tipAmount > 0); IERC20(erc20Token).transferFrom(msg.sender, account, tipAmount); emit Lens_AccountAction_Executed(account, data); return ""; }}
Now the TippingAccountAction is ready to be used. See the full code below:
contract TippingAccountAction is IAccountAction {
function configure(address account, bytes calldata data) external pure override returns (bytes memory) { revert(); // Configuration not needed for tipping. }
function execute( address account, bytes calldata data ) external override returns (bytes memory) { (address erc20Token, uint256 tipAmount) = abi.decode(data, (address, uint256)); require(tipAmount > 0); IERC20(erc20Token).transferFrom(msg.sender, account, tipAmount); emit Lens_AccountAction_Executed(account, data); return ""; }}