2 Ready-to-Use Farcaster Frames

Farcaster's groundbreaking Frames introduce a whole new way to experience social media, leaving Twitter (aka X) in the dust.

The latter has long been the main application for web3 and crypto-related content, users rely on Twitter to keep updated about new projects, tokenomics, airdrops, updates and much more. However, the app hasn’t done much to adapt to the web3 community values, which preach decentralization and permissionlessness.

Farcaster is a protocol completely open-source, where developers can make use of their creativity and build decentralized social media applications and more. The most used Farcaster client is called Warpcaster, which serves a Twitter-like user experience.

Just recently, Farcaster launched Frames, and its adoption increased exponentially. In Warpcaster, “tweets” are called “casts”, and users can embed links into these “casts”. Now these links can integrate Frames and display extra components to the embedding. This feature allows users to perform a suite of functions, both on-chain and off-chain, including minting NFTs, displaying slides, creating polls, making games and everything that comes to the developers’ minds.

As a Python developer with little knowledge of JavaScript, I wanted to create my own Frames to promote my project Post3, without getting into much advanced code.

Fortunately, I found a tweet by Dan Romero, one of the founders of Farcaster, showing how to make a Frame in 5 minutes.

I took the example and I built two types of Frames with OpenGraph tags. The first one is a linktree-like Frame, which was simply integrated into my Post3 Engine website. For the second I used basic Javascript and I made slides. Let’s now take a look at both Frames.

Linktree-like Frame

Making this kind of Frames is probably the simplest way to start because you only need to use the OpenGraph tags provided by Farcaster’s documentation in your HTML script.

<!DOCTYPE HTML>
<html lang="en-GB">
<head>
    <!-- Frames -->
    <meta charSet="utf-8"/>
    <meta name="viewport" content="width=device-width"/>
    <meta property="og:title" content="Post3 Links" />
    <meta property='og:image' content="https://images.mirror-media.xyz/publication-images/TayvxH_MswvGYxcV5AnJX.png?height=672&width=1344" />
    <meta property="fc:frame" content="vNext" />
    <meta property="fc:frame:image" content="https://images.mirror-media.xyz/publication-images/TayvxH_MswvGYxcV5AnJX.png?height=672&width=1344" />

    <meta property="fc:frame:button:1" content="Reports 📊" />
    <meta property="fc:frame:button:1:action" content="link" />
    <meta property="fc:frame:button:1:target" content="https://mirror.xyz/post3.eth" />

    <meta property="fc:frame:button:2" content="Datasets 🌊" />
    <meta property="fc:frame:button:2:action" content="link" />
    <meta property="fc:frame:button:2:target" content="https://market.oceanprotocol.com/profile/post3.eth" />

    <meta property="fc:frame:button:3" content="Engine 🚀" />
    <meta property="fc:frame:button:3:action" content="link" />
    <meta property="fc:frame:button:3:target" content="https://www.post3.xyz"/>

    <meta property="fc:frame:button:4" content="Discord 🫂" />
    <meta property="fc:frame:button:4:action" content="link" />
    <meta property="fc:frame:button:4:target" content="https://www.discord.gg/Z5Q2VAUTnu" />
</head>

The HTML script above generates the following on Warpcaster:

Linktree-like Frame on Warpcaster
Linktree-like Frame on Warpcaster

The flow is pretty straightforward, I added four buttons with the OpenGraph tags and their respective links. They are all <meta> HTML elements which are all defined inside the <head> element. Since this was integrated into my website, I have my own <body> HTML architecture, but you can try it with a simple “Hello World” page.

To deploy the website, you can use the example provided by Dan Romero, which uses Replit.

Slide Frame

To make a slide Frame I utilized JavaScript adapted from another piece of code. This Frame uses buttons to change the slide and redirect to external links. The code is available at my Replit, but let’s take a look at it below:

const http = require("http");

const preimage_url =
  "https://images.mirror-media.xyz/publication-images/tjtQ7ZJaeVVLauO2oiVzf.png?height=672&width=1344";
const postimage_1 ="https://arweave.net/Yreyl7-ZFTR8H-EZl7_Jk50S5JuXH48FulZpIfAZnDE";
const postimage_2 ="https://arweave.net/uZaQdegxucnWHYhEr-XCEhTqfmdpoZB55yYnlNMw-GI";
const postimage_3 ="https://arweave.net/y-smGQwDJTcVJM0n4UFCc1GDvtL6fd2xIY6MVRYAF38";

const REPORT_LINK =
  "https://mirror.xyz/post3.eth/M9fc-1rXGRak0i3gtkXHOlKLT8WTmd43I8A_A3YtMTg?referrerAddress=0x";

const DATASET_LINK = "https://market.oceanprotocol.com/asset/did:op:dee9e208d792567f0e22106073c4da918ac77862106aba7fcd3be15e28775780"

const MORE_LINK = "https://bento.me/post3";

const server = http.createServer((req, res) => {
  if (req.url === "/") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.write(`
     <html>
       <head>
         <link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
         <meta charSet="utf-8"/>
         <meta name="viewport" content="width=device-width"/>
         <meta property="og:title" content="Weekly Report" />
         <meta property='og:image' content=${preimage_url} />
         <meta property="fc:frame" content="vNext" />
         <meta property="fc:frame:image" content=${preimage_url} />
         <meta property="fc:frame:button:1" content="Full Report 📊" />
         <meta property="fc:frame:button:1:action" content="link" />
         <meta property="fc:frame:button:1:target" content=${REPORT_LINK} />
         <meta property="fc:frame:button:2" content="Next ⏩" />
         <meta property="fc:frame:post_url" content="https://post-3-reports-macrodrigues.replit.app/slide_1" />
       </head>
       <body>
        <p style="font-family: 'Open Sans', sans-serif; font-size: 50px;">More about Post3 <a href=${MORE_LINK} target="_blank" style="color: rgb(0, 122, 255);">here</a>.</p>
       </body>
     </html>
       `);
    res.end();
  } else if (req.url === "/slide_1") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.write(`
    <html>
      <head>
          <link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
          <meta property="og:title" content="Weekly Report" />
          <meta property='og:image' content=${postimage_1} />
          <meta property="fc:frame" content="vNext" />
          <meta property="fc:frame:image" content=${postimage_1} />          
          <meta property="fc:frame:button:1" content="Next ⏩" />
          <meta property="fc:frame:post_url" content="https://post-3-reports-macrodrigues.replit.app/slide_2" />
      </head>
       <body>
        <p style="font-family: 'Open Sans', sans-serif; font-size: 50px;">More about Post3 <a href="https://bento.me/post3" target="_blank" style="color: rgb(0, 122, 255);">here</a>.</p>
       </body>
    </html>
       `);
    res.end();
  } else if (req.url === "/slide_2") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.write(`
    <html>
      <head>
          <link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
          <meta property="og:title" content="Weekly Report" />
          <meta property='og:image' content=${postimage_2} />
          <meta property="fc:frame" content="vNext" />
          <meta property="fc:frame:image" content=${postimage_2} />
          <meta property="fc:frame:button:1" content="Next ⏩" />
          <meta property="fc:frame:post_url" content="https://post-3-reports-macrodrigues.replit.app/slide_3" />
      </head>
       <body>
        <p style="font-family: 'Open Sans', sans-serif; font-size: 50px;">More about Post3 <a href="https://bento.me/post3" target="_blank" style="color: rgb(0, 122, 255);">here</a>.</p>
       </body>
    </html>
       `);
    res.end();
  } else if (req.url === "/slide_3") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.write(`
    <!DOCTYPE html>
    <html>
      <head>
          <link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
          <meta property="og:title" content="Weekly Report" />
          <meta property='og:image' content=${postimage_3} />
          <meta property="fc:frame" content="vNext" />
          <meta property="fc:frame:image" content=${postimage_3} />
          <meta property="fc:frame:button:1" content="Full Report 📊" />
          <meta property="fc:frame:button:1:action" content="link" />
          <meta property="fc:frame:button:1:target" content=${REPORT_LINK} />
          <meta property="fc:frame:button:2" content="Buy Dataset 🌊" />
          <meta property="fc:frame:button:2:action" content="link" />
          <meta property="fc:frame:button:2:target" content=${DATASET_LINK} />
      </head>
      <body>
       <p style="font-family: 'Open Sans', sans-serif; font-size: 50px;">More about Post3 <a href="https://bento.me/post3" target="_blank" style="color: rgb(0, 122, 255);">here</a>.</p>
      </body>
    </html>
       `);
    res.end();
  } else {
    // Catchall 404 Route
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Page not found");
  }
});

server.listen(3000, () => {
  console.log("Server running on port 3000");
});

This script generates the following Frame:

Slide Frame on Warpcaster
Slide Frame on Warpcaster

When the user clicks on Next it takes him/her to another image until it reaches the last slide.

Final slide with external link options
Final slide with external link options

In the final slide above, the user has the option to either open the full report or go to the page to acquire the dataset from which the charts were obtained.

I know, at this point JavaScript stewards are looking at my piece of code and making it much shorter and efficient. However, as a non-JS developer, I found this structure easy to understand and follow for beginners.

👉 Join Post3 Discord community here. Follow Post3 on Twitter (aka X) and Farcaster

Final Notes

During the code screening, you’ve probably noticed that the most important OG tag is the fc:frame:button:$idx:action, because it grants dynamic interaction to the Frames. The actions of the buttons can be postpost_redirectmint or link. Head to this documentation to learn more.

At the moment I’ve only explored off-chain interactions, but you can also mint NFTs directly from the Warpcaster application with Frames and probably more features will be added shortly.

While the basic building blocks (OG tags) might seem simple, developers are already creating complex experiences like video games. This technology has huge potential for the future, and Farcaster's can be proud of changing forever the paradigm of traditional social networks. To truly unlock its power, Frames need to become more accessible, allowing everyone to participate without requiring coding expertise.

Subscribe to Marco Rodrigues
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.