Guides

Gated publication

Lens Protocol supports the creation of gated content experiences so you can share your garden with just the people you wish. Gated content on Lens allows a trustless way to control content accessibility, as encryption and decryption happen on the client side. The Gated Publications feature uses LIT Protocol decentralized access control behind the scenes.

So what can I do with it?

When you create a new Gated Publication, Lens users can now specify access conditions. Access controls mean that decrypting the publication content and media will only be available to (for example):

  • Users who have collected your publication
  • Your followers
  • Users that own any NFT from a specific collection (examples: a DAO membership NFT, an NFT of a collected publication posted on Lens, a Nouns DAO NFT etc.).
  • Users that own an NFT with a given TokenId, or a range of TokenIds (example: Your first 100 followers)
  • The owner of a specific Lens Profile or EOA address, for exclusive content.
  • Users who own some balance of an ERC20 token (example: People who have more than X stETH or who hold Y amount of a DAO governance token)
  • You can also combine the above using boolean AND and OR conditions.

The possibility of creating gated experiences enables various kinds of interesting use-cases for gating and monetizing your content in the Lens Ecosystem, giving creators and publishers more control over how content is consumed.

Privacy

The hidden metadata stays private end-to-end as it will be encrypted and decrypted only by the user who fulfils the access control condition.

How to use

To encrypt or decrypt gated content, you will need the @lens-protocol/client/gated secondary entry point of the @lens-protocol/client NPM package (see LensClient SDK). It works on both browser and NodeJS, and allows you to encrypt metadata and upload them to your preferred storage, as well as decrypting any existing metadata retrieved from the Lens API as long as you satisfy the conditions.

First of all, you should create Publication metadata compliant with the Publication Metadata standard.

That said, from a client perspective, there are couple of things to take care of. First of all, create an instance of the gated LensClient class (note the secondary entry point @lens-protocol/client/gated).

import { development, LensClient } from '@lens-protocol/client/gated';

const client = new LensClient({
  environment: development,

  authentication: {
    domain: process.env.DOMAIN,
    uri: process.env.URI,
  },

  signer,
});

Like with traditional LensClient configuration the environment dictates what Lens Protocol deployment to use as well as the correct Lens API endpoint. In the context of Gate publications it also specify the corresponding configuration for the Lit Protocol. You can choose between production (Polygon) and development (Amoy testnet) environments.

Then you need to provide authentication config. This is used to create a SIWE message (Sign-in with Ethereum) that Lit Protocol uses to authenticated user's on their network.

Finally you need to provide a signer compliant with the ISigner interface. For convenience this is structurally compatible with ethers Signer interface. This signer is used to sign the aforementioned SIWE message.

Like base LensClient class it's possible to specify an optional storage compliant with the IStorageProvider interface. By default if not provided it will fallback to an InMemoryStorage instance (which keeps any stored information in a ephemeral location in system memory). The gated LensClient uses the same storage to save Lit Protocol credentials so that signing of SIWE messages happens only when needed.

The IStorageProvider interface is structurally compatible with Web Storage API so in a browser you can simply use window.localStorage (or window.sessionStorage).

const client = new LensClient({
  environment: development,

  authentication: {
    domain: process.env.DOMAIN,
    uri: process.env.URI,
  },

  signer,
  
  storage: window.localStorage
});

Encrypting content

You can encrypt publication metadata using the client.gated.encryptPublicationMetadata method.

πŸ“˜

Keep in mind

Encryption of new content only supports the LIP-2 Metadata Standards format, see here.

import { article, erc721OwnershipCondition } from '@lens-protocol/metadata';

// create metadata via '@lens-protocol/metadata' helpers
const metadata = article({ content: '...' });

// encrypt the metadata specifying the access condition
const result = await client.gated.encryptPublicationMetadata(
  metadata,
  erc721OwnershipCondition({
    contract: { address: '0x...', chainId: 1 }
  })
);

// handle encryption errors
if (result.isFailure()) {
  console.error(result.error);
  return; // bail out
}

// upload the encrypted metadata to your storage of choice
const contentURI = await uploadToIPFS(result.value);

// use the contentURI to create a publication
const result = await client.publication.postOnchain({ contentURI });

What's happening?

  1. create metadata via @lens-protocol/metadata helpers,
  2. encrypt it with the given access condition,
  3. upload it to a public location (e.g. IPFS, Arweave, public S3 bucket),
  4. use the resulting contentURI to create a Lens publication.

Simple criteria

Supported criteria available as helpers in the @lens-protocol/metadata package:

Collect this publication

It's possible to define an access condition where one of the criteria is to collect the current publication. Because at the time of encryption the publication is not yet created, the client.publication.predictNextOnChainPublicationId can be used to predict the publication ID.

const condition = collectCondition({
  publicationId: await client.publication.predictNextOnChainPublicationId({
    from: profile.id,
  }),
  thisPublication: true, // flag to indicate that the current publication is the one to collect
});

Compound criteria

Multiple criteria can be combined using the orCondition and andCondition helpers.

const result = await client.gated.encryptPublicationMetadata(
  metadata,
  orCondition([
    profileOwnershipCondition({
      profileId: profile.id,
    }),
    erc721OwnershipCondition({
      contract: { address: '0x...', chainId: 1 }
    })
  ])
);

Supported compound criteria:

  • andCondition - up to 5 criteria can be combined using the AND operator (except orCondition and andCondition)
  • orCondition - up to 5 criteria can be combined using the OR operator (except orCondition and andCondition)

Decrypting content

You can decrypt publication metadata using the client.gated.decryptPublicationMetadataFragment method.

The method works seamlessly with publications returned by any LensClient method.

The LensClient SDK also support decryption of past publication metadata standards (namely v2).

import { isEncryptedPublicationMetadata } from '@lens-protocol/client/gated';

// fetch a publication, works with publications returned by any LensClient method
const post = await client.publication.fetch({ forId: '...' });

// check if the publication metadata is encrypted
if (isEncryptedPublicationMetadata(post.metadata)) {

  // decrypt the metadata
  const result = await client.gated.decryptPublicationMetadataFragment(post.metadata);

  // handle decryption errors
  if (result.isFailure()) {
    console.error(result.error);
    return; // bail out
  }

  // use the decrypted metadata
  console.log(result.value);
}

πŸ“˜

Custom fragments

In case you find yourself using this feature with publication metadata fragment not originated from the LensClient it's your responsibility to make sure the PublicationMetadata fragment is valid by making sure it:

  • has __typename defined at every level of the fragment
  • has the encryptedWith including ALL fields and sub-fields of the corresponding GQL node.