Building Farcaster Frame in Python using PyCaster

Summary: This post guides you to build a farcaster frame in Python. We’ve used Meroku dApp Store Kit APIs to build a mini app store frame. You’ll also get access to some handy functions to help build your own frame in Python.

The frame is available at https://pycaster-demo.dappstore.app

Farcaster is currently buzzing with activity, especially since the introduction of frames a few weeks ago. This development is particularly thrilling for me, given my background. My career began at a social gaming development company, where we were among the pioneers in adopting the OpenSocial framework around 2010, allowing us to launch applications and games across various social networks. This effectively leveled the playing field, giving every network access to tools comparable to those available through Facebook’s Apps framework.

The introduction of Farcaster frames represents a similarly transformative opportunity, opening up endless possibilities for engagement within the ecosystem. However, most resources for creating frames are tailored towards the JavaScript ecosystem. As a Python developer, I found the learning curve somewhat steep, which motivated me to create a comprehensive guide once I successfully developed my own frame

In this blog post, I aim to demystify the process and provide a roadmap for others like me. The topics covered include:

  • Understanding the Frames Interaction Flow
  • How to Build an App Store
  • Setting Up a Flask Application
  • Employing Advanced Techniques

This guide is designed to bridge the gap for Python developers looking to explore Farcaster frames, ensuring a smoother and more accessible entry into this exciting space.

Understanding Frames Interaction Flow

Frames as state machines

Consider frames as intricate state machines: they begin with an initial state, and users can navigate through the application via specific actions, such as clicking buttons or entering text. These actions trigger transitions to new states, with each state accessible only through predefined pathways. This mechanism ensures a controlled and intuitive user interaction flow within your application.

Frames as the frontend for your app

Frames serve as the dynamic frontend of your application. To facilitate this, you will need to implement a series of server-side operations that respond to POST requests, each time rendering a new “frame” based on user interactions. This setup allows for a responsive and engaging user experience, as your application dynamically adapts to user inputs and actions.

The following image from Farcaster docs specify the frame interactions

Building an App Store

Creating Your Own App Store with Ease

Leveraging the Meroku Protocol’s Dapp Store Kit APIs, building an App Store is straightforward and efficient.

Functionality of Our App Store

Our App Store will feature a dynamic listing of Farcaster apps, displayed in a random sequence. This simplicity is intentional, focusing on the core functionality to ensure a seamless user experience. While the Meroku’s Dapp Store Kit APIs offer a broad spectrum of additional features—such as app screenshots, user ratings, rankings, and categorization—we’ll concentrate on the essentials for this guide. The exploration of these advanced functionalities is left as an exercise for the reader, allowing for creative expansion beyond the basics.

Implementation Steps

  1. Acquire a Meroku Developer API Key: Begin by completing this form to obtain your developer API key.
  2. Integrate with the Dapp Registry Search Endpoint: Utilize the /api/v1/dapp/search endpoint, specifying storekey as farcaster. This request fetches a list of apps available on Farcaster, which your App Store will use to populate its listings.
  3. Display Random Apps: Ensure that each time the frame is loaded, a random Farcaster app is showcased to the user. This adds an element of discovery and variety to the user experience.
  4. Enhance with User Interaction: Respond to user actions by presenting a different app or enabling users to share their thoughts on the app they’ve explored. This interactive element enriches the engagement with your App Store.

Accessing the Farcaster Apps Registry

The steps outlined provide a solid foundation for creating a minimalist yet functional App Store. By focusing on these key aspects, you can launch an App Store that not only showcases the diverse applications within Farcaster but also encourages exploration and user interaction.

Getting Farcaster Apps Registry

So we’ll have the building blocks for these actions written down in Python. This is easy, just a bunch of API calls.

import http.client
import json
import os
def get_apps():
  conn = http.client.HTTPSConnection("api.meroku.store")
  headers = {
      'Accept': "application/json",
      'apikey': os.getenv("MEROKU_API_KEY")
  }
  conn.request("GET", "/api/v1/dapp/search?storeKey=farcaster", headers=headers)
  res = conn.getresponse()
  if res.status != 200:
    return []
  data = res.read()
  data = json.loads(data)["data"]
  return data

Creating an image card for the app

Since Farcaster Frames only show images, you need to be able to take the app data and convert it into an image. This is the part where frameworks like Satori etc have an edge. I couldn’t find something highly recommended and had to build some custom functions for this part. If you’re aware of something better, do let me know.

Steps for this are:

  1. Choosing a background image
  2. Deciding which contents & images should be placed on the background image
  3. Deciding the positions, font size etc of the placements.

The majority of code for this lies at src/lib/image.py. The class ImageComponent can be used to define the elements on the image and their positioning. Using ImageComponent and generate_app_image you can generate any kind of image you want to.

from src.lib.image import ImageComponent, generate_app_image
def gen_image_for_dapp(app):
  image_stack = []
  
  app_logo = ImageComponent(
    ImageComponent.EXTERNAL_IMAGE,
    position=(100, 100),
    external_img_url=_app['images']['logo'],
    display_type=ImageComponent.DISPLAY_TYPE_CIRCLE,
    circle_radius=60
  )
  image_stack.append(app_logo)
  
  app_name = ImageComponent(
    ImageComponent.TEXT,
    position=(120, 0),
    text=_app['name'],
    font_size=40,
    font_color=(128, 128, 128)
  )
  image_stack.append(app_name)
  
  description = ImageComponent(
    ImageComponent.TEXT,
    position=(0, 200),
    text=_app['description'],
    font_size=30,
    font_color=(0, 0, 0)
  )
  image_stack.append(description)
  
  img = generate_app_image(image_stack)
  
  return img

The Frames Backend (aka Flask app)

We will have the structure of a standard flask app as follows.

Now we can build the index frame (zero state) with a simple code as follows

app.py

app = Flask(__name__, template_folder='src/templates')
@app.route('/')
def index():
  apps = get_apps()
  app_img_url = url_for('frame_image', app_id=apps[0]['dappId'])
  image_url = f"https://{ app_url }{ app_img_url }"
  post_url = f"https://{ app_url }{ url_for('action', app_id=apps[0]['dappId']) }"
  return render_template('index.html', image_url=image_url, post_url=post_url)
@app.route('/frame/image/<app_id>')
def frame_image(app_id):
  apps = get_apps()
  apps = filter(lambda x: x['dappId'] == app_id, apps)
  _app = next(apps, None)
  
  img = generate_image_for_dapp(_app)
  img.seek(0)
  return send_file(img, mimetype='image/png')

And your templates/index.html file looking as below

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta property="og:title" content="FarCaster AppStore"/>
    <meta property="og:image" content="{{ image_url }}"/>
    <meta property="fc:frame" content="vNext"/>
    <meta property="fc:frame:image" content="{{ image_url }}"/>
    <meta property="fc:frame:post_url" content="{{ post_url }}"/>
    <meta property="fc:frame:button:1" content="Find another App"/>
    <meta property="fc:frame:button:2" content="Visit App"/>
    <meta property="fc:frame:button:2:action" content="post_redirect" />
    <meta property="fc:frame:button:3" content="Re-Cast"/>
    <meta property="fc:frame:button:3:action" content="post_redirect" />
    <title>FarCaster AppStore</title>
 </head>
  <body>
<p>Hello Farcaster AppStore</p>
  </body>
</html>

Frames are rendered on the basis of their FC tags. To know more about the specs of FC tags, visit this Frames specification

Here we have first button to pick another app, second button to visit the app (external redirect) and third button to cast this app (external redirect).

Both these external redirects will lead to Frames UI sending a POST request to the URL specified in fc:frame:post_url meta property.

So now, we will write another POST handler that takes these fields and then acts accordingly.

But first, a word about casting

When we want to have a user cast from the frame, we will need to redirect them to a Composer Cast Intent URL. A notion doc is available at https://warpcast.notion.site/Cast-composer-intents-73f2c44884c6474ab53031abfb5f1be3 that describes this in depth.

A cast Intent URL looks like

https://warpcast.com/~/compose?text=Hello%20world!. When someone visits this URL warpcast opens the compose modal.

This experience works great in desktop. However in mobile, a direct hit to this URL will lead to the warpcast download action page and then it redirects to app store or play store. Thus removing the desired effect of cast intentions. Hence it’s necessary to have an app redirect URL that redirects to warpcast.

POST Action Controller -> Redirects to “Redirector” Page -> Redirects to Warpcast Intent URL

The app.py code for this would be as below. It’s important to have the FULL URL in the redirect call. This is because Frames spec specify the Redirect header to be the full redirect URL.

@app.route('/redirect/<app_id>')
def redirect_url(app_id: str):
  apps = get_apps()
  apps = filter(lambda x: x['dappId'] == app_id, apps)
  _app = next(apps, None)
  link_url = f"https://explorer.meroku.org/dapp?id={ app_id }"
  cast_text = f"Check out {_app['name']} on Meroku!"
  cast_text = quote(cast_text)
  
  cast_intent_url = f"https://warpcast.com/~/compose?text={cast_text}&embeds[]={link_url}"
  
  return redirect(cast_intent_url, 302)

Now we are ready to have our POST action controller written down. It’s also simple and goes below

@app.route('/action/<app_id>', methods=['POST'])
def action(app_id: str):
  data = request.get_json(silent=True)
  untrusted_data = data['untrustedData']
  buttonIndex = untrusted_data['buttonIndex']
  
  if buttonIndex == 1:
    apps = get_apps()
    idx = random.randint(0, len(apps) - 1)
    app_img_url = url_for('frame_image', app_id=apps[idx]['dappId'])
    image_url = f"https://{ app_url }{ app_img_url }"
    post_url = f"https://{ app_url }{ url_for('action', app_id=apps[idx]['dappId']) }"
    return render_template('index.html', image_url=image_url, post_url=post_url)
  
  elif buttonIndex == 2:
    user_id = f"fc_user:{ untrusted_data['fid'] }"
    redirect_url = f"https://api.meroku.store/api/v1/o/view/{ app_id }?userId={ user_id }"
    return redirect(redirect_url, 302)
  
  elif buttonIndex == 3:
    redirect_url = f"https://{ app_url }{ url_for('redirect_url', app_id=app_id) }"
    return redirect(redirect_url, 302)

A few important things to note in above code

  1. Since the same action controller will be called for each button in the frame, you must handle the behavior by switching on button index.
  2. Each redirect should be a complete URL. Without this, Frames won’t redirect your users properly.

That’s it. Congratulations. Now you know everything that you have to, in order to build a Farcaster Frame in Python.

Advanced Techniques

  1. Caching: Since frames needs a response in 5s, you should cache some data that you can. Specially the images. In this codebase you will find usage of redis to cache the images and other data points related to users. Depending on your frames, they can help you a lot in reducing image generation times.
  2. Parallel API Calls: As much as possible go for parallel API calls. This is also to reduce the time it takes for your server to respond.
  3. Prefer faster LLM over latest: This is again due to the 5s hard timeoff for frames to respond. Choose the fastest model that does your job.
  4. Working with images can be tricky. Use the ImageComponent and the utility functions to make images.

If you found this post helpful, cast about it. Click here to cast.

Code & Usage

The source code for this repo exists at https://github.com/merokudao/farcaster-appstore-frame

The repo can be used as a boilerplate code for your own projects. You can fork it and then repurpose it for your use case. If you need any help, reach out to me on Twitter or Farcaster.

Leave a Reply