Guides

Open Actions Without A Profile

With Lens V2 supporting authentication without a profile (aka wallet authentication), this allows developers to create even more interactive experiences for Lens applications, for anyone with a wallet address.

Prerequisites

Your app needs to support wallet authentication. To do so, you simply create an authentication challengewithout the for property in the request. Any wallet signing this challenge will create a JWT token with a wallet role.

Acting as a wallet

You can act as a wallet by calling the createActOnOpenActionTypedData endpoint as usual. You will notice that:

  • the generated data has a domain that is a wrapper contract address instead of LensHub proxy.
  • the returned actingProfileId is one not owned by you.

What this means is that profiles are in fact required to collect in Lens V2. By invoking this process, the contract above will be used to act on the publication on your behalf using the dedicated profile to do so. Please note that this profile does not follow anyone, so any actions for followers only will fail.

mutation {
  createActOnOpenActionTypedData(request: {
    for: "0x01-0x01"
    actOn: {
      simpleCollectOpenAction: true
    }
  }) {
    id
    expiresAt
    typedData {
      types {
        Act {
          name
          type
        }
      }
      domain {
        name
        chainId
        version
        verifyingContract
      }
      value {
        nonce
        deadline
        publicationActedProfileId
        publicationActedId
        actorProfileId
        referrerProfileIds
        referrerPubIds
        actionModuleAddress
        actionModuleData
      }
    }
  }
}
{
  "data": {
    "createActOnOpenActionTypedData": {
      "id": "6fa3f7a4-6f70-4828-94a4-28e22c8c624f",
      "expiresAt": "2023-11-10T14:34:10.000Z",
      "typedData": {
        "types": {
          "Act": [
            {
              "name": "publicationActedProfileId",
              "type": "uint256",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "publicationActedId",
              "type": "uint256",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "actorProfileId",
              "type": "uint256",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "referrerProfileIds",
              "type": "uint256[]",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "referrerPubIds",
              "type": "uint256[]",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "actionModuleAddress",
              "type": "address",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "actionModuleData",
              "type": "bytes",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "nonce",
              "type": "uint256",
              "__typename": "EIP712TypedDataField"
            },
            {
              "name": "deadline",
              "type": "uint256",
              "__typename": "EIP712TypedDataField"
            }
          ],
          "__typename": "CreateActOnOpenActionEIP712TypedDataTypes"
        },
        "domain": {
          "name": "PublicActProxy",
          "chainId": 137,
          "version": "2",
          "verifyingContract": "0x53582b1b7BE71622E7386D736b6baf87749B7a2B",
          "__typename": "EIP712TypedDataDomain"
        },
        "value": {
          "nonce": 0,
          "deadline": 1699626850,
          "publicationActedProfileId": "0x01",
          "publicationActedId": "0x01",
          "actorProfileId": "0x01ec67",
          "referrerProfileIds": [],
          "referrerPubIds": [],
          "actionModuleAddress": "0x0D90C58cBe787CD70B5Effe94Ce58185D72143fB",
          "actionModuleData": "0x000000000000000000000000acab2c2cdde3a5839b91babeffd5fd5128590d6f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
          "__typename": "CreateActOnOpenActionEIP712TypedDataValue"
        },
        "__typename": "CreateActOnOpenActionEIP712TypedData"
      },
      "__typename": "CreateActOnOpenActionBroadcastItemResult"
    }
  }
}

Note: the values for domain.verifyingContract and value.actorProfileId in the response above. For every wallet action, these values will always be the same for each environment (Polygon / Amoy) .

Calling the wrapper contract

This action has to be explicitly done by the integrator as there is no API support for calling this right now. Please also know that this implies users will have to sign the transaction themselves, as well as pay any gas and collect fees. That said, the wrapper contract addresses can be found in the deployed contracts list for Developer Preview and Amoy Mainnet.

Free actions and collects

For free actions, you can call the functions publicCollect (0x96bb7386) and publicFreeAct (0xc0cc2190) using the generated typed data from the above call to createActOnOpenActionTypedData and complete the transaction. The contract will use the given profile to act on your behalf and move any minted NFTs to the signed-in wallet.

Paid actions and collects

Paid actions can use the publicCollectWithSig (0xc30f6969) contract function, exactly the same way as above. In this case, you can handle nonces yourself or use the Lens Public API, by supplying the incremented nonce at each sequential call. To get the correct nonce, you can use the nonces read function on the same contract or by using lensPublicActProxyOnchainSigNonce on the API, learn more about managing nonce from the API on Nonce management typed data.

Example using LensClient SDK

The full example you can find in the LensSDK GitHub repository here.

The first step is to prepare ethers, provider, wallet, and LensClient SDK.

const provider = new ethers.providers.JsonRpcProvider(url);
const wallet = new ethers.Wallet(privateKey, provider);
const address = await wallet.getAddress();
const client = new LensClient({
  environment: development,
});

// authenticate LensClient with wallet only
const challenge = await client.authentication.generateChallenge({
  signedBy: address,
});
const signature = await wallet.signMessage(challenge.text);
await client.authentication.authenticate({ id: challenge.id, signature });

The second step is to fetch typed data from Lens API and initialize the PublicActProxy contract. All Lens contract ABIs you can find here.

// get typed data from the Lens API
const resultTypedData = await client.publication.actions.createActOnTypedData({
  actOn: {
    simpleCollectOpenAction: true,
  },
  for: '0x02fe-0x01', // publication id to be acted on
});

// init publicActProxy contract
const publicActProxy = new ethers.Contract(
  publicActionProxyAddress.development, // '0xab5607f5447d538fc79bb32364ddecd8f76d7ee8'
  PublicActProxyAbi,
  wallet,
);

The last step is to sign the typed data and trigger correctly the contract function. Make sure the wallet has enough funds to submit a transaction as it has to pay the gas and potential collect price itself.

// sign the typed data
const { typedData } = resultTypedData.unwrap();

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

// prepare data for the contract
const { v, r, s } = ethers.utils.splitSignature(signedTypedData);

// submit tx
const tx = await publicActProxy.publicCollectWithSig(
  {
    publicationActedProfileId: typedData.value.publicationActedProfileId,
    publicationActedId: typedData.value.publicationActedId,
    actorProfileId: typedData.value.actorProfileId,
    referrerProfileIds: typedData.value.referrerProfileIds,
    referrerPubIds: typedData.value.referrerPubIds,
    actionModuleAddress: typedData.value.actionModuleAddress,
    actionModuleData: typedData.value.actionModuleData,
  },
  { signer: wallet.address, v, r, s, deadline: typedData.value.deadline },
);

console.log(`Submitted a tx with a hash: `, tx.hash);
console.log(`Waiting for tx to be mined...`);

const receipt = await tx.wait();
console.log(`Tx was mined: `, receipt);

That's it, the collected publication should be already in your wallet!