This system extends the existing miniapp (BaseCord-style card) by:
- Continuously collecting Farcaster activity for registered users (by
fid). - Analyzing that activity into a structured social persona and metrics for self-promotion / simple CV.
- Generating visual cards (like the existing design) driven by this structured data.
The system is backend-first: the frontend miniapp consumes a stable API and “card context” JSON to render cards.
- Automatically ingest and store Farcaster casts and profile stats per user.
- Derive meaningful social metrics (activity, engagement, graph) without LLM.
- Use an LLM to generate:
- Topics and persona labels.
- Short promotional summaries and taglines.
- Provide an API to:
- Trigger card generation.
- Fetch latest persona + metrics + card context.
- Support instant card regeneration without waiting for live sync (data is maintained in background).
- No posting or replying on Farcaster on behalf of users.
- No editing of cards directly inside the image layer (edits are done via persona/card context data).
- No cross-platform aggregation (only Farcaster in this spec).
-
User
A person with a Farcaster identity known byfidand attached to an account in the miniapp. -
Cast
A Farcaster post (text, embeds, interactions). -
Social Metrics
Aggregated numeric data derived from casts and profile (e.g., average casts/week, followers count). -
Social Persona
LLM-derived description of the user’s tone, topics, and self-promotion copy. -
Card Context
The final structured payload used by the design system to render a card.
Represents a known Farcaster user.
id(UUID, PK)fid(bigint, unique, indexed)username(string)display_name(string)avatar_url(string)created_at(timestamp)updated_at(timestamp)last_synced_at_casts(timestamp, nullable)last_synced_at_persona(timestamp, nullable)
Stores individual Farcaster casts.
id(UUID, PK)fid(bigint, FK → users.fid, indexed)cast_hash(string, unique)timestamp(timestamp, indexed)text(text)channel(string, nullable)client(string, nullable)mentions(jsonb: list of mentioned fids or usernames)replies_count(int)recasts_count(int)likes_count(int)raw(jsonb: full provider payload)
Soft limit: only keep the last N casts per user (e.g., 2–3k) if storage becomes a concern.
Snapshot of numeric metrics per user.
id(UUID, PK)fid(bigint, FK → users.fid, indexed)snapshot_at(timestamp, indexed)
Derived metrics (examples):
-
Activity
casts_last_7d(int)casts_last_30d(int)avg_casts_per_week(numeric)first_cast_at(timestamp)last_cast_at(timestamp)
-
Engagement
avg_likes_per_cast(numeric)avg_recasts_per_cast(numeric)avg_replies_per_cast(numeric)top_cast_like_count(int)top_cast_hash(string, nullable)
-
Graph
followers_count(int)following_count(int)follow_ratio(numeric)
LLM-derived interpretation of a user’s social presence.
id(UUID, PK)fid(bigint, FK → users.fid, indexed)generated_at(timestamp, indexed)
Content fields (stored as JSON or columns):
tone(string; e.g.,"technical and helpful")primary_topics(jsonb array of strings)secondary_topics(jsonb array of strings)persona_labels(jsonb array of strings; e.g.,["Developer", "Infra builder"])summary(short paragraph; 1–3 sentences)sample_quotes(jsonb array of short strings)raw_json(jsonb; exact LLM output for debugging)
Multiple rows per user allow persona history over time.
A materialized snapshot of everything needed to render one card.
id(UUID, PK)fid(bigint, FK → users.fid, indexed)persona_id(UUID, FK → social_persona.id)metrics_snapshot_at(timestamp)created_at(timestamp)
Rendered fields:
headline(string; main label on card, e.g.,"Infra & DeFi Builder")subheadline(string; e.g.,"@basename • Developer")top_topics(jsonb array of strings; shown as chips)highlight_stats(jsonb; array like{ label, value })summary_line(string; single sentence if used in UI)style_meta(jsonb; template name, color theme, variant)raw_json(jsonb; full context used to render the card)
Responsible for Step 1 in the original spec.
Responsibilities
- Periodically fetch new casts and profile stats for each registered user.
- Write/append scripts for:
castssocial_metricsusersbasic profile updates.
Inputs
fidlist fromuserstable.- Service credentials for Farcaster data provider (e.g., Neynar).
Process
- For each user:
- Fetch profile (followers/following, username, display_name, avatar).
- Fetch casts since
last_synced_at_casts.
- Upsert new casts into
casts. - Compute metrics and insert a new
social_metricsrow. - Update
users.last_synced_at_casts.
Outputs
- Updated
users,casts,social_metrics.
Scheduling
- Every N minutes/hours (configurable).
- Optional: separate cron for high-activity users.
Responsible for Step 2 in the original spec.
Responsibilities
- Periodically or on demand, compute
social_personafrom recent data. - Use an LLM to infer topics, tone, and summary.
Inputs
fid.- Recent
casts(e.g., last 200–500). - Latest
social_metricssnapshot.
LLM Interface
Input JSON (example):
{
"metrics": {
"avg_casts_per_week": 3.4,
"avg_likes_per_cast": 12.1,
"followers_count": 954,
"following_count": 410
},
"sample_casts": [
{"text": "example cast text 1", "likes": 24, "recasts": 3, "replies": 4},
{"text": "example cast text 2", "likes": 3, "recasts": 0, "replies": 1}
]
}Expected output JSON:
{
"tone": "technical and helpful",
"primary_topics": ["L2 infra", "DeFi", "Farcaster dev tooling"],
"secondary_topics": ["personal updates", "memes"],
"persona_labels": ["Developer", "Infra builder"],
"summary": "Onchain infra-focused developer sharing hands-on tips and updates about DeFi and L2 scaling.",
"sample_quotes": [
"I like turning complex infra into simple tools for builders.",
"Most people underestimate how much UX matters in onchain apps."
]
}Process
- Load metrics + sample casts from DB.
- Call LLM with strict JSON schema.
- Validate output (types, required fields).
- Insert new
social_personarow. - Update
users.last_synced_at_persona.
Triggers
- Time-based (e.g., once per day per active user).
- Or threshold-based (e.g., N new casts since last persona).
Responsible for Step 3 in the original spec.
Responsibilities
- Assemble a
card_contextrecord on demand when user requests a card. - Optionally call LLM for a catchy headline or tagline, but can be deterministic.
Inputs
fid.- Latest
social_personaandsocial_metricsfor that user.
Process
- Load latest persona and metrics.
- Construct:
headline(derived from persona labels/topics).subheadline(display_name + handle + role).top_topics(top 3 primary topics).highlight_stats(e.g., casts/week, avg likes, followers).summary_line(short version of persona summary).style_meta(template name, e.g.,"basecard-v1").
- Insert into
card_context. - Return created
card_contextrow to caller.
API endpoint (example)
POST /users/:fid/cards- Body: optional options (template variant, language).
- Response:
card_contextJSON +card_context.id.
Responsible for Steps 4 and part of 3 in the original spec.
Responsibilities
- Render visual cards like the existing BaseCord-style image, but enriched with social info.
- Support multiple design templates while consuming the same
card_contextstructure.
Inputs
card_contextJSON.
Rendering Options
- Client-side React component (for miniapp) reading
card_context. - Server-side renderer that outputs:
- HTML/CSS (for share pages).
- PNG/OG-image (for sharing on other platforms).
Layout Ideas
- Left: avatar / mascot or user PFP.
- Right top:
headline,display_name, handle. - Right middle: role text; topic chips under it.
- Right bottom: small metrics row:
- “Casts/week”, “Avg likes”, “Followers”.
- Branding: existing BaseCord logo and border style.
Responsible for Step 5 in the original spec.
- Background ingestion and persona workers keep data fresh.
- On each user visit:
- Frontend can call
GET /users/:fid/card-latestwhich:- Returns latest
card_contextif recent enough. - Optionally enqueues a “refresh now” job in the background if stale.
- Returns latest
- Frontend can call
- Users can press “Regenerate card” to force a new
card_contextbased on latest persona/metrics.
Freshness policy is configurable (e.g., consider persona “fresh” if < 24 hours old and < N new casts since generation).
GET /users/:fid/card-latest
- Response 200
{
"card_context": {
"id": "uuid",
"headline": "Infra & DeFi Builder",
"subheadline": "@basename • Developer",
"top_topics": ["Farcaster dev", "DeFi", "L2 scaling"],
"highlight_stats": [
{"label": "Casts/week", "value": "3.4"},
{"label": "Avg likes", "value": "12"},
{"label": "Followers", "value": "954"}
],
"summary_line": "Onchain infra-focused developer sharing DeFi and L2 scaling tips.",
"style_meta": {"template": "basecard-v1"},
"created_at": "2026-01-07T00:00:00Z"
}
}POST /users/:fid/cards
- Body (optional)
{
"template": "basecard-v1",
"language": "en"
}- Response 201
{
"card_context_id": "uuid",
"status": "ready"
}POST /admin/users/:fid/ingest-now– trigger immediate ingestion.POST /admin/users/:fid/persona-now– trigger persona recomputation.
- Keep LLM prompts small by sending compressed sample casts (e.g., top N by engagement plus random sample).
- Strictly define JSON schemas and validate all LLM responses before writing to DB.
- Treat
card_contextas immutable snapshots (good for debugging and letting users “freeze” a version they like). - When you later add other chains (X, GitHub, onchain data), you can extend
social_metricsandsocial_personawithout breaking the card API; card templates just see richer fields.