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 challenge
without 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 / Mumbai) .
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 below for Developer Preview and Polygon Mainnet:
Environment | PublicActionProxy contract | Proxy ProfileId |
---|---|---|
Developer Preview | 0xab5607f5447d538fc79bb32364ddecd8f76d7ee8 | 0x0363 |
Polygon mainnet | 0x53582b1b7BE71622E7386D736b6baf87749B7a2B | 0x01ec67 |
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!
Updated 8 days ago