Skip to main content

XMTP Frames Integration Guide

The XMTP community has been actively discussing and implementing ways to enhance user experience by supporting frames within XMTP applications. An effort in this direction is detailed in a community post Supporting Frames in XMTP.

This video shows how its implemented in xmtp.chat

Introduction

The XMTP Frames guide you're looking at is designed to help developers integrate XMTP frames into their applications. It covers both the use of protocol libraries, which enable the creation and handling of XMTP frames, and the practical aspects of rendering these frames within an application. Here's a breakdown to clarify the separation:

Protocol Libraries

These are the foundational tools that allow developers to create, sign, and manage XMTP frames. The protocol libraries are essential for interacting with the XMTP network at a lower level, handling the creation of frames, signing payloads, and managing frame actions. Key aspects include:

Rendering Frames in Your Application

📥 Need a quick reference? Check out this GitHub repo: xmtp-quickstart-frames

This part of the guide focuses on how to render XMTP frames within your application, making the frames interactive and visually integrated. It includes:

  • Validating Frame URL: Ensuring the URL embedded within the message content is appropriate for frame processing and meets XMTP standards.
  • Getting Frame Metadata: Extracting and processing metadata from frames, including images, titles, buttons, and URLs, to facilitate rendering and interaction.
  • Rendering Frames: Dynamically generating a Frame component using the extracted metadata to visually present the frame content within your application.
  • Handling Clicks in the Frames: Interpreting user interactions within a frame to determine and execute the corresponding action, such as navigating to a link or updating frame content.
  • Sign XMTP Payload: Authenticating and securing frame actions by signing XMTP payloads, ensuring the integrity and origin of frame interactions.

Use cases

For inspiration and a deeper dive into how XMTP Frames can work across various domains, refer to the XMTP Frames Use Cases section below.


Protocol Libraries

These are the foundational tools that allow developers to create, sign, and manage XMTP frames. The protocol libraries are essential for interacting with the XMTP network at a lower level, handling the creation of frames, signing payloads, and managing frame actions. Key aspects include:

Install Required Packages

To start, add the necessary XMTP packages to your project:

npm install @xmtp/frames-client @xmtp/frames-validator

These packages enable your frame to send, receive, and validate messages across different protocols.

Declare Protocol Compatibility

Use a meta tag in your frame's HTML to declare the client protocols your frame supports:

<meta property="of:accepts:xmtp" content="2024-02-01" />

This informs client applications about the protocols your frame can interact with.

Validate Incoming Messages

Implement message validation using @xmtp/frames-validator to ensure the authenticity of incoming POST requests. This step is crucial for security, especially when dealing with multiple protocols.

import { validateFramesPost } from "@xmtp/frames-validator";

export function handler(requestBody: any) {
if (requestBody.clientProtocol.startsWith("xmtp")) {
const { verifiedWalletAddress } = await validateFramesPost(requestBody);
// Handle verified XMTP payload
} else {
// Handle Farcaster or other protocol payloads
}
}

XMTP Frames are also supported in onchainkit.

Enable Secure Communication

Use @xmtp/frames-client to sign and send frame actions securely. This package facilitates interoperable communication by ensuring that messages are securely signed and verifiable.

const xmtpClient = await Client.create(wallet);
const framesClient = new FramesClient(xmtpClient);

const frameUrl = "https://www.myframe.xyz";

// Read data from a frame
const frameMetadata = await framesClient.proxy.readMetadata(frameUrl);

// Get a proxied image URL, which you can use directly in an <image> tag
const imageUrl = framesClient.proxy.mediaUrl(
frameMetadata.metaTags["fc:frame:image"],
);

// Handle a click to button 2 from a conversation with topic "/xmtp/0/123" and participant addresses "abc" and "xyz"
const payload = await signFrameAction({
frameUrl,
buttonIndex: 2,
conversationTopic: "/xmtp/0/123",
participantAccountAddresses: ["abc", "xyz"],
});

// If the button action type was `post`
const updatedFrameMetadata = await framesClient.proxy.post(frameUrl, payload);
// If the button action type was `post_redirect`
const { redirectedTo } = await framesClient.proxy.postRedirect(
frameUrl,
payload,
);

Rendering Frames in Your Application

This part of the guide focuses on how to render XMTP frames within your application, making the frames interactive and visually integrated. It includes:

Validating Frame URL

The validation of frame URLs is performed using a regular expression to identify URLs within the message content. Once a URL is identified, it's processed to extract metadata, including frame information.

const words = message.content?.split(/(\r?\n|\s+)/);
const urlRegex =
/^(http[s]?:\/\/)?([a-z0-9.-]+\.[a-z0-9]{1,}\/.*|[a-z0-9.-]+\.[a-z0-9]{1,})$/i;

// Split potential concatenated URLs based on "http" appearing in the middle of the string
const splitUrls = (word) => {
const splitPattern = /(?=http)/g;
return word.split(splitPattern);
};

// Then, in your Promise.all block, adjust the logic to first split words that could be concatenated URLs
void Promise.all(
words.flatMap(splitUrls).map(async (word) => {
// Use flatMap with the splitUrls function
const isUrl = !!word.match(urlRegex)?.[0];
if (isUrl) {
//Get frame info
}
}),
);

Getting Frame Metadata

Frame metadata is retrieved using the FramesClient.readMetadata method. This metadata includes essential frame details such as image, title, buttons, and post URL.

const BUTTON_PREFIX = "fc:frame:button:";
const IMAGE_PREFIX = "fc:frame:image";
const POST_URL_PREFIX = "fc:frame:post_url";
const TITLE_PREFIX = "og:title";

const buttons = [];
let image = "";
let postUrl = "";
let title = "";
for (const key in extractedTags) {
if (key.startsWith(BUTTON_PREFIX)) {
if (!key.includes(":action")) {
const buttonIndex = parseInt(key.replace(BUTTON_PREFIX, ""), 10) - 1;
// Initialize the button object if it doesn't exist
buttons[buttonIndex] = buttons[buttonIndex] || {};
// Set the label for the button
buttons[buttonIndex].label = extractedTags[key];
}
} else if (key.startsWith(IMAGE_PREFIX)) {
image = extractedTags[key];
} else if (key.startsWith(POST_URL_PREFIX)) {
postUrl = extractedTags[key];
} else if (key.startsWith(TITLE_PREFIX)) {
title = extractedTags[key];
}

// Separately handle action tags to fill the actions object and directly assign to buttons
if (key.includes(":action")) {
const actionIndex = parseInt(key.split(":")[3], 10) - 1; // Adjusted to match buttonIndex calculation
// Initialize the button object if it doesn't exist
buttons[actionIndex] = buttons[actionIndex] || {};
// Set the action for the button
buttons[actionIndex].action = extractedTags[key];
}
}

Rendering Frames

Frames are rendered by dynamically creating a Frame component based on the extracted frame information. This component is responsible for displaying the frame's content, including images, titles, and interactive buttons.

//word is the extracted frame url
const framesClient = new FramesClient(client);
const metadata = await framesClient.proxy.readMetadata(word);
if (metadata) {
const info = getFrameInfo(metadata.extractedTags);
console.log("Frame info: ", info);
info.url = metadata.url;
setFrameInfo(info);
}

Once we have the frame metadata we can render them visually

{
!isLoading && frameInfo && (
<Frame
info={frameInfo}
handleClick={handleFrameButtonClick}
frameButtonUpdating={frameButtonUpdating}
frameUrl={frameInfo.url} // Passing the new prop
/>
);
}

Finally, we render the buttons dynaically

{buttons?.map((button, index) => {
if (!button) {
return null;
}
const handlePress = () => handleClick(index + 1);
const buttonStyle = {
...styles.button,
...(index === 0 ? styles.firstButton : {}),
...(index === buttons.length - 1 ? styles.lastButton : {}),
};
return (
<button
key={`${button}-${index}`}
onClick={handlePress}
disabled={frameButtonUpdating === index + 1}
style={buttonStyle}>
{button.label}{" "}
{button.action === "post_redirect" && (
<span style={styles.redirectIcon}></span>
)}
</button>
);

Handling Clicks in the Frames

Clicks within the frame are handled by the handleFrameButtonClick function. This function determines the action to be taken based on the button clicked within the frame.

  const handleFrameButtonClick = async (buttonNumber) => {
if (!frameInfo) {
return;
}
const actionType = frameInfo.buttons[buttonNumber - 1].action;
console.log("Action type: ", actionType);
console.log(frameInfo);

const conversationTopic = message.conversationTopic;

//Continue with Frames logic
...

Sign XMTP Payload

To sign the XMTP payload for frame actions, the FramesClient.signFrameAction method is used. This method signs the action to be taken with the frame, such as posting back to the frame's URL.

const xmtpClient = await Client.create(wallet);
const framesClient = new FramesClient(xmtpClient);
const payload = await framesClient.signFrameAction({
frameUrl,
buttonIndex: buttonNumber,
conversationTopic,
participantAccountAddresses: [peerAddress, xmtpClient.address],
});
console.log("Payload: ", payload);

Handle Frame actions

If the frame action involves a redirect, the framesClient.proxy.postRedirect method is used to handle the redirection to the specified URL after the frame action is executed.

if (actionType === "post") {
const updatedFrameMetadata = await framesClient.proxy.post(
frameInfo.postUrl,
payload,
);
const updatedFrameInfo = getFrameInfo(updatedFrameMetadata.extractedTags);
setFrameInfo(updatedFrameInfo);
} else if (actionType === "post_redirect") {
// If the button action type was `post_redirect`
console.log("Redirecting to: ", frameUrl);
const { redirectedTo } = await framesClient.proxy.postRedirect(
frameInfo.postUrl,
payload,
);
console.log("Redirected to: ", redirectedTo);
window.open(redirectedTo, "_blank").focus();
}

The guide combines these two aspects to provide a comprehensive overview of both the backend (protocol libraries) and frontend (rendering and interaction) components necessary for integrating XMTP frames into an application.

Was the information on this page helpful?
powered by XMTP