Building Your First Farcaster Frame: A Step-by-Step Guide to Creating a Crypto Price Feed
Farcaster is an innovative decentralized social media protocol that’s redefining how we interact online. Unlike traditional centralized social media platforms, Farcaster operates on a permissionless network, allowing users to own their data and identities while enjoying a familiar social media experience.
Farcaster is not a social media platform, but a protocol, upon which decentralized social media can be built. These social media app built on farcaster protocol are called farcaster clients.
Key features of Farcaster includes:
- Open Protocol: Developers can build applications on top of Farcaster without permission.
- Decentralized Identity: Users control their own identities.
- Data Portability: Your social graph and content can be moved between different Farcaster clients.
- Censorship Resistance: No central authority can unilaterally remove content or ban users.
- Different clients to choose from: Various client apps offer different user interfaces and features.
Farcaster combines the best aspects of Web3 technology with the usability of traditional social media. It’s fostering a new ecosystem of decentralized applications, one of which is the innovative concept of Farcaster Frames — the focus of this article.
Farcaster Frames: Interactive Content on farcaster
Frames are a groundbreaking feature of farcaster that allows developers to create interactive, app-like experiences in form of a post within Farcaster.
Frames are essentially mini web applications embedded in Farcaster posts. They can display content, accept user input, process it and update in real-time, all within the confines of a farcaster post. This capability turns Farcaster from a simple social media platform into a powerful hub of mini apps.
How Farcaster Frames Work
Farcaster Frames operate through a cycle of rendering, user interaction, and dynamic updates. When a user encounters a post containing a frame, their Farcaster client fetches the HTML from the frame’s URL. The client then parses this HTML, focusing on specific meta tags that define the frame’s properties, and renders the frame within the post. This initial render displays images, text, and interactive elements as specified in the frame’s HTML.
Users can interact with the frame through predefined elements like buttons or text inputs. When a user takes action, such as clicking a button, the frame captures this interaction. This typically triggers a request to a specified API endpoint on the frame’s server, as defined in the frame’s HTML using a special meta tag. The request can include data about the user, the action they took and any input they provided.
Upon receiving the API request, the frame’s server processes it. This processing could involve various operations such as querying a database, performing calculations, or even initiating a blockchain transaction. After processing, the server responds by sending back new HTML for an updated frame. This HTML contains fresh meta tags that define the new state of the frame. This creates the illusion of an interactive, stateful application within the post.
This cycle can repeat, allowing for multi-step interactions. Each interaction can result in a new frame state, enabling complex flows and dynamic content.
Examples of Farcaster Frames
Farcaster frames can be any mini application allows user interaction. example of a farcater frames are:
- Polls and Surveys: Users can vote directly in a post.
- Games: Simple games playable within a post.
- NFT Minting: Allow users to mint NFTs directly from a post.
- E-commerce: Showcase products and enable purchases without leaving Farcaster.
- Price Feeds: Display the current cryptocurrency prices (which we’ll build in this guide).
Open Graph Protocol and Farcaster’s Extension
Open Graph is a protocol originally developed by Facebook to enable any web page to become a rich object in a social graph. It allows websites to control how their content appears when shared on social media platforms. Open Graph uses meta tags in the HTML of a webpage to define properties like title, description, and image for the shared content.
Example of Open Graph meta tags properties include:
- og:title — The title of the object
- og:type — The type of object (e.g., article, video)
- og:image — An image URL representing the object
- og:url — The canonical URL of the object
Farcaster’s Extension of Open Graph for frames
Farcaster innovatively extends the Open Graph protocol to create interactive frames within its ecosystem. While maintaining compatibility with standard Open Graph tags, Farcaster introduces additional meta tags that define frame-specific properties and behaviors.
Example of Farcaster Frame meta tags properties include:
- fc:frame — Indicates that the page is a Farcaster Frame
- fc:frame:image — The image to display in the frame
- fc:frame:button:1 — Text for the first button (up to 4 buttons can be defined)
- fc:frame:post_url — The endpoint to call when a button is pressed
This extension allows Farcaster to leverage the familiarity and wide support of Open Graph while introducing powerful new capabilities for interactive content. By using these extended meta tags, developers can create dynamic, stateful frames that go beyond simple link previews, enabling rich applications within the Farcaster ecosystem. The combination of standard Open Graph tags and Farcaster-specific tags allows frames to be backward compatible with other platforms that support Open Graph, while providing enhanced functionality within Farcaster clients.
For a comprehensive list of all available frame meta tags properties and their usage, visit the official Farcaster Frame specification at: https://docs.farcaster.xyz/reference/frames/spec
Libraries and Frameworks for Building Farcaster Frames
While Farcaster frames can be built using plain HTML and JavaScript, several libraries and frameworks have emerged to simplify the development process. These tools provide abstractions and utilities for frame creation. Let’s explore some of the popular options:
- onchainkit — A library built by coinbase which includes utilities to help developers build top-tier onchain apps. It provides components and utility functions for building Farcaster frames.
- frames.js — A lightweight JavaScript library designed specifically for Farcaster frame development. It provides a simple API for creating and managing frames, handling user interactions, and updating frame content.
- frog — A minimal & lightweight framework built on top of Hono for building Farcaster frames. It offers a complete development environment for your frame.
These libraries and frameworks can significantly speed up the development process and help ensure that your frames adhere to the Farcaster specification. However, it’s important to note that the ecosystem around Farcaster frames is rapidly evolving, and new tools are regularly being introduced.
For the purposes of this guide, we will not be using any specific framework or library. Instead, we’ll build our frame using plain HTML and Typescript in a nextjs application to ensure a fundamental understanding of how frames work. This approach will provide you with the knowledge needed to work with any framework or library in the future.
By learning the basics first, you’ll be better equipped to choose the right tool for your specific needs and to understand the underlying mechanics of any framework you might use in the future.
Essential Development Tools for Farcaster Frames
When developing Farcaster frames, several tools are important. Here’s a list of essential development tools:
- Neynar API: Neynar provides a set of APIs that simplify interaction with the Farcaster protocol. While not strictly necessary for frame development, it can be extremely useful for integrating Farcaster data into your frames or building more complex applications. It is commonly used for Fetching user data and social graphs
Learn more at: https://neynar.com - Warpcast Frame Validator: The Warpcast Frame Validator is a crucial tool for testing and validating your Farcaster frames. It allows you to input your frame’s URL and simulates how it would render and behave in a Farcaster client. It is used for verifying your frame’s HTML and meta tags and ensuring compliance with Farcaster frame specifications.
Access it at: https://warpcast.com/~/developers/frames - Framegear: Framegear is a part of onchainkit project. It is a type of Frame validator, but while warpcast frame validator only work with live links, Framegear allows you to test and validate yout frame while its being developed locally.
Follow the simple instructions here to have it on your local machine https://onchainkit.xyz/frame/framegear
Project Overview: Building a Crypto Price Feed Frame
In this guide, we’ll be creating a practical and informative Farcaster frame. A real-time cryptocurrency price feed. This project will serve as an excellent introduction to frame development while also providing a useful tool for Farcaster users interested in cryptocurrency prices.
Our Crypto Price Feed Frame will:
- Allow user to request for current prices for Ethereum (ETH) and Bitcoin (BTC) in USD.
- Get the Price, Generate an image on the fly with the current prices on it
- Provide a “Refresh” option to fetch the latest price data.
- Showcase how to integrate external API calls into a Farcaster frame.
If you have a farcaster account, you can test out the finished frame here: https://warpcast.com/0xadek/0x5f695f99
By the end of this guide, you’ll have a working cryptocurrency price feed frame and the knowledge to start creating your own custom frames for the Farcaster ecosystem.
To get started, we’ll start by initializing a Next.js project and install the required libraries
You must not select the same options as i did, but these are the options I selected for the Nextjs initialization. Once initialized, cd into the project and install the dependencies
Next up, install the required libraries from the project.
npm i axios satori sharp
- axios is needed for api calls
- satori and sharp are needed for image generation
Fonts and Assets
If you’re following along, you may want to copy the images and fonts used in this guide and put them in their respective directory.
Images: https://github.com/AjayiMike/price-feed-farcaster-frame/tree/main/public/images
Fonts: https://github.com/AjayiMike/price-feed-farcaster-frame/tree/main/src/fonts
Environmental variables
Get your subgraph api key from the graph studio, create .env
file and paste this into it.
NEXT_PUBLIC_HOST_URL=http://localhost:3000
SUBGRAPH_API_KEY=<YOUR_SUBGRAPH_API_KEY>
WBTC_ADDRESS=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599
Don’t forget to replace <YOUR_SUBGRAPH_API_KEY>
with the api key generated from the graph studio.
Next, create src/constants/env.ts
and paste this into it
export const envVars = {
hostUrl: process.env.NEXT_PUBLIC_HOST_URL,
subgraphApiKey: process.env.SUBGRAPH_API_KEY,
WBTC_ADDRESS: process.env.WBTC_ADDRESS,
};
Clean up the layout component
The layout component, right now has bunch of code we don’t need, open up src/app/layout.tsx
and replace it’s content with
export const viewport = {
width: "device-width",
initialScale: 1.0,
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
The first page of the frame
The first page of your frame will be created the src/app/page.tsx
Replace the content of src/app/page.tsx with
import { Metadata } from "next";
import { envVars } from "../constants/env";
export const metadata: Metadata = {
title: "Price Feed Farcaster Frame",
description: "ETH & BTC Price Feed Farcaster Frame",
openGraph: {
title: "Price Feed Farcaster Frame",
description: "ETH & BTC Price Feed Farcaster Frame",
images: [`${envVars.hostUrl}/images/base.png`],
},
other: {
"fc:frame": "vNext",
"fc:frame:image": `${envVars.hostUrl}/images/base.png`,
"fc:frame:post_url": `${envVars.hostUrl}/api/price`,
"fc:frame:button:1": "Get Price",
},
};
export default function Home() {
return (
<main>
<h1 className="text-4xl font-bold">
ETH & BTC Price Feed Farcaster Frame
</h1>
</main>
);
}
This code sets up the initial frame for our Farcaster frame using Next.js. Here’s what it does:
- It defines metadata for the frame, including:
— Standard metadata (title, description)
— Open Graph metadata for social sharing
— Farcaster-specific metadata using `fc:frame` tags - The Farcaster metadata specifies:
— This page is a Farcaster frame (`fc:frame: “vNext”`)
— The image to display (`fc:frame:image`)
— The API endpoint to call when the button is pressed (`fc:frame:post_url`)
— The text for the interaction button (`fc:frame:button:1`) - The `Home` component renders a simple heading. This content isn’t visible in the Farcaster frame but may be useful for SEO or direct page access.
This setup creates a frame that displays an image and a “Get Price” button. When the button is pressed, it will trigger a request to our `/api/price` endpoint, which we’ll implement next to fetch and return the cryptocurrency prices.
Untility functions
Before defining our api route, there are a couple of necessary utility function we need to define.
formatLargeNumber — This function is pretty simple, and it is used to nicely format the dollar value of ETH and BTC.
Create a file named src/utils/index.ts
and paste this into it
export const formatLargeNumber = (
value: number,
excludeFraction = false
): string => {
if (excludeFraction) {
return value.toLocaleString("en-US", { maximumFractionDigits: 0 });
}
return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
};
getPrices — This is used to get ETH and BTC prices using UniswapV3 subgraph.
Create a file named src/utils/price.ts
and paste this into it
axios.post(endpoint, graphqlQuery, { headers });
if (response.data.error) {
throw new Error(response.data.error.message);
}
return {
eth: Number(response.data.data.bundle.ethPriceUSD),
btc:
Number(response.data.data.token.derivedETH) *
Number(response.data.data.bundle.ethPriceUSD)
};
};
The `getPrices` function fetches current prices for Ethereum (ETH) and Bitcoin (BTC) using the Uniswap V3 subgraph. Here’s how it works:
1. It uses axios to make a POST request to the subgraph endpoint.
2. The GraphQL query requests two pieces of information:
— The ETH price in USD from the ‘bundle’ entity
— The ‘derivedETH’ value for BTC (WBTC address) from the ‘token’ entity
3. After receiving the response, it calculates:
— ETH price in USD directly from the response
— BTC price in USD by multiplying BTC’s derivedETH value with the ETH price in USD
4. The function returns an object with both prices.
This approach allows us to get accurate, real-time prices from Uniswap’s subgraph. The use of a subgraph query ensures we’re getting data directly from the blockchain, providing reliable price information for our frame.
generatePriceImageSvg and generateBase64PriceImage — These functions are used to genetate the frame image, showing the current prices.
Create a file named src/utils/image.tsx
and paste this into it
import satori from "satori";
import { join } from "path";
import * as fs from "fs";
import { formatLargeNumber } from ".";
import { envVars } from "@/constants/env";
import sharp from "sharp";
const font = fs.readFileSync(
join(process.cwd(), "src/fonts/RedHatDisplayBlack.ttf")
);
const generatePriceImageSvg = async (
ethPrice: number,
btcPrice: number
): Promise<string> => {
return await satori(
<div
style={{
background: "rgba(13, 13, 15, 0.99)",
backgroundImage: `url(${envVars.hostUrl}/images/background.png)`,
backgroundRepeat: "no-repeat",
backgroundSize: "100% 100%",
display: "flex",
flexDirection: "column",
padding: "3.5rem",
width: "100%",
height: "100%",
alignContent: "center",
justifyContent: "space-between",
}}
>
<div style={{ display: "flex", justifyContent: "center"}}>
<span
style={{
fontSize: "18px",
lineHeight: "23.81px",
fontWeight: "900",
color: "rgba(255, 255, 255, 1)",
textShadow:
"0 0 5px #13547a, 0 0 10px #13547a, 0 0 5px #13547a, 0 0 5px #13547a",
}}
>
ETH and BTC Price
</span>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: "4rem",
width: "100%",
paddingBottom: "40px",
}}
>
<div style={{display: "flex",flexDirection: "column",alignItems: "center"}}>
<img
src={`${envVars.hostUrl}/images/eth.png`}
style={{ width: "60px", height: "60px" }}
/>
<span
style={{
color: "rgba(255, 255, 255, 1)",
fontSize: "22px",
lineHeight: "29.11px",
fontWeight: "900",
}}
>
${formatLargeNumber(ethPrice)}
</span>
</div>
<div style={{display: "flex", flexDirection: "column", alignItems: "center"}}>
<img
src={`${envVars.hostUrl}/images/btc.png`}
style={{ width: "60px", height: "60px" }}
/>
<span
style={{
color: "rgba(255, 255, 255, 1)",
fontSize: "22px",
lineHeight: "29.11px",
fontWeight: "900",
}}
>
{" "}
${formatLargeNumber(btcPrice)}
</span>
</div>
</div>
<div
style={{
fontSize: "14px",
position: "absolute",
color: "rgba(255, 255, 255, 1)",
bottom: "40px",
right: "20px",
}}
>
{new Date().toISOString()}
</div>
</div>,
{
width: 600,
height: 400,
fonts: [
{
data: font,
name: "Red Hat Display Black",
style: "normal",
},
],
}
);
};
export const generateBase64PriceImage = async (
ethPrice: number,
btcPrice: number
) => {
const svg = await generatePriceImageSvg(ethPrice, btcPrice);
return (await sharp(Buffer.from(svg)).toFormat("png").toBuffer()).toString(
"base64"
);
};
generatePriceImageSvg
: This function uses Satori to generate an SVG image displaying ETH and BTC prices. Here's what it does:
- Creates a styled div container with a background image
- Displays “ETH and BTC Price” as a title
- Shows ETH and BTC logos with their respective prices
- Includes a timestamp at the bottom right
- Uses custom fonts and styling for a polished look
generateBase64PriceImage
: This function builds upon generatePriceImageSvg
to create a base64-encoded PNG image. The process is:
- Call
generatePriceImageSvg
to get the SVG - Use Sharp to convert the SVG to a PNG buffer
- Convert the PNG buffer to a base64 string
These functions work together to create a visually appealing, up-to-date price display for the Farcaster frame. The use of SVG allows for crisp graphics, while the conversion to PNG ensures compatibility across different platforms.
The base64 encoding of the final image makes it easy for us to embed in the farcaster frame meta tag and help us avoid saving the image in the file system.
The API route
Remenber that in our first frame, we defined our post_url to be /api/price, that’s what we’ll be creating next.
Create a file named src/app/api/price/route.ts
, and paste the following in it
import { envVars } from "@/constants/env";
import { generateBase64PriceImage } from "@/utils/image";
import { getPrices } from "@/utils/price";
import { NextRequest, NextResponse } from "next/server";
async function getResponse(req: NextRequest): Promise<NextResponse> {
try {
const { eth, btc } = await getPrices();
const base64PriceImage = await generateBase64PriceImage(eth, btc);
return new NextResponse(`
<!DOCTYPE html>
<html>
<head>
<title>Price Feed Farcaster Frame</title>
<meta property="og:title" content="Price Feed Farcaster Frame" />
<meta
property="og:image"
content=${envVars.hostUrl}/images/base.png
/>
<meta property="fc:frame" content="vNext" />
<meta
property="fc:frame:image"
content="data:image/png;base64,${base64PriceImage}"
/>
<meta
property="fc:frame:post_url"
content=${envVars.hostUrl}/api/price
/>
<meta property="fc:frame:button:1" content="Refresh Price" />
</head>
<body>
<p>"Price Feed Farcaster Frame</p>
</body>
</html>
`);
} catch (error) {
return new NextResponse(`
<!DOCTYPE html>
<html>
<head>
<title>Price Feed Farcaster Frame</title>
<meta property="og:title" content="Price Feed Farcaster Frame" />
<meta
property="og:image"
content=${envVars.hostUrl}/images/error.png
/>
<meta property="fc:frame" content="vNext" />
<meta
property="fc:frame:image"
content=${envVars.hostUrl}/images/error.png
/>
<meta
property="fc:frame:post_url"
content=${envVars.hostUrl}
/>
<meta property="fc:frame:button:1" content="Reset" />
</head>
<body>
<p>"Price Feed Farcaster Frame</p>
</body>
</html>
`);
}
}
export async function POST(
req: NextRequest,
res: NextResponse
): Promise<Response> {
return getResponse(req);
}
export const dynamic = "force-dynamic";
This file (src/app/api/price/route.ts
) defines the API endpoint that handles the frame interactions. Here's a breakdown of its functionality:
- The main function
getResponse
is an async function that:
- Fetches the latest ETH and BTC prices using the
getPrices
function. - Generates a base64-encoded image of the prices using
generateBase64PriceImage
. - Returns an HTML response with embedded Farcaster frame metadata.
The HTML response includes:
- Standard HTML tags and a title.
- Open Graph metadata for social sharing.
- Farcaster-specific metadata (
fc:frame
tags) that define: The frame version, the dynamically generated price image (as a base64-encoded string), the post URL (which points back to this same endpoint) and a “Refresh Price” button
Error handling:
If an error occurs (e.g., failing to fetch prices), it returns an alternative HTML response. This error response includes different metadata, pointing to an error image and resetting the frame.
2. The POST
function:
This is the main export that Next.js uses to handle POST requests to this route. It simply calls getResponse
and returns its result.
The dynamic = "force-dynamic"
tells Next.js to always generate this page at request time, ensuring fresh data.
This setup creates a dynamic, interactive frame that displays current cryptocurrency prices and allows users to refresh the data. The use of base64-encoded images allows for real-time data visualization within the Farcaster frame constraints.
Testing and Deployment
To ensure your frame is correctly setup, you have to test, and then finally deploy once satisfied with your frame behaviour locally.
Local Development Testing
- Start your development server (ensure it is runing on port 3000).
- If you have the Framegear project set up (as mentioned earlier in the guide):
- Run Framegear (it should open on localhost:1337).
- Use Framegear to test your frame locally.
Deployment
Once satisfied with local testing, deploy your project. You can use Vercel or any hosting provider of your choice.
Environment Variable Setup and Redeployment
After deployment, set up the application environment variables on your hosting platform. Use the same variables as in your local .env
file. But Change NEXT_PUBLIC_HOST_URL
from localhost to your deployed app's URL.
Once env varaibles are in place, trigger a redeployment of your application. This step is crucial to ensure the new environment variables are applied.
Warpcast Frame Validator Testing
Once redeployed, using the deployed app’s URL, Validate and test your frame with the Warpcast Frame validator
Once you’re happy with how your frame works in the warpcast frame validator, you can then go ahead and post on warpcast if you want.
Key Points:
- Local testing with Framegear helps catch issues before deployment.
- Proper environment variable setup is crucial for the frame to function correctly in production.
- Redeployment after setting environment variables ensures they’re active in your deployed app.
- The Warpcast Frame Validator is the final check to ensure your frame works as expected in the Farcaster ecosystem.
By following these steps, you can ensure your Farcaster frame is thoroughly tested locally and functions correctly when deployed, ready for use in the Farcaster network.
Conclusion
Congratulations! You’ve successfully created your first Farcaster frame — a dynamic cryptocurrency price feed. This journey has taken you through the fundamentals of frame development, from understanding the Farcaster protocol to deploying a functional, interactive frame.
Key Takeaways:
- Farcaster Frames provide a powerful way to create interactive, app-like experiences within a decentralized social media context.
- The development process involves a combination of front-end design, API integration, and understanding of Farcaster-specific metadata.
- Tools like Satori and Sharp can be leveraged to create dynamic, visually appealing content for frames.
- Proper testing, both locally and post-deployment, is crucial for ensuring your frame functions correctly in the Farcaster ecosystem.
As you continue your journey with Farcaster frames, consider exploring:
- More complex interactions and multi-step flows within your frames
- Integration with other blockchain data or Web3 functionalities
- Creating frames that interact with smart contracts
- Developing frames for different use cases, such as polls, games, or decentralized applications
Remember, the Farcaster ecosystem is continually evolving, so stay connected with the community and keep an eye on updates to the frame specification.
Keep experimenting, stay curious, and happy framing!
Don’t forget to drop a clap and share if you find it helpful. Also, do well to drop a comment if you have any questions or would like to discuss this project further.
You can find the complete code here: https://github.com/AjayiMike/price-feed-farcaster-frame
Further study:
https://docs.farcaster.xyz/reference/frames/spec
https://www.youtube.com/watch?v=g_pkATT8pYU
https://www.youtube.com/live/M6JfvY4-APE?si=tN_ZlOVcZCq2TVU3&t=600