Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lens v3 frames #543

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/pages/reference/js/lens/getLensFrameMessage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Returns a `LensFrameMessageReturnType` object from the message payload. Throws a
## Usage

```ts
yarn add @lens-protocol/client@2.0.0
yarn add @lens-protocol/client@canary
```

```ts
Expand All @@ -15,15 +15,16 @@ const frameMessage = await getLensFrameMessage(frameActionPayload);
console.log(frameMessage);
/**
{
profileId: "0x01";
pubId: "0x02-0x01";
account: "0x0000000000000000000000000000000000000000";
post: "0x02-0x01";
app: "0x0000000000000000000000000000000000000000",
url: "https://example.com;
buttonIndex: 1;
unixTimestamp: 123456789;
deadline: 123456789;
inputText: "";
state: "";
actionResponse: "";
transactionId: "";
specVersion: "1.0.0";
isValid: true,
}
Expand Down
17 changes: 7 additions & 10 deletions docs/pages/reference/js/lens/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Callout, HomePage } from "vocs/components";

# Lens Support

Cross-protocol frames are supported by frames.js via familiar APIs. This guide will showcase how to write a simple stateless frame, which returns the Lens profileof the user that interacted with the frame in an OpenFrame supported application.
Cross-protocol frames are supported by frames.js via familiar APIs. This guide will showcase how to write a simple stateless frame, which returns the Lens account the user that interacted with the frame in an OpenFrame supported application.

<Callout type="warning">
This tutorial is out of date and uses a deprecated version of frames.js. Please check the [Lens Support guide](/guides/lens) for the latest version.
Expand All @@ -13,7 +13,7 @@ Cross-protocol frames are supported by frames.js via familiar APIs. This guide w
First, you need to install `frames.js` and `@lens-protocol/client`. You can do this by running the following command:

<HomePage.InstallPackage
name="frames.js @lens-protocol/client"
name="frames.js @lens-protocol/client@canary"
type="install"
/>

Expand Down Expand Up @@ -75,7 +75,7 @@ const acceptedProtocols: ClientProtocolId[] = [
{
// [!code focus]
id: "lens", // [!code focus]
version: "1.0.0", // [!code focus]
version: "1.1.0", // [!code focus]
}, // [!code focus]
{
// [!code focus]
Expand Down Expand Up @@ -133,7 +133,6 @@ const previousFrame = getPreviousFrame(searchParams);

let fid: number | undefined;
let walletAddress: string | undefined;
let lensProfileId: string | undefined;

if (
// [!code focus]
Expand Down Expand Up @@ -169,14 +168,13 @@ Now we can use data from the different message contexts to populate our `fid` an

let fid: number | undefined;
let walletAddress: string | undefined;
let lensProfileId: string | undefined;

if (
previousFrame.postBody &&
isLensFrameActionPayload(previousFrame.postBody)
) {
const frameMessage = await getLensFrameMessage(previousFrame.postBody);
lensProfileId = frameMessage?.profileId; // [!code focus]
walletAddress = frameMessage?.account; // [!code focus]
} else if (
previousFrame.postBody &&
isXmtpFrameActionPayload(previousFrame.postBody)
Expand All @@ -198,11 +196,11 @@ if (
// ...
```

Here we use the `frameMessage` to extract the `verifiedProfileId`, `verifiedWalletAddress` and `requesterFid` from the Lens, XMTP, and Farcaster frame messages respectively. We then use these to populate our `lensProfileId`, `walletAddress` and `fid` variables. You can use this information to execute some action like a database query or an onchain transaction.
Here we use the `frameMessage` to extract the `verifiedWalletAddress` and `requesterFid` from the Lens, XMTP, and Farcaster frame messages respectively. We then use these to populate our `walletAddress` and `fid` variables. You can use this information to execute some action like a database query or an onchain transaction.

### Returning a Frame

Now that we have our `lensProfileId`, `fid` and `walletAddress` variables populated, we can use them to determine the next frame to return.
Now that we have our `fid` and `walletAddress` variables populated, we can use them to determine the next frame to return.

```tsx filename="example7.tsx"
// ...
Expand All @@ -221,7 +219,6 @@ return (
This frame gets the interactor&apos;s wallet address or FID depending
on the client protocol.
</div>
{lensProfileId && <div tw="flex">Lens Profile ID: {lensProfileId}</div>}
{fid && <div tw="flex">FID: {fid}</div>}
{walletAddress && <div tw="flex">Wallet Address: {walletAddress}</div>}
</div>
Expand All @@ -231,4 +228,4 @@ return (
);
```

Above, we conditionally render the `lensProfileId`, `fid` and `walletAddress` variables in the frame.
Above, we conditionally render the `fid` and `walletAddress` variables in the frame.
8 changes: 4 additions & 4 deletions docs/pages/reference/render/identity/lens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ In order to use Lens identity you need to install `@rainbow-me/rainbowkit`, `@le
:::code-group

```bash [npm]
npm install @rainbow-me/rainbowkit @lens-protocol/client viem wagmi
npm install @rainbow-me/rainbowkit @lens-protocol/client@canary viem wagmi
```

```bash [yarn]
yarn add @rainbow-me/rainbowkit @lens-protocol/client viem wagmi
yarn add @rainbow-me/rainbowkit @lens-protocol/client@canary viem wagmi
```

```bash [pnpm]
pnpm add @rainbow-me/rainbowkit @lens-protocol/client viem wagmi
pnpm add @rainbow-me/rainbowkit @lens-protocol/client@canary viem wagmi
```

:::
Expand All @@ -36,7 +36,7 @@ import { WebStorage } from "@frames.js/render/identity/storage";
export function MyFrame() {
const lensFrameContext = useLensFrameContext({
fallbackContext: {
pubId: "0x01-0x01",
post: "0x01-0x01",
},
});
const signerState = useLensIdentity({
Expand Down
2 changes: 1 addition & 1 deletion packages/debugger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dependencies": {
"@farcaster/auth-kit": "^0.6.0",
"@upstash/redis": "^1.34.3",
"@lens-protocol/client": "^2.3.2",
"@lens-protocol/client": "^0.0.0-canary-20250224081220",
"@farcaster/frame-sdk": "^0.0.26",
"@xmtp/xmtp-js": "^12.0.0",
"is-port-reachable": "^4.0.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/frames.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@
],
"devDependencies": {
"@cloudflare/workers-types": "^4.20240320.1",
"@lens-protocol/client": "^2.3.2",
"@lens-protocol/client": "^0.0.0-canary-20250224081220",
"@open-frames/types": "^0.0.6",
"@remix-run/node": "^2.8.1",
"@types/express": "^4.17.21",
Expand All @@ -418,7 +418,7 @@
"license": "MIT",
"peerDependencies": {
"@cloudflare/workers-types": "^4.20240320.1",
"@lens-protocol/client": "^2.0.0",
"@lens-protocol/client": "^0.0.0-canary-20250224081220",
"@types/express": "^4.17.21",
"@xmtp/frames-validator": "^0.6.2",
"next": "^14.1.0",
Expand All @@ -437,4 +437,4 @@
"viem": "^2.7.8",
"zod": "^3.24.1"
}
}
}
73 changes: 37 additions & 36 deletions packages/frames.js/src/lens/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
FrameVerifySignatureResult,
LensClient,
development,
production,
PublicClient,
testnet,
FrameVerifySignatureResult
} from "@lens-protocol/client";
import {
verifyFrameSignature, createFrameTypedData
} from "@lens-protocol/client/actions";
import type { MessageWithWalletAddressImplementation } from "../middleware/walletAddressMiddleware";
import { InvalidFrameActionPayloadError } from "../core/errors";
import type { OpenFramesActionData } from "../types";
Expand All @@ -12,15 +14,16 @@ export type LensFrameRequest = {
clientProtocol: string;
untrustedData: {
specVersion: string;
profileId: string;
pubId: string;
account: string;
post: string;
app: string;
url: string;
buttonIndex: number;
unixTimestamp: number;
deadline: number;
inputText: string;
state: string;
actionResponse: string;
transactionId: string;
identityToken: string;
};
trustedData: {
Expand All @@ -31,11 +34,12 @@ export type LensFrameRequest = {
type LensFrameVerifiedFields = {
url: string;
buttonIndex: number;
profileId: string;
pubId: string;
account: string;
post: string;
app: string;
inputText: string;
state: string;
actionResponse: string;
transactionId: string;
deadline: number;
specVersion: string;
};
Expand Down Expand Up @@ -66,38 +70,39 @@ export async function getLensFrameMessage(
frameActionPayload: LensFrameRequest,
options?: LensFrameOptions
): Promise<LensFrameResponse> {
const lensClientEnvironment =
options?.environment === "development" ? development : production;
//const lensClientEnvironment =
// options?.environment === "development" ? development : production;

const lensClient = new LensClient({
environment: lensClientEnvironment,
const lensClient = PublicClient.create({
environment: testnet,
});

const {
url,
inputText,
state,
buttonIndex,
actionResponse,
profileId,
pubId,
transactionId,
account,
post,
app,
specVersion,
deadline,
identityToken,
} = frameActionPayload.untrustedData;

const typedData = await lensClient.frames
.createFrameTypedData({
url,
inputText,
state,
buttonIndex,
actionResponse,
profileId,
pubId,
specVersion,
deadline,
})
const typedData = await createFrameTypedData(lensClient, {
url,
inputText,
state,
buttonIndex,
transactionId,
account,
post,
app,
specVersion,
deadline,
})
.catch((e) => {
// eslint-disable-next-line no-console -- provide feedback to the developer
console.error(e);
Expand All @@ -106,7 +111,7 @@ export async function getLensFrameMessage(
);
});

const response = await lensClient.frames.verifyFrameSignature({
const response = await verifyFrameSignature(lensClient, {
identityToken,
signature: frameActionPayload.trustedData.messageBytes,
signedTypedData: typedData,
Expand All @@ -115,12 +120,8 @@ export async function getLensFrameMessage(
return {
...typedData.value,
isValid: response === FrameVerifySignatureResult.Verified,
async walletAddress() {
const profile = await lensClient.profile.fetch({
forProfileId: typedData.value.profileId,
});

return profile?.ownedBy.address;
walletAddress() {
return Promise.resolve(typedData.value.account);
},
};
}
15 changes: 8 additions & 7 deletions packages/frames.js/src/lens/lens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ describe("getLensMessage", () => {
specVersion: "1.0.0",
url: "http://localhost:3000/examples/multi-protocol",
buttonIndex: 1,
profileId: "0x01df7e",
pubId: "0x01-0x01",
account: "0x0000000000000000000000000000000000000000",
post: "0x01-0x01",
app: "0x0000000000000000000000000000000000000000",
inputText: "",
state: '{"pageIndex":0}',
actionResponse: "",
transactionId: "",
deadline: Math.round((Date.now() + 5000) / 1000),
identityToken:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4MDFkZjdlIiwiZXZtQWRkcmVzcyI6IjB4OGQyNTY4NzgyOUQ2Yjg1ZDllMDAyMEI4Yzg5ZTNDYTI0ZEUyMGE4OSIsInJvbGUiOiJwcm9maWxlX2lkZW50aXR5IiwiYXV0aG9yaXphdGlvbklkIjoiZTI1M2JiZjUtZDNiOS00ZmVmLWExZjAtMmRkYjgzNGExNzAzIiwiaWF0IjoxNzIxNzQ1NDQ2LCJleHAiOjE3MjE3NDcyNDZ9.tIt8_O1SMP9MThQ9KnihhackfR1zIgoZr8RddU0aF2w",
Expand All @@ -32,19 +33,19 @@ describe("getLensMessage", () => {
},
})
).resolves.toMatchObject({
actionResponse: "",
transactionId: "",
buttonIndex: 1,
inputText: "",
profileId: "0x01df7e",
pubId: "0x01-0x01",
account: "0x01df7e",
post: "0x01-0x01",
isValid: false,
url: "http://localhost:3000/examples/multi-protocol",
});
});

it("throws an error if the message cannot be decoded", async () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function -- we don't care
consoleErrorSpy.mockImplementation(() => {});
consoleErrorSpy.mockImplementation(() => { });
const farcasterMessage: FrameActionPayload = {
untrustedData: {
fid: 1689,
Expand Down
4 changes: 2 additions & 2 deletions packages/render/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@
],
"devDependencies": {
"@farcaster/frame-host-react-native": "^0.0.19",
"@lens-protocol/client": "^2.3.2",
"@lens-protocol/client": "^0.0.0-canary-20250224081220",
"@rainbow-me/rainbowkit": "^2.1.2",
"@remix-run/node": "^2.8.1",
"@types/react-native": "^0.73.0",
Expand All @@ -350,7 +350,7 @@
"license": "MIT",
"peerDependencies": {
"@farcaster/frame-host-react-native": "^0.0.19",
"@lens-protocol/client": "^2.0.0",
"@lens-protocol/client": "^0.0.0-canary-20250224081220",
"@rainbow-me/rainbowkit": "^2.1.2",
"@types/react": "^18.2.0",
"@types/react-native": "^0.73.0",
Expand Down
Loading
Loading