Skip to content

Build a transaction Frame

Follow these steps to build a transaction Open Frame that can be displayed in an app built with XMTP.

To build a transaction Open Frame:
  1. Create a boilerplate Next.js app.
cmd
npx create-next-app my-next-app
  1. Install @coinbase/onchainkit as a dependency.
cmd
npm i @coinbase/onchainkit
  1. Add the base URL in .env.local as a NEXT_PUBLIC_BASE_URL environment variable.
  2. In app/page.tsx, replace the boilerplate with the following code — this is what will be rendered as the initial frame:
import { getFrameMetadata } from "@coinbase/onchainkit/frame";
import { Metadata } from "next";
 
const frameMetadata = getFrameMetadata({
  // Accepts and isOpenFrame keys are required for Open Frame compatibility
  accepts: { xmtp: "2024-02-09" },
  isOpenFrame: true,
 
  buttons: [
    {
      // Whatever label you want your first button to have
      label: "Make transaction",
      // Required 'tx' action for a transaction frame
      action: "tx",
      // Below buttons are 2 route urls that will be added in the next steps.
      // Target will send back info about the transaction
      target: `${process.env.NEXT_PUBLIC_BASE_URL}/api/transaction`,
      // postUrl will send back a transaction success screen
      postUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/api/transaction-success`,
    },
  ],
 
  // This is the image shown on the default screen
  // Add whatever path is needed for your starting image
  // In this case, using an Open Graph image
  image: `${process.env.NEXT_PUBLIC_BASE_URL}/api/og?transaction=null`,
});
 
export const metadata: Metadata = {
  title: "Transaction Frame",
  description: "A frame to demonstrate transactions",
  other: {
    ...frameMetadata,
  },
};
 
export default function Home() {
  return (
    <>
      <h1>Open Frames Tx Frame</h1>
    </>
  );
}
  1. Add the route to /api/transaction/route.tsx. The route is used to get information about the transaction that is sent to the target URL.
import { NextRequest, NextResponse } from "next/server";
import { parseEther, encodeFunctionData } from "viem";
import type { FrameTransactionResponse } from "@coinbase/onchainkit/frame";
import { getXmtpFrameMessage } from "@coinbase/onchainkit/xmtp";
 
async function getResponse(req: NextRequest): Promise<NextResponse | Response> {
  const body = await req.json();
  const { isValid } = await getXmtpFrameMessage(body);
  if (!isValid) {
    return new NextResponse("Message not valid", { status: 500 });
  }
 
  // This optional param is needed in scenarios where you're interacting with a smart contract
  // The values passed will depend on the implementation details of your contract; this is just an example
  const data = encodeFunctionData({
    abi: JSON.parse(contractAbi),
    functionName: "publicMint",
    args: [],
  });
 
  const txData: FrameTransactionResponse = {
    // Sepolia or whichever chain id; we suggest avoiding mainnet for now
    chainId: `eip155:11155111`,
    method: "eth_sendTransaction",
    params: {
      abi: [],
      // Address receiving the transaction — in this case, hi.xmtp.eth
      to: "0x194c31cAe1418D5256E8c58e0d08Aee1046C6Ed0",
      // Transaction value in eth sent back as wei — in this case, ~1 cent.
      value: parseEther("0.0000032", "wei").toString(),
      data, // If applicable
    },
  };
  return NextResponse.json(txData);
}
 
export async function POST(req: NextRequest): Promise<Response> {
  return getResponse(req);
}
  1. Get the confirmation frame screen HTML via the @coinbase/onchainkit helper to the success image and the success button action — in this case a redirect outside of the frame. (The redirect logic is outside the scope of this tutorial.)
export const confirmationFrameHtml = getFrameHtmlResponse({
  accepts: {
    xmtp: "2024-02-09",
  },
  isOpenFrame: true,
  buttons: [
    {
      action: "post_redirect",
      label: "Learn more about transaction frames",
    },
  ],
  postUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/api/end`,
  image: `${process.env.NEXT_PUBLIC_BASE_URL}/api/og?transaction=0.0000032`,
});
  1. Add the route to return the success frame HTML with the new meta tags at api/transaction-success/route.ts.
import { confirmationFrameHtml } from "@/app/page";
import { getXmtpFrameMessage } from "@coinbase/onchainkit/xmtp";
import { NextRequest, NextResponse } from "next/server";
 
async function getResponse(req: NextRequest): Promise<NextResponse> {
  const body = await req.json();
  const { isValid } = await getXmtpFrameMessage(body);
 
  if (!isValid) {
    return new NextResponse("Message not valid", { status: 500 });
  }
 
  return new NextResponse(confirmationFrameHtml);
}
export async function POST(req: NextRequest): Promise<Response> {
  return getResponse(req);
}
  1. Send your transaction Frame in an XMTP message and try interacting with it!

Resources

If you need an XMTP messaging app to use, try one of these: