# 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.](https://docs.farcaster.xyz/reference/frames/spec)

## Complete Frame Guide
### WTF is a Frame?
<iframe width="560" height="315" src="https://www.youtube.com/embed/rp9X8rAPzPM?si=sqJyKybRyKY4yrHe" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
### Frame Speedrun
If you are already super comfortable with full-stack applications; check out the speedrun video:
<iframe width="560" height="315" src="https://www.youtube.com/embed/JAIr8kyBxxU?si=yYQ3jnOgFyPFR8G3" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
### 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.
```htmlembedded
<!-- 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:
<iframe width="560" height="315" src="https://www.youtube.com/embed/L6qT5usMWsc?si=LqqI4w_IZXKRsvoj" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
---
### post
[What is a post?](https://hackmd.io/@KyleStargarden/HJ5wbuN1C#What-is-an-HTTP-post)
The `fc:frame:post_url` meta tag describes the content URL.
```html!
<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?](https://hackmd.io/@KyleStargarden/HJ5wbuN1C#What-is-a-post-redirect)
```html
<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://.
---
### link
```html
<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?](https://hackmd.io/@KyleStargarden/HJ5wbuN1C#What-is-CAIP-10)
```html
<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.](https://basescan.org/address/0xf5a3b6dee033ae5025e4332695931cadeb7f4d2b#code)
---
### tx
[What is eth_send huh?](https://hackmd.io/@KyleStargarden/HJ5wbuN1C#What-is-eth_sendTransaction)
```html
<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:
```jsonld
{
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
```typescript
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.
```typescript
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!?](https://hackmd.io/@KyleStargarden/HJ5wbuN1C)
### Data Structures
#### Frame Signature
[What is a protobuf?](https://hackmd.io/@KyleStargarden/HJ5wbuN1C#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.](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#2-message-specifications) Like all FC messages, it must be signed with an active Ed25519 account key (aka signer) that belongs to the user.
```protobuf
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.
```json
{
"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.](https://docs.farcaster.xyz/reference/hubble/httpapi/message#validatemessage) 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](https://onchainkit.xyz/) for building on-chain capable Frames:
<iframe width="560" height="315" src="https://www.youtube.com/embed/YwlfgIJK0j8?si=97oDqYQfcl0vWDlx&start=220" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
---
## Workflow Notes
[These are specific notes for the tutorial which can be found here.](https://docs.base.org/building-with-base/guides/nft-minting-frame/)
- 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
- [Everyone is using Frog Frame Framework from Paradigm](https://www.paradigm.xyz/2024/02/frames)
- [Raid Guild Deux](https://hackmd.io/ZIOAnYnhT2Sc6Dj6Kr-EHg?both)
- [Raid Guild Hacking Frames](https://hackmd.io/G3msHBgKR6yAXmmD8mS1hw?view)
## Resources:
- [The Farcaster Youtube Channel, with all dev calls and a number of helpful developer oriented videos.](https://www.youtube.com/@farcasterxyz/videos)
- [David Furlong's Frame Resources](https://github.com/davidfurlong/awesome-frames)
## Open Source Frames:
- [Token Gating in a Frame](https://github.com/unlock-protocol/token-gated-frame)