Skip to main content

Farcaster Integration

Reward users on Farcaster for casts, reactions, and frame interactions.

Overview

Integrate Loyalteez with Farcaster to reward:

  • ✅ Casts and recasts
  • ✅ Frame interactions
  • ✅ Channel participation
  • ✅ Social engagement
  • ✅ Content creation

Integration Methods

Interactive frames that reward users for engagement.

Method 2: Bot/Webhook

Automated bot that tracks casts and rewards users.

Quick Start: Farcaster Frame

Step 1: Setup Next.js Project (5 min)

npx create-next-app@latest farcaster-rewards
cd farcaster-rewards
npm install frames.js axios

Step 2: Create Reward Frame (10 min)

Create app/api/frame/route.ts:

import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';

const LOYALTEEZ_API = 'https://api.loyalteez.app/loyalteez-api/manual-event';

export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { untrustedData } = body;

const { fid, buttonIndex, castId } = untrustedData;

// Map FID to email
const email = `farcaster_${fid}@loyalteez.app`;

// Track the interaction
const response = await axios.post(LOYALTEEZ_API, {
event: 'farcaster_frame_interaction',
email,
metadata: {
platform: 'farcaster',
fid: fid,
button_index: buttonIndex,
cast_id: castId,
timestamp: Date.now()
}
});

const ltzEarned = response.data.ltzDistributed || 0;
const walletAddress = response.data.walletAddress;

// Return success frame
return new NextResponse(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${process.env.BASE_URL}/success.png" />
<meta property="fc:frame:post_url" content="${process.env.BASE_URL}/api/frame" />
<meta property="fc:frame:button:1" content="Earn More LTZ" />
<meta property="fc:frame:button:2" content="View Wallet" />
<meta property="fc:frame:button:2:action" content="link" />
<meta property="fc:frame:button:2:target" content="https://marketplace.loyalteez.xyz" />
<title>You earned ${ltzEarned} LTZ! 🎉</title>
</head>
<body>
<h1>You earned ${ltzEarned} LTZ!</h1>
<p>Wallet: ${walletAddress}</p>
</body>
</html>`,
{
headers: {
'Content-Type': 'text/html',
},
}
);
} catch (error) {
console.error('Frame error:', error);

return new NextResponse(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${process.env.BASE_URL}/error.png" />
<meta property="fc:frame:post_url" content="${process.env.BASE_URL}/api/frame" />
<meta property="fc:frame:button:1" content="Try Again" />
<title>Error</title>
</head>
<body>
<h1>Something went wrong</h1>
</body>
</html>`,
{
headers: {
'Content-Type': 'text/html',
},
}
);
}
}

export async function GET() {
// Initial frame
return new NextResponse(
`<!DOCTYPE html>
<html>
<head>
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:image" content="${process.env.BASE_URL}/initial.png" />
<meta property="fc:frame:post_url" content="${process.env.BASE_URL}/api/frame" />
<meta property="fc:frame:button:1" content="Earn 50 LTZ" />
<title>Earn LTZ on Farcaster</title>
</head>
<body>
<h1>Earn LTZ Rewards!</h1>
<p>Click to earn your first 50 LTZ</p>
</body>
</html>`,
{
headers: {
'Content-Type': 'text/html',
},
}
);
}

Step 3: Create .env.local

BASE_URL=https://your-domain.vercel.app
BRAND_ID=your_brand_id_here

Step 4: Deploy to Vercel (5 min)

vercel

Step 5: Share Frame on Farcaster

Cast the frame URL on Farcaster:

Check out this new frame!
https://your-domain.vercel.app/api/frame

Users can now earn LTZ by interacting with your frame! 🎉

Advanced Frame Examples

Daily Check-In Frame

// Track daily check-ins
export async function POST(req: NextRequest) {
const { untrustedData } = await req.json();
const { fid } = untrustedData;

const email = `farcaster_${fid}@loyalteez.app`;
const today = new Date().toISOString().split('T')[0];

await axios.post(LOYALTEEZ_API, {
event: 'daily_checkin',
email,
metadata: {
platform: 'farcaster',
fid,
date: today
}
});

// Return streak info
return new NextResponse(/* frame HTML */);
}

Poll Frame with Rewards

// Reward users for voting
export async function POST(req: NextRequest) {
const { untrustedData } = await req.json();
const { fid, buttonIndex } = untrustedData;

const email = `farcaster_${fid}@loyalteez.app`;

await axios.post(LOYALTEEZ_API, {
event: 'poll_vote',
email,
metadata: {
platform: 'farcaster',
fid,
vote_option: buttonIndex,
poll_id: 'poll_123'
}
});

return new NextResponse(/* results frame HTML */);
}

Quiz Frame with Rewards

// Reward correct answers
export async function POST(req: NextRequest) {
const { untrustedData } = await req.json();
const { fid, buttonIndex } = untrustedData;

const email = `farcaster_${fid}@loyalteez.app`;
const correctAnswer = 2;

if (buttonIndex === correctAnswer) {
await axios.post(LOYALTEEZ_API, {
event: 'quiz_correct',
email,
metadata: {
platform: 'farcaster',
fid,
quiz_id: 'quiz_123'
}
});

return new NextResponse(/* success frame with "You earned 100 LTZ!" */);
} else {
return new NextResponse(/* try again frame */);
}
}

Method 2: Farcaster Bot

Track all casts and reward quality content:

Step 1: Setup Bot Project

mkdir farcaster-bot
cd farcaster-bot
npm init -y
npm install @neynar/nodejs-sdk axios dotenv

Step 2: Create Bot Script

Create bot.js:

const { NeynarAPIClient } = require('@neynar/nodejs-sdk');
const axios = require('axios');
require('dotenv').config();

const neynar = new NeynarAPIClient(process.env.NEYNAR_API_KEY);
const LOYALTEEZ_API = 'https://api.loyalteez.app/loyalteez-api/manual-event';

// Track a cast
async function trackCast(cast) {
const { author, hash, text, embeds } = cast;
const email = `farcaster_${author.fid}@loyalteez.app`;

// Determine event type
let event = 'farcaster_cast';
let reward = 10; // base reward

// Reward quality content
if (text.length > 280) {
event = 'long_form_cast';
reward = 25;
}

if (embeds && embeds.length > 0) {
event = 'cast_with_media';
reward = 15;
}

try {
const response = await axios.post(LOYALTEEZ_API, {
event,
email,
metadata: {
platform: 'farcaster',
fid: author.fid,
username: author.username,
cast_hash: hash,
cast_length: text.length,
has_embeds: embeds?.length > 0,
timestamp: Date.now()
}
});

console.log(`✅ Rewarded @${author.username} with ${response.data.ltzDistributed} LTZ`);

// Optional: Reply to cast
// await neynar.publishCast({
// text: `You earned ${response.data.ltzDistributed} LTZ! 🎉`,
// parent: hash
// });
} catch (error) {
console.error('Error tracking cast:', error.message);
}
}

// Monitor channel for new casts
async function monitorChannel(channelId) {
console.log(`Monitoring channel: ${channelId}`);

let lastCheck = Date.now();

setInterval(async () => {
try {
const feed = await neynar.fetchFeedByChannelIds([channelId], {
limit: 10
});

for (const cast of feed.casts) {
const castTime = new Date(cast.timestamp).getTime();

if (castTime > lastCheck) {
await trackCast(cast);
}
}

lastCheck = Date.now();
} catch (error) {
console.error('Error fetching feed:', error.message);
}
}, 60000); // Check every minute
}

// Start monitoring
monitorChannel('loyalteez'); // Replace with your channel

Step 3: Create .env

NEYNAR_API_KEY=your_neynar_api_key
BRAND_ID=your_brand_id_here

Step 4: Run Bot

node bot.js

Webhook Integration

Set up webhooks to track all interactions:

// Express server to receive Farcaster webhooks
const express = require('express');
const axios = require('axios');

const app = express();
app.use(express.json());

app.post('/webhook/farcaster', async (req, res) => {
const { type, data } = req.body;

switch (type) {
case 'cast.created':
await trackCast(data);
break;
case 'reaction.created':
await trackReaction(data);
break;
case 'follow.created':
await trackFollow(data);
break;
}

res.status(200).json({ success: true });
});

async function trackCast(cast) {
const email = `farcaster_${cast.author.fid}@loyalteez.app`;

await axios.post('https://api.loyalteez.app/loyalteez-api/manual-event', {
event: 'farcaster_cast',
email,
metadata: {
platform: 'farcaster',
fid: cast.author.fid,
cast_hash: cast.hash
}
});
}

async function trackReaction(reaction) {
const email = `farcaster_${reaction.user.fid}@loyalteez.app`;

await axios.post('https://api.loyalteez.app/loyalteez-api/manual-event', {
event: 'farcaster_reaction',
email,
metadata: {
platform: 'farcaster',
fid: reaction.user.fid,
reaction_type: reaction.type
}
});
}

app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});

Reward Strategies

1. Quality Content

  • Long casts (>280 chars): 25 LTZ
  • Casts with media: 15 LTZ
  • Casts in your channel: 20 LTZ

2. Engagement

  • Likes/recasts: 5 LTZ
  • Comments: 10 LTZ
  • Shares: 15 LTZ

3. Community Building

  • Following your channel: 50 LTZ
  • Daily active: 10 LTZ
  • Referrals: 100 LTZ

User Identification

// Map Farcaster ID to email
const email = `farcaster_${fid}@loyalteez.app`;

// Get user's connected address (optional)
const userAddress = cast.author.verified_addresses[0];

Configuration

Partner Portal Setup

Configure rewards in Partner Portal → Settings → LTZ Distribution:

EventLTZ Amount
farcaster_cast10
long_form_cast25
cast_with_media15
farcaster_reaction5
daily_active10
channel_follow50

Testing

1. Test Frame Locally

npm run dev
# Open http://localhost:3000/api/frame

2. Validate Frame

Use Warpcast Frame Validator

3. Test in Production

  1. Deploy to Vercel
  2. Cast the frame URL
  3. Interact with frame
  4. Check Partner Portal analytics

Deployment

Vercel (Frames)

vercel --prod

Railway (Bot)

railway init
railway up

Advanced Features

Frame with State

// Multi-step frame
export async function POST(req: NextRequest) {
const { untrustedData } = await req.json();
const { fid, buttonIndex, state } = untrustedData;

const currentStep = JSON.parse(state || '{}').step || 0;

if (currentStep === 0 && buttonIndex === 1) {
// User clicked "Start"
return new NextResponse(/* Step 2 frame with state */);
} else if (currentStep === 1 && buttonIndex === 1) {
// User completed action - reward them
await trackEvent(fid, 'completed_journey');
return new NextResponse(/* Success frame */);
}
}

Gamified Rewards

// Track user progress
const userProgress = new Map();

export async function POST(req: NextRequest) {
const { fid } = (await req.json()).untrustedData;

const progress = userProgress.get(fid) || { level: 1, xp: 0 };
progress.xp += 10;

if (progress.xp >= 100) {
progress.level++;
progress.xp = 0;

// Reward level up
await trackEvent(fid, 'level_up', { level: progress.level });
}

userProgress.set(fid, progress);
}

Best Practices

  • ✅ Validate frame interactions
  • ✅ Implement rate limiting
  • ✅ Cache user data
  • ✅ Handle errors gracefully
  • ✅ Test frames before casting
  • ✅ Monitor analytics

Common Issues

Issue: Frame not rendering

Solution: Validate meta tags with Warpcast validator

Issue: Duplicate rewards

Solution: Implement deduplication logic

Issue: FID mapping

Solution: Use consistent email format

Resources

Support

Your Farcaster community now earns real rewards! 🎉