Skip to content

XMTP 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 identity of the user that interacted with the frame in Farcaster or a XMTP chat.

Steps

Create a new project

Create a new Next.js based Frames app

npx create-next-app@latest my-project --ts --eslint --tailwind --app
cd my-project

Add frames.js to your project

npm
npm install frames.js

Create your Frames app

Create a frames directory in your Next.js app directory and add the following file:

./app/frames/frames.ts
import { openframes } from "frames.js/middleware";
import { createFrames } from "frames.js/next";
import { getXmtpFrameMessage, isXmtpFrameActionPayload } from "frames.js/xmtp";
 
export const frames = createFrames({
  // basePath must point to the route of initial frame
  // in this case it will reside in app/frames/route.tsx therefore /frames
  basePath: "/frames",
  middleware: [
    openframes({
      clientProtocol: {
        id: "xmtp",
        version: "2024-02-09",
      },
      handler: {
        isValidPayload: (body) => isXmtpFrameActionPayload(body),
        getFrameMessage: async (body) => {
          if (!isXmtpFrameActionPayload(body)) {
            return undefined;
          }
 
          return getXmtpFrameMessage(body);
        },
      },
    }),
  ],
});

Your Frames app by default always supports Farcaster, now you also added XMTP support.

Create a route

Create following file:

./app/frames/route.tsx
/* eslint-disable react/jsx-key */
import { Button } from "frames.js/next";
import { frames } from "./frames";
 
const handleRequest = frames(async (ctx) => {
  let iAm: string | undefined;
 
  if (ctx.message) {
    iAm = (await ctx.message.walletAddress()) ?? "anonymous";
  }
 
  return {
    image: <span>{iAm ? `I am ${iAm}` : `Click the button`}</span>,
    buttons: [<Button action="post">Who am I?</Button>],
  };
});
 
export const GET = handleRequest;
export const POST = handleRequest;

If you have an existing page, render Frames in your metadata

./app/page.tsx
import { fetchMetadata } from "frames.js/next";
 
export async function generateMetadata() {
  return {
    title: "My Page",
    // provide a full URL to your /frames endpoint
    other: await fetchMetadata(
      new URL(
        "/frames",
        process.env.VERCEL_URL
          ? `https://${process.env.VERCEL_URL}`
          : "http://localhost:3000"
      )
    ),
  };
}
 
export default function Page() {
  return <span>My existing page</span>;
}

Run

npm
npm run dev

Done! 🎉