Create set dispatcher 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 a dispatcher for your Lens profile.
Dispatcher allows another address to post, comment, mirror, set follow module and change the profile picture on behalf of you using their wallet
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 CreateSetDispatcherTypedData {
createSetDispatcherTypedData(request:{
profileId: "0x1d",
dispatcher: "0xdfd7D26fd33473F475b57556118F8251464a24eb"
}) {
id
expiresAt
typedData {
types {
SetDispatcherWithSig {
name
type
}
}
domain {
name
chainId
version
verifyingContract
}
value {
nonce
deadline
profileId
dispatcher
}
}
}
}
{
"data": {
"createSetDispatcherTypedData": {
"id": "1c0829c0-94d9-4ca8-938e-61f87a73c811",
"expiresAt": "2022-02-18T15:45:17.000Z",
"typedData": {
"types": {
"SetDispatcherWithSig": [
{
"name": "profileId",
"type": "uint256"
},
{
"name": "dispatcher",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
}
]
},
"domain": {
"name": "Lens Protocol Profile",
"chainId": 80001,
"version": "1",
"verifyingContract": "0x23C1ce2b0865406955Da08F1D31c13fcc3f72A3a"
},
"value": {
"nonce": 0,
"deadline": 1645199117,
"profileId": "0x1d",
"dispatcher": "0xdfd7D26fd33473F475b57556118F8251464a24eb"
}
}
}
}
}
type Mutation {
createSetDispatcherTypedData(
request: SetDispatcherRequest!
): CreateSetDispatcherBroadcastItemResult!
}
input SetDispatcherRequest {
# The profile id
profileId: ProfileId!
# If not set will use the bean dispatcher relay
dispatcher: EthereumAddress
# If you want to enable or disable it
enable: Boolean!
}
# ProfileId custom scalar type
scalar ProfileId
# Ethereum address custom scalar type
scalar EthereumAddress
type CreateSetDispatcherBroadcastItemResult {
# This broadcast item ID
id: BroadcastId!
# The date the broadcast item expiries
expiresAt: DateTime!
# The typed data
typedData: CreateSetDispatcherEIP712TypedData!
}
# The set dispatcher eip 712 typed data
type CreateSetDispatcherEIP712TypedData {
# The types
types: CreateSetDispatcherEIP712TypedDataTypes!
# The typed data domain
domain: EIP712TypedDataDomain!
# The values
value: CreateSetDispatcherEIP712TypedDataValue!
}
# The set dispatcher eip 712 typed data types
type CreateSetDispatcherEIP712TypedDataTypes {
SetDispatcherWithSig: [EIP712TypedDataField!]!
}
# 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 dispatcher eip 712 typed data value
type CreateSetDispatcherEIP712TypedDataValue {
nonce: Nonce!
deadline: UnixTimestamp!
profileId: ProfileId!
dispatcher: EthereumAddress!
}
# Nonce custom scalar type
scalar Nonce
# UnixTimestamp custom scalar type
scalar UnixTimestamp
Request
Let's touch on this request so it's super clear.
profiled - required
You must pass in a profileId
that is mandatory.
dispatcher
You can pass in the dispatcher
as per the above to set a dispatcher.
enabled
You can remove the dispatcher by setting enabled: false
.
Example in the request as below:
mutation CreateSetDispatcherTypedData {
createSetDispatcherTypedData(request:{
profileId: "0x1d",
enabled: false
}) {
id
expiresAt
typedData {
types {
SetDispatcherWithSig {
name
type
}
}
domain {
name
chainId
version
verifyingContract
}
value {
nonce
deadline
profileId
dispatcher
}
}
}
}
Full code example using typed data above
import { apolloClient } from './apollo-client';
import { gql } from '@apollo/client'
const CREATE_SET_DISPATCHER_TYPED_DATA = `
mutation($request: SetDispatcherRequest!) {
createSetDispatcherTypedData(request: $request) {
id
expiresAt
typedData {
types {
SetDispatcherWithSig {
name
type
}
}
domain {
name
chainId
version
verifyingContract
}
value {
nonce
deadline
profileId
dispatcher
}
}
}
}
`
export const enableDispatcherWithTypedData = (profileId, dispatcher) => {
return apolloClient.mutate({
mutation: gql(CREATE_SET_DISPATCHER_TYPED_DATA),
variables: {
request: {
profileId,
dispatcher
}
},
})
}
export const disableDispatcherWithTypedData = (profileId) => {
return apolloClient.mutate({
mutation: gql(CREATE_SET_DISPATCHER_TYPED_DATA),
variables: {
request: {
profileId,
enabled: false
}
},
})
}
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.
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 { enableDispatcherWithTypedData } from './create-set-dispatcher-typed-data';
import { lensHub } from './lens-hub';
export const setDispatcher = async () => {
// hard coded to make the code example clear
const setDispatcherRequest = {
profileId: "0x1d",
dispatcher: "0xdfd7D26fd33473F475b57556118F8251464a24eb"
}
const result = await enableDispatcherWithTypedData(setDispatcherRequest.profileId, setDispatcherRequest.dispatcher);
const typedData = result.data.createSetDispatcherTypedData.typedData;
const signature = await signedTypeData(typedData.domain, typedData.types, typedData.value);
const { v, r, s } = splitSignature(signature);
const tx = await lensHub.setDispatcherWithSig({
profileId: typedData.value.profileId,
dispatcher: typedData.value.dispatcher,
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
}
import { apolloClient } from './apollo-client';
import { gql } from '@apollo/client'
const CREATE_SET_DISPATCHER_TYPED_DATA = `
mutation($request: SetDispatcherRequest!) {
createSetDispatcherTypedData(request: $request) {
id
expiresAt
typedData {
types {
SetDispatcherWithSig {
name
type
}
}
domain {
name
chainId
version
verifyingContract
}
value {
nonce
deadline
profileId
dispatcher
}
}
}
}
`
export const enableDispatcherWithTypedData = (profileId, dispatcher) => {
return apolloClient.mutate({
mutation: gql(CREATE_SET_DISPATCHER_TYPED_DATA),
variables: {
request: {
profileId,
dispatcher
}
},
})
}
export const disableDispatcherWithTypedData = (profileId) => {
return apolloClient.mutate({
mutation: gql(CREATE_SET_DISPATCHER_TYPED_DATA),
variables: {
request: {
profileId,
enabled: false
}
},
})
}
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.
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();
// remove the __typedname from the signature!
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()
)
Hooking in without using the type data
You may not want to go down the typed data with the signature route and just send the transaction directly from the client to the blockchain without any API call to map the data for you. This is out of scope for the API documentation as would have been explained and showed how to do it in the contract docs. The contract method is setDispatcher
.
Updated 3 days ago