Create set follow NFT Uri typed data

📘

full code repo https://github.com/lens-protocol/lens-api-examples

This API call allows you to get the typed data to then call the withSig method to set your follow NFT for your profile on lens. This is what your users will mint and see on secondary marketplaces and in their wallets as the follower NFT.

🚧

This request is protected by authentication

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

Typed data is a way to try to show the users what they are signing in a more readable format. You can read more about it here.

Constructing that type of data is normally difficult. On the type data, you also need to get the nonce, deadline, contract version, contract address, chain id, and the name of the contract for the signature to be able to be signed and verified.

When using this API the server checks every detail before it generates the typed data. For example: if you try to create typed data on an always failing transaction the server will throw an error in a human-readable form. This is great for debugging but also saves issues with users sending always failing transactions or a mismatch of a bad request.

We will show you the typed data approach using ethers and the API side by side. Keep in mind that with the typed data approach you use the withSig methods which can be called by you with your signature or with that signature any relay could call it for you on your behalf allowing gasless transactions.

API Design

mutation CreateSetFollowNFTUriTypedData {
  createSetFollowNFTUriTypedData(request: {
    profileId: "0x02",
    followNFTURI: "ipfs://LmTqN4LZ2G4QRrsS2y2QFMUH5K7dT2ix6P6TuL3pq9CShx"
  }) {
    id
    expiresAt
    typedData {
      types {
        SetFollowNFTURIWithSig {
          name
          type
        }
      }
      domain {
        name
        chainId
        version
        verifyingContract
      }
      value {
        nonce
        profileId
        deadline
        followNFTURI
      }
    }
  }
}
{
  "data": {
    "createSetFollowNFTUriTypedData": {
      "id": "5ee5c6d5-4e54-4a32-b044-34b16bef3001",
      "expiresAt": "2022-03-03T09:45:32.000Z",
      "typedData": {
        "types": {
          "SetFollowNFTURIWithSig": [
            {
              "name": "profileId",
              "type": "uint256"
            },
            {
              "name": "followNFTURI",
              "type": "string"
            },
            {
              "name": "nonce",
              "type": "uint256"
            },
            {
              "name": "deadline",
              "type": "uint256"
            }
          ]
        },
        "domain": {
          "name": "Lens Protocol Profile",
          "chainId": 80001,
          "version": "1",
          "verifyingContract": "0x23C1ce2b0865406955Da08F1D31c13fcc3f72A3a"
        },
        "value": {
          "nonce": 11,
          "profileId": "0x02",
          "deadline": 1646300732,
          "followNFTURI": "ipfs://QmTqN4LZ2G4QRrsS2y2QFMUH5K7dT2ix6P6TuL3pq9CShx"
        }
      }
    }
  }
}
type Mutation {
  createSetFollowNFTUriTypedData(
    request: CreateSetFollowNFTUriRequest!
  ): CreateSetFollowNFTUriBroadcastItemResult!
}
input CreateSetFollowNFTUriRequest {
  profileId: ProfileId!
  followNFTURI: Url
}

# ProfileId custom scalar type
scalar ProfileId

# ProfileId custom scalar type
scalar Url
# The broadcast item
type CreateSetFollowNFTUriBroadcastItemResult {
  # This broadcast item ID
  id: BroadcastId!

  # The date the broadcast item expiries
  expiresAt: DateTime!

  # The typed data
  typedData: CreateSetFollowNFTUriEIP712TypedData!
}
  
# The eip 712 typed data field
type EIP712TypedDataField {
  # The name of the typed data field
  name: String!

  # The type of the typed data field
  type: String!
}
  
# The eip 712 typed data domain
type EIP712TypedDataDomain {
  # The name of the typed data domain
  name: String!

  # The chainId
  chainId: ChainId!

  # The version
  version: String!

  # The verifying contract
  verifyingContract: ContractAddress!
}

# The set follow nft uri eip 712 typed data
type CreateSetFollowNFTUriEIP712TypedData {
  # The types
  types: CreateSetFollowNFTUriEIP712TypedDataTypes!

  # The typed data domain
  domain: EIP712TypedDataDomain!

  # The values
  value: CreateSetFollowNFTUriEIP712TypedDataValue!
}

# The set follow nft uri eip 712 typed data types
type CreateSetFollowNFTUriEIP712TypedDataTypes {
  SetFollowNFTURIWithSig: [EIP712TypedDataField!]!
}

# The set follow nft uri eip 712 typed data value
type CreateSetFollowNFTUriEIP712TypedDataValue {
  nonce: Nonce!
  deadline: UnixTimestamp!
  profileId: ProfileId!
  followNFTURI: Url!
}

Request

Let's touch on this request so it's super clear.

ProfileId - required

This is mandatory

followNFTURI

The follow NFT URI is the NFT metadata your followers will mint when they follow you. This can be updated at all times. If you do not pass in anything it will create a super cool changing NFT which will show the last publication of your profile as the NFT which looks awesome! This means people do not have to worry about writing this logic but still have the ability to customize it for their followers.

We do not enforce our standards on this metadata as it is purely to be showed on secondary marketplaces. We advise you follow opensea metadata spec for this https://docs.opensea.io/docs/metadata-standards

Full code example

// this is showing you how you use it with react for example
// if your using node or something else you can import using
// @apollo/client/core!
import { apolloClient } from './apollo-client';
import { gql } from '@apollo/client'

const CREATE_SET_FOLLOW_NFT_URI_TYPED_DATA = `
  mutation($request: CreateSetFollowNFTUriRequest!) { 
    createSetFollowNFTUriTypedData(request: $request) {
      id
      expiresAt
      typedData {
        types {
          SetFollowNFTURIWithSig {
            name
            type
          }
        }
      domain {
        name
        chainId
        version
        verifyingContract
      }
      value {
        nonce
        profileId
        deadline
        followNFTURI
      }
     }
   }
 }
`

export const createSetFollowNFTUriTypedData = (setFollowNFTUriRequest) => {
   return apolloClient.mutate({
    mutation: gql(CREATE_SET_FOLLOW_NFT_URI_TYPED_DATA),
    variables: {
      request: setFollowNFTUriRequest
    },
  })
}
// this is showing you how you use it with react for example
// if your using node or something else you can import using
// @apollo/client/core!
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client'

const httpLink = new HttpLink({ uri: 'https://api-mumbai.lens.dev/' });

// example how you can pass in the x-access-token into requests using `ApolloLink`
const authLink = new ApolloLink((operation, forward) => {
  // Retrieve the authorization token from local storage.
  // if your using node etc you have to handle your auth different
  const token = localStorage.getItem('auth_token');

  // Use the setContext method to set the HTTP headers.
  operation.setContext({
    headers: {
      'x-access-token': token ? `Bearer ${token}` : ''
    }
  });

  // Call the next link in the middleware chain.
  return forward(operation);
});

export const apolloClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
})

Hooking typed data in with ethers

import { signedTypeData, getAddressFromSigner, splitSignature } from './ethers.service';
import { createSetFollowNFTUriTypedData } from './create-set-follow-nft-uri-typed-data';
import { lensHub } from './lens-hub';

export const setFollowNftUri = async () => {
  // hard coded to make the code example clear
  const setFollowNftUriRequest = {
    profileId: "0x01",
    // should be ipfs or reachable url with the metadata mapped
    followNFTURI: "ipfs://LmTqN4LZ2G4QRrsS2y2QFMUH5K7dT2ix6P6TuL3pq9CShx"
  };
        
  const result = await createSetFollowNFTUriTypedData(setFollowNftUriRequest);
  const typedData = result.data.createSetFollowNFTUriTypedData.typedData;
  
  const signature = await signedTypeData(typedData.domain, typedData.types, typedData.value);
  const { v, r, s } = splitSignature(signature);
  
  const tx = await lensHub.setFollowNFTURIWithSig({
    profileId: typedData.value.profileId,
    followNFTURI: typedData.value.followNFTURI,
    sig: {
      v,
      r,
      s,
      deadline: typedData.value.deadline,
    },
  });
  console.log(tx.hash);
  // 0x64464dc0de5aac614a82dfd946fc0e17105ff6ed177b7d677ddb88ec772c52d3
  // you can look at how to know when its been indexed here: 
  //   - https://docs.lens.dev/docs/has-transaction-been-indexed
}
// this is showing you how you use it with react for example
// if your using node or something else you can import using
// @apollo/client/core!
import { apolloClient } from './apollo-client';
import { gql } from '@apollo/client'

const CREATE_SET_FOLLOW_NFT_URI_TYPED_DATA = `
  mutation($request: CreateSetFollowNFTUriRequest!) { 
    createSetFollowNFTUriTypedData(request: $request) {
      id
      expiresAt
      typedData {
        types {
          SetFollowNFTURIWithSig {
            name
            type
          }
        }
      domain {
        name
        chainId
        version
        verifyingContract
      }
      value {
        nonce
        profileId
        deadline
        followNFTURI
      }
     }
   }
 }
`

export const createSetFollowNFTUriTypedData = (setFollowNFTUriRequest) => {
   return apolloClient.mutate({
    mutation: gql(CREATE_SET_FOLLOW_NFT_URI_TYPED_DATA),
    variables: {
      request: setFollowNFTUriRequest
    },
  })
}
// this is showing you how you use it with react for example
// if your using node or something else you can import using
// @apollo/client/core!
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client'

const httpLink = new HttpLink({ uri: 'https://api-mumbai.lens.dev/' });

// example how you can pass in the x-access-token into requests using `ApolloLink`
const authLink = new ApolloLink((operation, forward) => {
  // Retrieve the authorization token from local storage.
  // if your using node etc you have to handle your auth different
  const token = localStorage.getItem('auth_token');

  // Use the setContext method to set the HTTP headers.
  operation.setContext({
    headers: {
      'x-access-token': token ? `Bearer ${token}` : ''
    }
  });

  // Call the next link in the middleware chain.
  return forward(operation);
});

export const apolloClient = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
})
import { ethers, utils } from 'ethers';
import omitDeep from 'omit-deep';

// This code will assume you are using MetaMask.
// It will also assume that you have already done all the connecting to metamask
// this is purely here to show you how the public API hooks together
export const ethersProvider = new ethers.providers.Web3Provider(window.ethereum);

export const getSigner = () => {
    return ethersProvider.getSigner();
}

export const getAddressFromSigner = () => {
  return getSigner().address;
}

export const init = async() => {
    const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
  return accounts[0];
}

export const signedTypeData = (domain, types, value) => {
  const signer = getSigner();
  return signer._signTypedData(
    omitDeep(domain, '__typename'),
    omitDeep(types, '__typename'),
    omitDeep(value, '__typename')
  );
}

export const splitSignature = (signature) => {
    return utils.splitSignature(signature)
}

export const sendTx = (transaction) => {
  const signer = ethersProvider.getSigner();
  return signer.sendTransaction(transaction);
}
import { getSigner } from './ethers.service';

// lens contract info can all be found on the deployed
// contract address on polygon.
// not defining here as it will bloat the code example
export const lensHub = new ethers.Contract(
  LENS_HUB_CONTRACT_ADDRESS,
  LENS_HUB_ABI,
  getSigner()
)

Did this page help you?