Guides

🚧

This request is protected by authentication

hint: this means it requires an x-access-token header put in the request with your authentication token.

📘

Lens Profile Manager Compatible: Gasless & Signless

This action can be used through the Lens Profile Manager to enable a gasless and signless experience.

Follow a Lens Profile so that the profile's content is included in all feed based queries and gain the ability to perform actions only available to a profile's followers.

There are two different approaches you can use to follow a profile. Depending on the whitelist status of your app and the settings of the profile you would like to follow from, you can use:

a) Follow via Lens Profile Manager

b) Follow Using TypedData and Broadcasting Onchain via the API

  • Criteria:
    • Your app is not whitelisted
    • OR
    • The profile you would like to follow from has not enabled the Lens Profile Manager

a) Follow via Lens Profile Manager

If possible, using the Lens Profile Manager is the best way to execute a follow action. This will be a gasles and signless operation.

Request

You can follow many people in a single contract call so the interface here is designed around that as well.

  • follow: Array<Follow> (required)
    • profileId: string (required)
      • The profileId of the profile you would like to follow.
    • followModule: FollowModule (optional)
      • If you are following someone who has a follow module defined you must pass in the properties to redeem it. We make you pass in the properties from the client-side because if we read it from the server each time someone could front-run the request and make you sign something which your client was not seeing at the time of generating the signature. Follow modules can be changed at any time by the profile. If no follow module is setup for this profile you do not need to pass anything in.
      • Lens Profile Manager only supports the free follow module.

Invocation

const result = await lensClient.profile.follow({
  follow: [
    {
      profileId: "PROFILE_TO_FOLLOW_ID",
    },
  ],
});
mutation Follow($request: FollowLensManagerRequest!) {
  follow(request: $request) {
    ... on RelaySuccess {
      ...RelaySuccess
    }
    ... on LensProfileManagerRelayError {
      ...LensProfileManagerRelayError
    }
  }
}

Response

{
  "txHash": "0x000000",
  "txId": "0x01"
}
{
  "reason": "FAILED" // LensProfileManagerRelayErrorReasonType
}

b) Follow Using TypedData and Broadcasting Onchain via the API

Request

You can follow many people in a single contract call so the interface here is designed around that as well.

  • follow: Array<Follow> (required)
    • profileId: string (required)
      • The profileId of the profile you would like to follow.
    • followModule: FollowModule (optional)
      • The follow module data. Lens Profile Manager only supports the free follow module.

Additional Information on Follow Modules

If you are following someone who has a follow module defined you must pass in the properties to redeem it. We make you pass in the properties from the client-side because if we read it from the server each time someone could front-run the request and make you sign something which your client was not seeing at the time of generating the signature. Follow modules can be changed at any time by the profile. If no follow module is setup for this profile you do not need to pass anything in.

Defining the follow module to be redeemed is very easy with the schema we have created:

Fee Follow module

The person you want to follow requires sets a fee required in order to follow them. To enable this you need to set the feeFollowModule to true.

mutation CreateFollowTypedData {
  createFollowTypedData(
    request: {
      follow: [{ profileId: "0x01", followModule: { feeFollowModule: true } }]
    }
  ) {
    ...CreateFollowBroadcastItemResult
  }
}

Unknown Follow Module

If the profile you want to follow has an unknown follow module set, you must compose the follow module request to match.

mutation CreateFollowTypedData {
  createFollowTypedData(
    request: {
      follow: [
        {
          profileId: "0x01"
          followModule: {
            unknownFollowModule: { address: "0x000", data: "..." }
          }
        }
      ]
    }
  ) {
    ...CreateFollowBroadcastItemResult
  }
}

Invocation

const result = await lensClient.profile.createFollowTypedData({
  follow: [
    {
      profileId: "PROFILE_ID",
    },
  ],
});
mutation CreateFollowTypedData {
  createFollowTypedData(request: { follow: [{ profile: "0x01" }] }) {
    ...CreateFollowBroadcastItemResult
  }
}

Response

{
  "id": "0x01",
  "expiresAt": "2023-10-01T00:00:00Z",
  "typedData": {
    "types": {
      "Follow": [
        { "name": "...", "type": "..." },
        { "name": "...", "type": "..." }
      ]
    },
    "domain": {
      "name": "...",
      "chainId": "...",
      "version": "...",
      "verifyingContract": "0x0000000"
    },
    "value": {
      "nonce": "...",
      "deadline": "2023-10-01T01:00:00Z"
    }
  }
}

Broadcasting the TypedData

Once you have the typed data for the follow action, you need to get the user to sign with their wallet and then broadcast it onchain.

See Broadcasting Onchain for more information.

Calling the Contract Directly

If you opt to bypass the API and directly push transactions from the client to the blockchain, you'll be responsible for encoding and validation.

This approach isn't covered in the API documentation, you can find guidance in the contract documentation.

Aligned with best practices demonstrated by projects like seaport on OpenSea, this is aimed at enhancing the user's awareness of what they're signing.

Full LensClient Example

Using the Lens Profile Manager:

// lensClient is an authenticated instance of LensClient

const recommendedProfiles = await lensClient.profile.recommendations({
  for: "YOUR_PROFILE_ID",
});

console.log(
  `First 3 recommended profiles`,
  recommendedProfiles.items.slice(0, 3).map((p) => ({
    id: p.id,
    handle: p.handle,
    isFollowedByMe: p.isFollowedByMe,
  }))
);

const result = await lensClient.profile.follow({
  follow: [
    {
      profileId: "PROFILE_TO_FOLLOW_ID",
    },
  ],
});

console.log(
  `Follow of ${recommendedProfiles[0].id} triggered with through the Lens Profile Manager: `,
  result.unwrap()
);

const followResultValue = result.unwrap();

if (!isSuccessfulLensProfileManagerResponse(followResultValue)) {
  throw new Error(`Something went wrong`);
}

await lensClient.transaction.waitUntilComplete({
  txId: followResultValue.txId,
});

Using TypedData and Broadcasting Onchain:

const recommendedProfiles = await lensClient.profile.recommendations({
  for: "profile-id",
});

const result = await lensClient.profile.createFollowTypedData({
  follow: [
    {
      profileId: recommendedProfiles[1].id,
    },
  ],
});

const data = result.unwrap();

const signedTypedData = await wallet._signTypedData(
  data.typedData.domain,
  data.typedData.types,
  data.typedData.value
);

const broadcastResult = await lensClient.transaction.broadcastOnchain({
  id: data.id,
  signature: signedTypedData,
});

const followBroadcastResultValue = broadcastResult.unwrap();

if (!isRelaySuccess(followBroadcastResultValue)) {
  console.log(`Something went wrong`, followBroadcastResultValue);
  return;
}

console.log(
  `Transaction to follow ${recommendedProfiles[1].id} was successfuly broadcasted with txId ${followBroadcastResultValue.txId}`
);

// wait for follow to be indexed
console.log(`Waiting for the transaction to be indexed...`);
await lensClient.transaction.waitUntilComplete({
  txId: followBroadcastResultValue.txId,
});

Full GraphQL API Example

📘

Follow: GraphQL API Full Example