changed a year ago
Linked with GitHub

On-Chain Basic Base Frames Workshop

Overview

This is a comprehensive educational workshop for aspiring Frame developers.

Note: a tremendous amount of the subsequent writing is ripped directly from the Frame official documentation found here.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Complete Frame Guide

WTF is a Frame?

Frame Speedrun

If you are already super comfortable with full-stack applications; check out the speedrun video:

Frame Repo Structure

Frames have two key distinct code components; the front end app, back end api. Here's an example of a Frame's codebase structure:

Folders

  • app: This folder likely contains the core front-end logic and components of the application. It's where the main user interface and client-side functionality are developed.
  • api: This folder is typically used for back-end services or the API layer of the application. It would contain server-side logic, including request handling, data processing, and interaction with databases or other services.

Files

  • route.ts: This TypeScript file contains routing logic for the Frame. This is frame specific server-side routing (handling API endpoints and requests on the back-end).
  • config.ts: This file holds configuration settings for the application. This could include various environment settings (like API endpoints, database connection details, feature flags).
  • layout.tsx: The .tsx extension indicates this is a TypeScript file with JSX syntax. layout.tsx likely is the Frame's React layout component. It serves as a template or structure for the UI, including common elements like headers, body and footers.
  • page.tsx: The page file will contain the meat of our Frame. This is where buttons and user actions are defined.

Initial Frame

A frame is an HTML web application that lives at a URL (e.g. foo.com/app) on a web server. We'll refer to this web server as the "frame server."

The frame server:

  • Must return a valid frame in the HTML head section.
  • Should return a valid HTML body, in case the user clicks through to the frame in a browser.
  • Should not include dynamic content in the initial frame, since it is cached by Farcaster clients.
  • Should not include an fc:frame:state tag.

Response Frames

When a user clicks a button on a frame, the app makes a POST request to the frame server with a frame signature which proves that the request came from the user. The server must respond with a new frame that is sent back to the user.

When a frame server receives a POST request:

  • It must respond within 5 seconds.
  • It must respond with a 200 OK and another frame, on a post button click to indicate success.
  • It must respond with a 302 OK and a Location header, on a post_redirect button click to indicate success.
  • It may respond with 4XX status, content-type: application/json header, and JSON body containing a message property that is 90 or less characters to indicate an application-level error.
  • Any Location header provided must contain a URL that starts with http:// or https://.

Best Practices

Follow these best practices to work around the limitations of frames:

  • Start your initial frame with a load button if you must show dynamic content.
  • Add timestamps or UUIDs to image urls on subsequent frames to bust caches. Cache busting is a technique web developers employ to ensure that users receive the most recent version of a website or application. It “busts” the browser's cache, forcing it to download new files instead of using outdated ones.
  • Return a frame with a "refresh" button if your response takes > 5 seconds.

Rendering Frames

A frame enters Farcaster when a user creates a cast and embeds the frame URL in it. An app that wants to support frames must:

  • Check all call cast embed URLs for valid frames.
  • If the frame is valid, render the frame when the cast is viewed.
  • If the frame is malformed, fall back to treating it as an OpenGraph embed.
  • Follow the frame security model.

Constructing A Frame

A frame must include required properties and may contain optional properties. Frames can be validated using the Frame Validator tool provided by Warpcast.


Frame Properties

A frame property is a meta tag with a property and a content value. The properties are always prefixed with fc:frame.

<!-- An example declaring a frame and supported version -->

<meta property="fc:frame" content="vNext" />

Required Properties

  • fc:frame A valid frame version string. The string must be a release date (e.g. 2020-01-01) or vNext. Apps must ignore versions they do not understand. Currently, the only valid version is vNext.
  • fc:frame:image An image which should have an aspect ratio of 1.91:1 or 1:1
  • og:image An image which should have an aspect ratio of 1.91:1. Fallback for clients that do not support frames.

Optional Properties

  • fc:frame:post_url A 256-byte string which contains a valid URL to send the Signature Packet to.
  • fc:frame:button:$idx A 256-byte string which is the label of the button at position $idx. A page may contain 0 to 4 buttons. If more than 1 button is present, the idx values must be in sequence starting from 1 (e.g., 1, 2, 3). If a broken sequence is present (e.g., 1, 2, 4), the frame is invalid.`
  • fc:frame:button:$idx:action Must be post, post_redirect, link, mint or tx. Defaults to post if not specified. See Button Actions for details on each action.
  • fc:frame:button:$idx:target A 256-byte string which determines the target of the action.
  • fc:frame:button:$idx:post_url A 256-byte string that defines a button-specific URL to send the Signature Packet to. If set, this overrides fc:frame:post_url.
  • fc:frame:input:text Adding this property enables the text field. The content is a 32-byte label that is shown to the user (e.g., Enter a message).
  • fc:frame:image:aspect_ratio Must be either 1.91:1 or 1:1. Defaults to 1.91:1
  • fc:frame:state A string containing serialized state (e.g. JSON) passed to the frame server. May be up to 4096 bytes. Serialized state just means that an object like a JSON has been serialized into bytes; which is ready to transmit server for reconstruction into the same JSON object.

Button Actions

There are several different kinds of button actions that are used to drive the mechanics of a Frame. Here's a quick look at how Frame Button Actions are handled and authenticated:


post

What is a post?

The fc:frame:post_url meta tag describes the content URL.

<meta name="fc:frame:post_url" content="https://frame.example.com/start" />
<meta name="fc:frame:button:1" content="Start" />

The post action sends an HTTP POST request to the frame or button post_url. This is the default button type.

The frame server receives a Signature Packet in the POST body, which includes information about which button was clicked, text input, and the cast context. The frame server must respond with a 200 OK and another frame.


post_redirect

What is a post redirect?

<meta name="fc:frame:post_url" content="https://frame.example.com/redirect" />
<meta name="fc:frame:button:1" content="Redirect" />
<meta name="fc:frame:button:1:action" content="post_redirect" />

The post_redirect action sends an HTTP POST request to the frame or button post_url. You can use this action to redirect to a URL based on frame state or user input.

The frame server receives a Signature Packet in the POST body. The frame server must respond with a 302 Found and Location header that starts with http:// or https://.


<meta name="fc:frame:button:1" content="Farcaster Docs" />
<meta name="fc:frame:button:1:action" content="link" />
<meta name="fc:frame:button:1:target" content="https://docs.farcaster.xyz" />

The link action redirects the user to an external URL. You can use this action to redirect to a URL without handling a POST request to the frame server.

Clients do not make a request to the frame server for link actions. Instead, they redirect the user to the target URL.


mint

What is a CAIP-10 address?

<meta name="fc:frame:button:1" content="Mint" />
<meta name="fc:frame:button:1:action" content="mint" />
<meta
  name="fc:frame:button:1:target"
  content="eip155:8453:0xf5a3b6dee033ae5025e4332695931cadeb7f4d2b:1"
/>

The mint action allows the user to mint an NFT. Clients that support relaying or initiating onchain transactions may enhance the mint button by relaying a transaction or interacting with the user's walletl. Clients that do not fall back to linking to an external URL.

The target property must be a valid CAIP-10 address, plus an optional token ID. In this case the CAIP-10 address is the address of the NFT contract. Crawling the provided contract address yields an easter egg.


tx

What is eth_send huh?

<meta property="fc:frame:button:1" content="Transaction" />
<meta property="fc:frame:button:1:action" content="tx" />
<meta
  property="fc:frame:button:1:target"
  content="https://frame.example.com/get_tx_data"
/>
<meta
  property="fc:frame:button:1:post_url"
  content="https://frame.example.com/tx_callback"
/>

The tx action allows a frame to send a transaction request to the user's connected wallet. Unlike other action types, tx actions have several steps.

First, the client makes a POST request to the target URL to fetch data about the transaction. The frame server receives a Signature Packet in the POST body, including the address of the connected wallet. The frame server must respond with a 200 OK and a JSON response describing the transaction:

{
  chainId: "eip155:10",
  method: "eth_sendTransaction",
  params: {
    abi: [...], // JSON ABI of the function selector and any errors
    to: "0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D",
    data: "0x783a112b0000000000000000000000000000000000000000000000000000000000000e250000000000000000000000000000000000000000000000000000000000000001",
    value: "984316556204476",
  },
};

The client forwards this transaction data to the user's wallet. If the user signs the transaction, the client makes a POST request to the post_url with a Signature Packet that includes the transaction hash. The frame server must respond with a 200 OK and another frame. The frame server must monitor the transaction hash to determine if the transaction succeeds, reverts, or times out.

Transaction Data Response Type

A transaction data response must match the following TransactionTargetResponse type with:

  • chainId: A CAIP-2 chain ID to identify the tx network (e.g. Ethereum mainnet)
  • method: A method ID to identify the type of tx request. (e.g. "eth_sendTransaction")
  • attribution: Optional. Return false to omit the calldata attribution suffix. If this value is undefined or true, clients will append the attribution suffix.
  • params: Specific parameters for chainId and method
type TransactionTargetResponse {
  chainId: string;
  method: "eth_sendTransaction";
  attribution?: boolean;
  params: EthSendTransactionParams;
}

Ethereum Params

If the method is eth_sendTransaction and the chain is an Ethereum EVM chain, the param must be of type EthSendTransactionParams:

  • to: transaction to address
  • abi: JSON ABI which must include encoded function type and should include potential error types. Can be empty.
  • value: value of ether to send with the transaction in wei. Optional.
  • data: transaction calldata. Optional.
type EthSendTransactionParams {
  abi: Abi | [];
  to: Hex;
  value?: string;
  data?: Hex;
}

Images

There are a few rules for serving images in fc:frame:image tags:

  • The size of the image must be < 10 MB.
  • The type of image must be jpg, png or gif.
  • The image source must either be an external resource with content headers or a data URI.
  • Clients may resize larger images or crop those that do not fit in their aspect ratio. SVG images are not because they can contain scripts and extra work must be done by clients to sanitize them.

Frame servers can use cache headers to refresh images and offer more dynamic first frame experiences:

Frame servers can use the max-age directive in the HTTP Cache-Control header to ensure images in the initial frame refresh automatically. A lower max-age ensures images update regularly without user interactions.
App developers should respect cache headers set by the original frame image, and their image proxy implementations should not interfere with durations.


Securing Frames

There are important security concerns that must be addressed by both frame developers and apps that implement frames.

  • Sanitize all input received from the user via text inputs.
  • Verify the signature of a frame signature packet.
  • Validate the origin URL in the frame signature packet.
  • Load transaction calldata only from trusted origins.

WTF does this mean!?

Data Structures

Frame Signature

What is a protobuf?

A Frame signature proves that a user clicked a frame button. It is created by the Farcaster app, signed by the user's account and sent to the Frame server.

When a frame button is clicked, the Farcaster app must generate a FrameAction protobuf. A FrameAction is a new type of Farcaster message. Like all FC messages, it must be signed with an active Ed25519 account key (aka signer) that belongs to the user.

message FrameActionBody {
  bytes frame_url = 1;      // The URL of the frame app
  bytes button_index = 2;   // The index of the button that was clicked
  CastId cast_id = 3;       // The cast which contained the frame URĽ
  bytes input_text = 4;     // The text from the user input (if any)
  bytes state = 5;          // Serialized frame state value
  bytes transaction_id = 6; // Transaction ID
  bytes address = 7;        // User's connected address
}

// MessageType and MessageData are extended to support the FrameAction

enum MessageType {
  .....
  MESSAGE_TYPE_FRAME_ACTION = 13;
}

message MessageData {
  oneof body {
		...
		FrameActionBody frame_action_body = 16
  }
}

A FrameActionBody in a message m is valid only if it passes these validations:

  • m.signature_scheme must be SIGNATURE_SCHEME_ED25519.
  • m.data.type must be MESSAGE_TYPE_FRAME_ACTION
  • m.data.body.type must be a valid UserDataType
  • m.data.body.url must be <= 256 bytes
  • m.data.body.button_index index must be ≥1 and ≤4.
  • m.data.body.input_text must be <= 256 bytes
  • m.data.body.state must be <= 4096 bytes
  • m.data.body.transaction_id must be <= 256 bytes
  • m.data.body.address must be <= 64 bytes

Frame Signature Packet

A signature packet is a JSON object sent to the Frame server when a button is clicked. It contains two objects:

  • Signed Message — an authenticated protobuf that represents the user action. This message must be unpacked by a farcaster hub to read the data inside.

  • Unsigned Message — an unathenticated JSON object that represents the user action. can be read directly.

Unsigned messages can be spoofed and should usually be ignored. It is only safe to use them if you are performing an unauthenticated request.

If you are unsure, always read the signed message by sending it into the validateMessage endpoint on hubs and only trust the data it returns.

{
  "untrustedData": {
    "fid": 2,
    "url": "https://fcpolls.com/polls/1",
    "messageHash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
    "timestamp": 1706243218,
    "network": 1,
    "buttonIndex": 2,
    "inputText": "hello world", // "" if requested and no input, undefined if input not requested
    "state": "%7B%22counter%22%3A1%7D",
    "transactionId": "0x83afec0f72e32d2409ceb7443dc9e01443d0dec6d38ab454bf20918cf633a455",
    "address": "0xf6ea479f30a71cc8cb28dc28f9a94246e1edc492",
    "castId": {
      "fid": 226,
      "hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
    }
  },
  "trustedData": {
    "messageBytes": "d2b1ddc6c88e865a33cb1a565e0058d757042974..."
  }
}

The Signed Message can be validated by calling the validateMessage API on Hubs. See the Hub HTTP API for reference. The hub (assuming it’s fully in sync) will validate the following:

  • the fid is a valid, registered farcaster fid
  • the signer is currently active and associated with the fid
  • the message hash is correct (contents match the hash)
  • the signature for the message is valid, and came from the signer
  • the FrameActionBody passes the above validations

Onchain Frames

Here's a nice tutorial for utilizing CoinBase's onchainkit for building on-chain capable Frames:


Workflow Notes

These are specific notes for the tutorial which can be found here.

  • Deploy to Vercel directly from Github

  • Add a .env file and .env* to GitIgnore

  • Create .env.local and add:

    • BASESCAN_API_KEY - https://basescan.org/myapikey
    • NFT_CONTRACT_ADDRESS - As described above
    • WALLET_PRIVATE_KEY - the private key of the wallet that owns the above NFT contract and can mint it. This key is the same as your 12-word phrase
  • Images must have a 1.91 to 1 aspect ratio

Raid Guild Frames Resources

Resources:

Open Source Frames:

Select a repo