How to Create an On-Chain Farcaster Frame

Turn your cast into a decentralized interactive app

Jennifer Tran
The Internet Computer Review
7 min readFeb 7, 2024

--

Farcaster is a protocol for building sufficiently decentralized social networks.

Farcaster recently introduced the concept of “frames” which enables you to turn any post or “cast” into an interactive app.

This tutorial provides an overview of creating and hosting a Farcaster frame completely on-chain. It is also an example of how to create a decentralized server.

By hosting your frame on-chain, you unlock the following use cases:

  • Frames can be governed by decentralized autonomous organizations (DAOs).
  • Frame creators achieve complete ownership, free from the constraints of centralized servers.

Tutorial

The tutorial creates a REST API using the ExpressJS framework and deploys it on the Internet Computer protocol (ICP).

Step #1: Download DFX

dfx is the command-line interface for the IC SDK, the SDK built by DFINITY to manage ICP canister smart contracts.

Run the following command to download DFX:

sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"

Step #2: Download Azle

Azle is a library to build ICP canister smart contracts in Typescript and Javascript.

More specifically, it is a canister development kit (CDK) or an adapter used by the IC SDK that provides Typescript and Javascript with all the features necessary to create and manage canister smart contracts.

Run the following command to download Azle:

npx install -g azle

Please note that you may need to download additional dependencies depending on your device. Please refer to the following resource for all dependency requirements: https://demergent-labs.github.io/azle/get_started.html.

For example, please run these commands if using a Mac:

xcode-select - install
brew install llvm
brew link llvm - force // requires llvm to locate at "/opt/homebrew/opt/llvm/bin:$PATH"
echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc

Step #3: Create a new Azle project

Run the following commands to create a new project using Azle:

npx azle new farcaster-frame-azle
cd farcaster-frame-azle

Step #4: Create your frame

A Frame is created with Farcaster-specific OpenGraph tags as referenced in this document.

Here is an example of a frame with an image, four buttons, and a link to a URL:

<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name='next-size-adjust' />
<meta property='fc:frame' content='vNext' />
<meta property='fc:frame:image' content='${imageUrl}' />
<meta property='fc:frame:button:1' content='${button1Text}' />
<meta property='fc:frame:button:2' content='${button2Text}' />
<meta property='fc:frame:button:3' content='${button3Text}' />
<meta property='fc:frame:button:4' content='${button4Text}' />
<meta property='fc:frame:post_url' content='${apiUrl}' />

Save this in a text file for use later in the tutorial.

Step #5: Set up your Express server in src/index.ts

Azle provides a Server method that lets you pass in a callback function that must return a node httpServer instance.

import { Server } from 'azle';

export default Server(() => {

})

Utilize ExpressJS to return a node httpServer instance.

This is an example of an httpServer built using ExpressJS.

const app = express();

app.use(express.json());

app.post('/', (req, res) => {

});

return app.listen();

Combining both results in something like this:

import { Server } from 'azle';

export default Server(() => {
const app = express();

app.use(express.json());

app.get('/', (req, res) => {

});

return app.listen();
})

Step #6: Return the Farcaster frame of the REST API call

Return the Farcaster frame of the REST API call.

The frame in Step 4 had an image, four buttons, and a URL.

This example passes in the content of the image link, button text, and URL link, passes into the frame, and sends the entire constructed frame as the API response:

app.post('/', (req, res) => {
const body = req.body as FarcasterMessage;
const buttonChosen = body.untrustedData.buttonIndex;
let buttonTexts = [
'Button 1',
'Button 2',
'Button 3',
'Button 4'
];
buttonTexts[buttonChosen - 1] = '**' + buttonTexts[buttonChosen - 1] + '**';
res.send(pageFromTemplate(
'https://raw.githubusercontent.com/demergent-labs/azle/main/logo/logo.svg',
buttonTexts[0],
buttonTexts[1],
buttonTexts[2],
buttonTexts[3],
'https://pd7ra-qiaaa-aaaan-qltsq-cai.raw.icp0.io/api',
mainPageBody
))
});

const mainPageBody = `
<div>
<p>
This is a Farcaster frame using Azle (<a
href='https://github.com/demergent-labs/azle'>https://github.com/demergent-labs/azle</a>) hosted on the
<a href='https://internetcomputer.org'>Internet Computer</a>.
</p>
<p>
<a href='https://github.com/Gekctek/farcaster-frame-azle'>
Github
(https://github.com/Gekctek/farcaster-frame-azle)
</a>
</p>
</div>
`;


let pageFromTemplate = (
imageUrl: string,
button1Text: string,
button2Text: string,
button3Text: string,
button4Text: string,
apiUrl: string,
body: string
) => `
<!DOCTYPE html>
<html lang='en'>

<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name='next-size-adjust' />
<meta property='fc:frame' content='vNext' />
<meta property='fc:frame:image' content='${imageUrl}' />
<meta property='fc:frame:button:1' content='${button1Text}' />
<meta property='fc:frame:button:2' content='${button2Text}' />
<meta property='fc:frame:button:3' content='${button3Text}' />
<meta property='fc:frame:button:4' content='${button4Text}' />
<meta property='fc:frame:post_url' content='${apiUrl}' />
<meta property='og:title' content='Azle farcaster frame' />
<meta property='og:image' content='${imageUrl}' />
<title>Azle farcaster frame</title>
</head>

<body>
${body}
</body>

</html>
`;

Step #7 Update the src/index.ts with the entire code from Steps 5 and 6

Update the src/index.ts with this code:

import { Server } from 'azle';
import express from 'express';

interface FarcasterMessage {
untrustedData: {
fid: number;
url: string;
messageHash: string;
timestamp: number;
network: number;
buttonIndex: number;
castId: {
fid: number;
hash: string;
};
};
trustedData: {
messageBytes: string;
};
}

export default Server(() => {
const app = express();

app.use(express.json());

app.get('/', (req, res) => {
res.send(pageFromTemplate(
'https://raw.githubusercontent.com/demergent-labs/azle/main/logo/logo.svg',
'Button 1',
'Button 2',
'Button 3',
'Button 4',
'https://pd7ra-qiaaa-aaaan-qltsq-cai.raw.icp0.io/api',
mainPageBody
))
});


return app.listen();
});

const mainPageBody = `
<div>
<p>
This is a Farcaster frame using Azle (<a
href='https://github.com/demergent-labs/azle'>https://github.com/demergent-labs/azle</a>) hosted on the
<a href='https://internetcomputer.org'>Internet Computer</a>.
</p>
<p>
<a href='https://github.com/Gekctek/farcaster-frame-azle'>
Github
(https://github.com/Gekctek/farcaster-frame-azle)
</a>
</p>
</div>
`;


let pageFromTemplate = (
imageUrl: string,
button1Text: string,
button2Text: string,
button3Text: string,
button4Text: string,
apiUrl: string,
body: string
) => `
<!DOCTYPE html>
<html lang='en'>

<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name='next-size-adjust' />
<meta property='fc:frame' content='vNext' />
<meta property='fc:frame:image' content='${imageUrl}' />
<meta property='fc:frame:button:1' content='${button1Text}' />
<meta property='fc:frame:button:2' content='${button2Text}' />
<meta property='fc:frame:button:3' content='${button3Text}' />
<meta property='fc:frame:button:4' content='${button4Text}' />
<meta property='fc:frame:post_url' content='${apiUrl}' />
<meta property='og:title' content='Azle farcaster frame' />
<meta property='og:image' content='${imageUrl}' />
<title>Azle farcaster frame</title>
</head>

<body>
${body}
</body>

</html>
`;

Step #8: Deploy the application

Local

Deploy it locally to test the application.

Run the following command to deploy locally:

dfx deploy

After this command runs, a success message in the shell will appear similar to this.

Please note that the canister ID and ID provided in the backend canister Candid interface link during deployment will vary from the example shown here.
If example repo is forked, please delete the canister_ids.json before deploying.

The backend canister Candid interface link listed is a UI to test your backend functions and is provided in this format:

http://127.0.0.1:4943/?canisterId=[canisterId]&id=[id]

The API endpoint is a derivative of this format and should adhere to the following structure:

http://[id].localhost:4943

Therefore, if the deployment provided a backend canister Candid interface link such as:

http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai

Access your API through this base endpoint:

http://bkyz2-fmaaa-aaaaa-qaaaq-cai.localhost:4943

Mainnet (Production)

Deploy on the main net or production to have a functional application. To acquire cycles or gas to deploy on mainnet, check out this guide or cast jennifertran on Farcaster.

Run the following command to deploy the application on the Internet Computer mainnet.

dfx deploy --network ic

After this command runs, a success message in the shell will appear similar to this:

Please note that the canister ID and ID provided in the backend canister Candid interface link during deployment will vary from the example shown here.
If the example repo is forked, please delete the canister_ids.json before deploying.

The backend canister Candid interface link listed is a UI to test your backend functions and is provided in this format:

https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=swua7-kqaaa-aaaao-a3fna-cai

The API endpoint is a derivative of this format and should adhere to the following structure:

http://[id].raw.icp0.io

Therefore, if the deployment provided a backend canister Candid interface link such as:

https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=swua7-kqaaa-aaaao-a3fna-cai

Access your API through this base endpoint:

https://swua7-kqaaa-aaaao-a3fna-cai.raw.icp0.io/

Step #9: Preview the Farcaster Frame using their Frame Validator

You can preview the Farcaster Frame using their Frame Validator tool.

Please note that only mainnet production endpoints are supported; local endpoints cannot be used.

Enter the endpoint that you received as part of Step 8 and then click Load. For instance, if your endpoint was https://swua7-kqaaa-aaaao-a3fna-cai.raw.icp0.io/, the preview should look like this:

Resources

Full example code: https://github.com/Gekctek/farcaster-frame-azle

Example of how to build an ExpressJS server on-chain using Azle: https://github.com/demergent-labs/azle/tree/main/examples/express

Use Typescript to build a canister smart contract on ICP via Azle: https://demergent-labs.github.io/azle/the_azle_book.html

A big shoutout to Gekctek for his contributions to this example and Jordan Last for Azle.

--

--