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
Method 1: Farcaster Frames (Recommended)
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:
| Event | LTZ Amount |
|---|---|
farcaster_cast | 10 |
long_form_cast | 25 |
cast_with_media | 15 |
farcaster_reaction | 5 |
daily_active | 10 |
channel_follow | 50 |
Testing
1. Test Frame Locally
npm run dev
# Open http://localhost:3000/api/frame
2. Validate Frame
3. Test in Production
- Deploy to Vercel
- Cast the frame URL
- Interact with frame
- 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
- Farcaster Frames: frames.js
- Neynar API: docs.neynar.com
- Warpcast: warpcast.com
- Loyalteez API: Event Handler API
Support
- Farcaster Integration: [email protected]
- Frame Help: Warpcast Developer Docs
Your Farcaster community now earns real rewards! 🎉