Transaction Lifecycle

This guide will help you manage the lifecycle of Lens transactions.


Tiered Transaction Model

The Lens API’s approach to write operations prioritizes user convenience through a tiered transaction model. This model spans from signless transactions to options requiring user signatures and gas fees, offering the best possible experience based on the individual user's circumstances.

There are three classes of operations in this model:

  • Social Operations: These operations can be done by an Account Manager on behalf of the owner.

  • Restricted Operations: These operations require the user's signature due to their nature.

  • Management Operations: These operations could be funded by the server if the user is eligible. These are used to facilitate management operations such as creating new Apps, creating Custom Graphs, etc.

The Lens API adapts its operation results based on user eligibility, ensuring users can proceed with the best available options, from the smoothest experience to necessary fallbacks.

Social Operations

The tiered transaction model for Social Operations is as follows:

1

Signless Sponsored Transaction - Best UX

  • Description: Automatically signed and sent by the Lens API, with gas fees sponsored.

  • Requirements: Available when the user enabled the signless experience, the user is eligible for sponsorship, and the operation is deemed secure for signless execution.

2

Sponsored Transaction Request

  • Description: Requires the user to sign and send a gasless transaction request, powered by ZKsync EIP-712.

  • Requirements: Available if the user is eligible for sponsorship but lacks signless support.

3

Self-Funded Transaction Request - Fallback

  • Description: Requires user signature and gas payment, following a standard EIP-1559 transaction request.

  • Requirements: Used when neither signless nor sponsored transactions are available.

Restricted Operations

The tiered transaction model for Restricted Operations is as follows:

1

Sponsored Transaction Request - Best UX

  • Description: Requires the user to sign and send a gasless transaction request, powered by ZKsync EIP-712.

  • Requirements: Available when the user is eligible for sponsorship.

2

Self-Funded Transaction Request - Fallback

  • Description: Requires user signature and gas payment, following a standard EIP-1559 transaction request.

  • Requirements: Used when the user is not eligible for sponsorship.

Management Operations

The tiered transaction model for Management Operations is as follows:

1

Sent Sponsored Transaction - Best UX

  • Description: Automatically signed and sent by the Lens API, with gas fees sponsored.

  • Requirements: Available when the user is eligible for sponsorship and the operation is deemed secure for signless execution.

2

Self-Funded Transaction Request - Fallback

  • Description: Requires user signature and gas payment, following a standard EIP-1559 transaction request.

  • Requirements: Used when the server deems the user as not eligible for sponsorship.

Operation Results

The tiered transaction model results is modelled as a union type with the possible outcomes for each operation type.

In the examples below the terms Operation Result and Operation Response are used as placeholders for the actual operation at hand (e.g., PostResult and PostResponse, FollowResult and FollowResponse).

type OperationResult =  | OperationResponse  | SponsoredTransactionRequest  | SelfFundedTransactionRequest  | TransactionWillFail;

The union could include additional failure scenarios specific to the operation.

Where:

  • OperationResponse: Indicates that the transaction was successfully sent and returns the transaction hash for further monitoring.

  • SponsoredTransactionRequest: Requests the user to sign and send the transaction, with gas fees covered.

  • SelfFundedTransactionRequest: Requests the user to sign and send the transaction, with the user covering gas fees.

  • TransactionWillFail: This is an omnipresent entry that, if returned, indicates that the transaction will fail with a specific reason.

type SponsoredTransactionRequest = {  reason: string;  sponsoredReason: string;  raw: Eip712TransactionRequest;};
type Eip712TransactionRequest = {  type: string;  to: string;  from: string;  nonce: number;  gasLimit: string;  gasPrice: string;  data: string;  value: string;  chainId: number;  customData?: {    gasPerPubdata?: string;    factoryDeps?: string[];    customSignature?: string;    paymasterParams?: {      paymaster: string;      paymasterInput: string;    };  };};

Both SponsoredTransactionRequest and SelfFundedTransactionRequest types include:

  • reason - a user-friendly message indicating the reason for the fallback, if any.

  • an enum reason field - a dev-friendly enum indicating the reason for the fallback, if any:
    • sponsoredReason: SIGNLESS_DISABLED, SIGNLESS_FAILED

    • selfFundedReason: NOT_SPONSORED, CANNOT_SPONSOR

  • raw - the transaction request details to be signed and sent by the user.

Use one of the provided adapters to handle the operation results with the user's wallet.

import type { WalletClient } from 'viem';import { post } from '@lens-protocol/client/actions';import { handleWith } from '@lens-protocol/client/viem';
const walletClient: WalletClient = ...;
const result = await post(sessionClient, { contentUri: "lens://…" })  .andThen(handleWith(walletClient));

Transaction Monitoring

At this point, whether you received an Operation Response or you sent a transaction request via the user's wallet (SponsoredTransactionRequest or SelfFundedTransactionRequest), you should have a transaction hash.

Chain the sessionClient.waitForTransaction method to monitor the transaction's until it's fully mined and indexed.

const result = await post(sessionClient, { contentUri: "lens://…" })  .andThen(handleWith(walletClient))  .andThen(sessionClient.waitForTransaction);
if (result.isOk()) {  console.log("Transaction indexed:", result.value); // "0x1234…"} else {  console.error("Transaction failed:", result.error);}

If you are more familiar with a Promise-based approach, you can use the waitForTransaction method directly:

const result = await post(sessionClient, { contentUri: "lens://…" });
if (result.isErr()) {  return console.error("Transaction failed:", result.error);}
switch (result.value.__typename) {  case "PostResponse":    await sessionClient.waitForTransaction(result.value.hash);    break;
  case "SponsoredTransactionRequest":    const hash = await sendEip712Transaction(walletClient, {      data: transaction.raw.data,      gas: BigInt(transaction.raw.gasLimit),      gasPrice: BigInt(transaction.raw.gasPrice),      nonce: transaction.raw.nonce,      paymaster: transaction.raw.customData.paymasterParams?.paymaster,      paymasterInput:        transaction.raw.customData.paymasterParams?.paymasterInput,      to: transaction.raw.to,      value: BigInt(transaction.raw.value),    });    break;
  case "SelfFundedTransactionRequest":    const hash = await sendTransaction(walletClient, {      data: transaction.raw.data,      gas: BigInt(transaction.raw.gasLimit),      maxFeePerGas: BigInt(transaction.raw.maxFeePerGas),      maxPriorityFeePerGas: BigInt(transaction.raw.maxPriorityFeePerGas),      nonce: transaction.raw.nonce,      to: transaction.raw.to,      type: "eip1559",      value: BigInt(transaction.raw.value),    });    break;
  default:    console.error("Failed to post due to:", result.value.reason);}