Broadcast Onchain Transaction


Broadcasting transactions is free on testnet (Mumbai) but for production (Mainnet) you must be on the whitelist

Broadcasting transactions enables a gasless experience for users when interacting with the Lens Protocol. Unlike using the dispatcher, they must provide a signature through signing a message with their wallet.

Using this approach, you must assemble the typed data for the action the user wants to take using the provides meethods (e.g. createPostTypedData), the profile wallet must then sign this data and the signature is used to relay the transaction onchain.

Use the transaction broadcasting for post, comment, and mirror related actions.

If the user has the dispatcher enabled, you should use that mechanism over broadcasting.

Broadcast a Transaction


  • id: broadcastId (required
    • This is the id field that is returned from the typed data calls (create*TypedData).
  • signature :Signature (required)
    • The signed typed data.


const broadcastResult = await lensClient.transaction.broadcastOnchain({
mutation BroadcastOnchain($request: BroadcastRequest!) {
  broadcastOnchain(request: $request) {
    ... on RelaySuccess {
    ... on RelayError {


  "txHash": "0x000000000",
  "txId": "0x01"
  "reason": "REJECTED" // RelayErrorReasons

On error, the RelayError type contains a reason of type RelayErrorReasons:

export enum RelayErrorReasons {

Additional Information

Please note you will be given an expiresAt date if you try to send the broadcast after that has expired it will be rejected. It is worth checking that for edge cases if someone takes a long time accepting the approval modal.

This is the signature without it being split so after your call ethers _signTypedData as you must do now if using the typed data methods instead of calling ethers utils.splitSignature you just pass in the full hex string of the signature.

If you see a rejection it is worth allowing them to pay for it themselves so if the error happens use the normal withSig methods so you can handle gasless and if gasless is ever turned off without your code-breaking. REJECTED can mean they have used the max allowance in the hour.

Full LensClient Example

import { isRelayerResult } from "@lens-protocol/client";

const typedDataResult = await lensClient.profile.createOnChainSetProfileMetadataTypedData({

  // typedDataResult is a Result object
  const data = typedDataResult.unwrap();

  // sign with the wallet
  const signedTypedData = await wallet._signTypedData(

  // broadcast
  const broadcastResult = await lensClient.transaction.broadcastOnchain({
    signature: signedTypedData,

  // broadcastResult is a Result object
  const broadcastResultValue = broadcastResult.unwrap();

  if (!isRelayerResult(broadcastResultValue)) {
    console.log(`Something went wrong`, broadcastResultValue);

  console.log(`Transaction was successfuly broadcasted with txId ${broadcastResultValue.txId}`);

You can also check the status of the transaction using:

// result is a Result object
const result = await lensClient.transaction.status({ txId: broadcastResultValue.txId });

if (!result.isSuccess()) {
    console.log(`Something went wrong`, result);

 const isCompleted = result.value.status === LensTransactionStatusType.Complete;

  // or wait till transaction is indexed
  await lensClient.transaction.waitUntilComplete({ txId: broadcastResultValue.txId });

Read here about the returned Result type.

Full GraphQL API Example


Broadcast Onchain Follow: GraphQL API Full Example

Querying If The Transaction Has Been Indexed

You need to use endpoint to know when it's been indexed. This should be your source of truth and the only thing you call to watch for it to be successful. The main difference between what you should do with this call when using the relay and what you should call when not using the relay is instead of passing in the txHash into the hasTxHashBeenIndexed pass in the txId returned in RelayerResult this is because our relay will speed up gas on the transactions if the gas prices move or if it's taking too long to be picked up, this, of course, generates a new txHash and the old one would be dropped. So this is to make sure your client is never stuck in a loop forever. Also because we have to do an extra HTTP call here to find out the status from the transaction id when using txId it will be longer response times than using txHash so we recommend only calling it once every 1 second.