Build an autonomous, onchain AI agent that posts daily build-streak prompts on Farcaster, replies to community members, and tips builders with USDC on Base.
Built for: Base Builder Quest (5 ETH prize pool) & Base East Africa Workshop
Stack: OpenClaw + Farcaster + Base (USDC) + Node.js + systemd
Agent FID: 2660927 | Username: @basedeabuilds
Wallet: 0xD90D5483660D76D69B3406db3F42c41b7d92dB2d
- What We're Building
- Architecture Overview
- How x402 Works (Important!)
- Prerequisites
- Step 1 — VPS Setup
- Step 2 — Install OpenClaw
- Step 3 — Farcaster Account (Manual Flow)
- Step 4 — Agent Personality (SOUL.md & AGENTS.md)
- Step 5 — Daily Prompt Script
- Step 6 — Reply to Replies (Smart Reply System)
- Step 7 — Tipping with USDC on Base
- Step 8 — Discovery Job (Web Research)
- Step 9 — Systemd Services & Timers
- Step 10 — OpenClaw Gateway & Dashboard
- Step 11 — HEARTBEAT.md & Cron Jobs
- What Worked & What Didn't
- OpenClaw Deep Dive
- Key Concepts (x402, ERC-8004)
- File Structure (on VPS)
- Cost Budget
- Troubleshooting
An autonomous Farcaster agent that:
- Posts daily build-streak prompts — GM/BM/evening casts encouraging builders to share progress
- Replies to community members — smart, multi-cast reply system with conversation threading, web research, and 4 distinct reply styles
- Discovers trending news — Brave Search integration surfaces Base ecosystem news twice daily
- Tips builders with USDC — deterministic winner selection using Base block hash, sends USDC to their Farcaster verified wallet, and replies with the basescan tx link
- Community pulse cron job — gateway-managed wakeup that lets the agent freely decide whether to post
- Runs 24/7 on a VPS — systemd timers + gateway cron jobs, no human in the loop
┌──────────────────────────────────────────────────────────┐
│ Contabo VPS (Ubuntu 24.04, Node 22) │
│ │
│ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ OpenClaw Gateway │ │ Scheduled Jobs (systemd) │ │
│ │ :18789 │ │ │ │
│ │ │ │ hourly → daily_prompt.js │ │
│ │ SOUL.md │ │ :00/:15/:30/:45 │ │
│ │ AGENTS.md │ │ → reply_to_...js │ │
│ │ BOOTSTRAP.md │ │ 16:00 → tip_winners.js │ │
│ │ Skills │ │ 6AM/6PM→ discovery_job.js │ │
│ │ HEARTBEAT.md │ └────────────┬──────────────┘ │
│ │ Cron Jobs │ │ │
│ └──────────────────┘ ▼ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ External APIs (all via x402 — paid with USDC) │ │
│ │ • Neynar Hub — post casts, read conversations │ │
│ │ (0.001 USDC per call via x402/EIP-3009) │ │
│ │ │ │
│ │ Free APIs │ │
│ │ • OpenRouter — LLM generation (claude-3.5-haiku) │ │
│ │ • Base RPC — USDC transfers (mainnet.base.org) │ │
│ │ • Brave Search — Base ecosystem news discovery │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
This agent uses x402 for every Farcaster interaction. x402 is an HTTP 402 payment protocol:
- Agent wants to post a cast or read a conversation from Neynar Hub
- Agent creates an EIP-3009
transferWithAuthorizationsignature (USDC on Base) - This signature is base64-encoded into an
X-PAYMENTHTTP header - Neynar receives the request, a facilitator settles the USDC payment (0.001 per call)
- Neynar processes the request
This means every API call costs 0.001 USDC. Reading a conversation = 0.001. Posting a cast = 0.001. The agent wallet needs USDC on Base to operate.
The x402 flow is implemented in src/x402.js in the farcaster-agent repo. The key function is createX402Header(wallet) which signs the EIP-712 typed data for USDC transfer authorization.
| Tool | Version | Notes |
|---|---|---|
| Ubuntu VPS | 24.04 LTS | Contabo Cloud VPS S (~$7/mo) — or Contabo OpenClaw Hosting (pre-installed) |
| Node.js | 22.x | nvm install 22 |
| OpenClaw | 2026.2.x | npm i -g openclaw |
| ETH on Optimism | ~$0.50 | For FID registration (happens on OP Mainnet) |
| USDC on Base | ~$1+ | For x402 API calls + tipping builders |
| OpenRouter API key | free tier | openrouter.ai — for LLM prompt generation |
| Brave Search API key | free tier | brave.com/search/api — for web research (1 req/sec) |
You do NOT need a separate Neynar API key. The farcaster-agent uses x402 (USDC payments) instead of API keys for all Neynar Hub interactions.
SSH into your fresh Ubuntu 24.04 VPS:
ssh root@YOUR_VPS_IPInstall Node.js 22 via nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
source ~/.bashrc
nvm install 22
node -v # should show v22.22.0Enable lingering for systemd user services (critical — lets timers run after SSH disconnect):
loginctl enable-linger $USERnpm i -g openclaw
openclaw --version # 2026.2.xRun the onboarding wizard:
openclaw setupThis creates:
~/.openclaw/openclaw.json— config (model, API keys, ports)~/.openclaw/workspace/— default workspace for SOUL.md, AGENTS.md, skills
The skill provides postCast(), wallet creation, FID registration, and signer management.
Lesson learned:
npx clawhub@latest install farcaster-agentonly downloads theSKILL.mddescriptor, NOT the full source code. You need togit clonethe full repo.
cd ~/.openclaw/workspace
git clone https://github.com/rishavmukherji/farcaster-agent.git
cd farcaster-agent
npm installThis installs key dependencies: ethers, @farcaster/hub-nodejs, and the x402 payment module.
Lesson learned: The
auto-setup.jsscript tries to swap USDC→ETH and bridge Base→Optimism. The bridge reverted for us. The manual flow is more reliable.
cd ~/.openclaw/workspace/farcaster-agent
node src/auto-setup.jsThis generates a custody wallet and saves it to ~/.openclaw/secrets/farcaster-wallet.json. Write down the wallet address — you'll need to fund it.
Your wallet needs two things:
| Chain | Asset | Amount | Purpose |
|---|---|---|---|
| Optimism | ETH | ~0.0003 ETH | FID registration + signer (happens on OP Mainnet) |
| Base | USDC | ~$1+ | x402 API calls (0.001 USDC each) + tipping |
Send funds to the wallet address from Step 3a. You can bridge from mainnet via bridge.base.org or withdraw from an exchange directly to the right chain.
Lesson learned: You do NOT need $4+ of ETH. Registration + signer costs ~0.0002–0.0003 ETH on Optimism.
Run these one by one on the VPS. Stop if any step errors.
cd ~/.openclaw/workspace/farcaster-agent
# Load wallet private key from the saved wallet file
export PRIVATE_KEY=$(node -p "require(process.env.HOME + '/.openclaw/secrets/farcaster-wallet.json').privateKey")Step 1 — Register FID (on Optimism):
node src/register-fid.jsWrite down the FID number from the output (e.g., 2660927).
Step 2 — Add signer (on Optimism):
node src/add-signer.jsWrite down the signer public key and signer private key from the output.
Step 3 — Save credentials:
The manual flow does NOT auto-save. You need to save them:
node src/credentials.js getIf it says "No credentials found", save them manually:
read -s -p "Signer private key: " SIGNER_PK; echo
read -p "FID: " FID
export SIGNER_PK FID
node - <<'NODE'
const fs = require('fs');
const path = require('path');
const signer = process.env.SIGNER_PK;
const fid = Number(process.env.FID);
if (!signer || !fid) { console.error('Missing SIGNER_PK or FID'); process.exit(1); }
const walletPath = path.join(process.env.HOME, '.openclaw', 'secrets', 'farcaster-wallet.json');
const wallet = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
const data = {};
data[String(fid)] = {
fid: String(fid),
custodyPrivateKey: wallet.privateKey,
signerPrivateKey: signer,
fname: null,
createdAt: new Date().toISOString()
};
data._active = String(fid);
const out = path.join(process.env.HOME, '.openclaw', 'farcaster-credentials.json');
fs.writeFileSync(out, JSON.stringify(data, null, 2), { mode: 0o600 });
console.log('Credentials saved to', out);
NODE
unset SIGNER_PK FIDLesson learned: You MUST
exportthe variables —readcreates local shell variables that are invisible to Node.js. Theexport SIGNER_PK FIDline is critical.
Verify credentials saved:
node src/credentials.js list
node src/credentials.js getLesson learned: The credentials file uses a nested format with an
_activepointer.loadCredentials()readsdata[data._active]to find the active account. The file lives at~/.openclaw/farcaster-credentials.json.
cd ~/.openclaw/workspace/farcaster-agent
export PRIVATE_KEY=$(node -p "require(process.env.HOME + '/.openclaw/secrets/farcaster-wallet.json').privateKey")
export SIGNER_PRIVATE_KEY=$(node -p "JSON.parse(require('fs').readFileSync(process.env.HOME + '/.openclaw/farcaster-credentials.json','utf8'))[JSON.parse(require('fs').readFileSync(process.env.HOME + '/.openclaw/farcaster-credentials.json','utf8'))._active].signerPrivateKey")
export FID=$(node -p "JSON.parse(require('fs').readFileSync(process.env.HOME + '/.openclaw/farcaster-credentials.json','utf8'))._active")
node src/post-cast.js "gm from my autonomous agent!"If you see Submitted successfully and Cast verified on network! — you're live on Farcaster.
cd ~/.openclaw/workspace/farcaster-agent
node - <<'NODE'
const { setupFullProfile, loadCredentials } = require('./src');
(async () => {
const creds = loadCredentials();
await setupFullProfile({
privateKey: creds.custodyPrivateKey,
signerPrivateKey: creds.signerPrivateKey,
fid: Number(creds.fid),
fname: 'YOUR_USERNAME',
displayName: 'Your Agent Display Name',
bio: 'Your agent bio goes here.',
pfpUrl: 'https://example.com/your-avatar.png'
});
console.log('Profile updated');
})().catch(err => {
console.error('Profile update failed:', err?.message || err);
process.exit(1);
});
NODEReplace YOUR_USERNAME (lowercase, 1-16 chars, hyphens OK), display name, bio, and PFP URL with your own values.
Note: Farcaster usernames can only be changed once every 28 days. Pick one you're happy with.
OpenClaw injects these files into the agent's system prompt. Place them in the workspace:
~/.openclaw/workspace/SOUL.md — who the agent is:
# Your Agent Name — Soul
I am [name], an autonomous [role] agent on Farcaster.
My mission: [what you do].
## Personality
- [trait 1]
- [trait 2]
- [trait 3]
## Voice
- Clear, direct, optimistic.
- Say "onchain," not "on-chain."~/.openclaw/workspace/AGENTS.md — how the agent operates:
# AGENTS.md
## Agent: main
Role: [your agent's role]
### Rules
- [rule 1]
- [rule 2]
### Safety
- Never ask for private keys or secrets.
- Never claim official endorsement.
- If funds are low, pause tipping.~/.openclaw/workspace/BOOTSTRAP.md — first-run conversation script. OpenClaw uses this on first launch to let the agent discover its identity through conversation.
The heartbeat: scripts/daily_prompt.js — lives inside the farcaster-agent directory.
What it does:
- Checks time-of-day slot (morning → "GM", afternoon → "BM", evening, night)
- Rolls mood/energy/theme using deterministic seeds (
dateKey:hour) - Calls LLM (OpenRouter) to generate a 1-2 sentence prompt (max 240 chars)
- Checks similarity against recent posts (Jaccard distance, threshold 0.7)
- Posts to Farcaster via x402 Hub submission (costs 0.001 USDC)
- Saves state: hash, timestamp, streak count, recent prompts
Key environment (~/.openclaw/secrets/prompt.env):
OPENROUTER_API_KEY=sk-or-v1-...
PROMPT_PROVIDER=openrouter
PROMPT_MODEL=anthropic/claude-3.5-haikuKey behavior:
- Probability-based posting (not every hour) — morning 25%, afternoon 32%, evening 30%
- Guaranteed at least 1 post per day (probability → 100% after
MUST_POST_BYhour) - Cooldown between posts (default 2 hours)
- Max 5 posts per day, target 3
- Quiet hours 22:00–06:00
Test it:
cd ~/.openclaw/workspace/farcaster-agent
node scripts/daily_prompt.js --dry-run # preview without posting
node scripts/daily_prompt.js --force # bypass cooldown/probabilityscripts/reply_to_replies.js — fully autonomous reply system that polls every 15 minutes.
What makes it smart:
- Multi-cast checking — fetches the agent's recent casts directly from Neynar API (no dependency on daily_prompt state). Checks up to 5 casts within the last 72 hours.
- Conversation threading — uses
reply_depth=2to see replies to the agent's own replies, enabling back-and-forth conversations (up to 3 levels deep). - Web research — detects builder replies using keyword classification (needs 2+ indicators like "built"+"app", or contains a URL). Searches Brave for project info and feeds it to the LLM for specific, informed feedback. Capped at 2 searches per run.
- 4 distinct reply styles:
- Casual (gm, short replies): max 140 chars, warm + invite to share
- Builder with research: max 320 chars, specific feedback referencing web findings
- Builder without research: max 200 chars, acknowledge + ask follow-up
- Thread continuation: max 200 chars, includes prior context
- Tiered frequency — latest cast checked every run, 2nd oldest every other run, 3rd+ every 4th run (cost optimization).
Flow:
- Calls Neynar
/v2/farcaster/feed/user/castsvia x402 to get agent's recent casts - For each cast, fetches conversation thread via x402 (
reply_depth=2) - Filters for new replies not yet responded to (tracked in
.state/replies.json) - Classifies each reply (casual vs builder), optionally searches Brave for project context
- Generates contextual reply using LLM (OpenRouter, temperature 0.85)
- Cleans LLM output (strips extra quotes, truncates at word boundary)
- Posts reply as a threaded cast using
parentCastIdin the Hub CastAdd message via x402
Key detail: The farcaster-agent's postCast() does NOT support replies. Our script builds the CastAdd message directly using @farcaster/hub-nodejs with a parentCastId field containing the reply's hash and author FID, then submits via x402.
Configuration (via environment):
MAX_REPLIES_PER_RUN=5— max replies per runMAX_CASTS_TO_CHECK=5— how many recent casts to checkMAX_CAST_AGE_HOURS=72— ignore casts older than thisMAX_SEARCHES_PER_RUN=2— cap on Brave searches per runMAX_THREAD_DEPTH=3— max conversation depth to follow
Cost: ~0.001 USDC per conversation read + 0.001 per reply posted. ~0.11 USDC/day.
Test it:
cd ~/.openclaw/workspace/farcaster-agent
node scripts/reply_to_replies.js --dry-runscripts/tip_winners.js — runs daily at 4 PM EAT.
Full flow:
- Checks if the latest post is old enough (≥2 hours)
- Fetches replies via x402 conversation API
- Gets each replier's Farcaster verified wallet automatically (
author.verified_addresses.primary.eth_address) — no need for users to paste addresses! - Deterministic winner selection:
Fully auditable — anyone can verify with the same inputs.
seed = latestBlockHash + ":" + castHash + ":" + postTimestamp winnerIndex = keccak256(seed) % eligibleCount - Sends 0.02 USDC per winner via
usdc.transfer()on Base - Replies to the winner on Farcaster with a celebratory LLM-generated message + basescan tx link
Example tip reply:
Woohoo @xiaomaov2, your creative spark just lit up Base! Keep pushing the boundaries of what's possible. 🚀
https://basescan.org/tx/0x769dcd52a5298e6abf88d8278e7cfb8fd6d61abeccaeb393bedd61db689a6164
Configuration (via systemd environment):
TIP_AMOUNT_USDC=0.02— tip amount per winnerTIP_MAX_WINNERS=2— number of winners per roundTIP_MIN_AGE_MINUTES=120— minimum age of post before tipping
Test it:
cd ~/.openclaw/workspace/farcaster-agent
node scripts/tip_winners.js --dry-runscripts/discovery_job.js — searches the web for Base ecosystem news and generates "sparks" for the agent to use.
Flow:
- Runs 4 Brave Search queries: "Base chain news", "Base app update", "Farcaster Base creators", "Base East Africa builders"
- Takes top 2 results per query (8 items total)
- Sends items to LLM to generate 3 short "sparks" (under 140 chars each, no URLs/hashtags)
- Saves results to
.state/discovery.jsonandmemory/DISCOVERY.md
Key details:
- Uses Brave Search API (free plan, 1 req/sec)
- 1.5s delay between searches to avoid 429 rate limiting
- API key stored in
~/.openclaw/secrets/discovery.env - Output feeds into HEARTBEAT.md so the gateway agent can reference fresh news
Configuration (~/.openclaw/secrets/discovery.env):
BRAVE_API_KEY=BSA...Schedule: Runs at 6 AM and 6 PM via bea-discovery.timer.
Test it:
cd ~/.openclaw/workspace/farcaster-agent
node scripts/discovery_job.jsWe use systemd user services so everything runs without needing an active SSH session.
bea-heartbeat — hourly posting:
[Service]
Type=oneshot
Environment=QUIET_ENABLED=0
Environment=MIN_POSTS=2
Environment=MAX_POSTS=6
Environment=COOLDOWN_HOURS=1
Environment=MUST_POST_BY=23
ExecStart=/usr/bin/node /root/.openclaw/workspace/farcaster-agent/scripts/daily_prompt.jsTimer: OnCalendar=hourly with 15min random delay.
bea-replies — reply checking every 15 min:
[Service]
Type=oneshot
WorkingDirectory=/root/.openclaw/workspace/farcaster-agent
ExecStart=/usr/bin/node /root/.openclaw/workspace/farcaster-agent/scripts/reply_to_replies.jsTimer: OnCalendar=*:00,15,30,45 with 1min random delay.
bea-tips — daily tipping at 4 PM:
[Service]
Type=oneshot
WorkingDirectory=/root/.openclaw/workspace/farcaster-agent
ExecStart=/usr/bin/node /root/.openclaw/workspace/farcaster-agent/scripts/tip_winners.js
Environment=TIP_MAX_WINNERS=2Timer: OnCalendar=16:00.
bea-discovery — web research at 6 AM and 6 PM:
[Service]
Type=oneshot
WorkingDirectory=/root/.openclaw/workspace/farcaster-agent
ExecStart=/usr/bin/node /root/.openclaw/workspace/farcaster-agent/scripts/discovery_job.jsTimer: OnCalendar=06:00,18:00.
mkdir -p ~/.config/systemd/user
# Create service and timer files (see examples above)
# Then enable:
systemctl --user daemon-reload
systemctl --user enable --now bea-heartbeat.timer
systemctl --user enable --now bea-replies.timer
systemctl --user enable --now bea-tips.timer
systemctl --user enable --now bea-discovery.timer
systemctl --user enable --now openclaw-gateway.servicesystemctl --user list-timers --all
journalctl --user -u bea-heartbeat.service --since today
journalctl --user -u bea-replies.service --since todayThe gateway is a persistent service that:
- Serves a web dashboard at
http://localhost:18789 - Manages agent sessions via WebSocket
- Loads SOUL.md and AGENTS.md into the system prompt
- Loads skills from the workspace
- Routes chat messages to the configured LLM
| Section | What It Does |
|---|---|
| Overview | Agent health, uptime, recent activity |
| Channels | Communication channels (webchat, WhatsApp, Telegram) |
| Instances | Running agent instances |
| Sessions | Active chat sessions with history |
| Cron Jobs | Gateway-managed scheduled wakeups |
| Agents | Agent configurations |
| Skills | Installed skills (like farcaster-agent) |
| Config | Gateway settings (port, auth, model) |
| Debug / Logs | Diagnostics and raw logs |
# From your LOCAL machine (not the VPS):
ssh -N -L 18789:127.0.0.1:18789 root@YOUR_VPS_IP \
-o ServerAliveInterval=30 -o ServerAliveCountMax=3
# Then open http://localhost:18789Key settings:
agents.defaults.model.primary— which LLM to use (we useopenrouter/anthropic/claude-3.5-haiku)gateway.port— default 18789gateway.auth.token— auth token for dashboard access
Lesson learned: The gateway was initially set to
claude-opus-4which is expensive and may have caused empty responses. Switching toclaude-3.5-haikufixed the dashboard chat.
HEARTBEAT.md lives in the workspace root alongside SOUL.md. It tells the agent what to do when it wakes up from a cron job or scheduled event. Without this file, cron jobs fail with empty-heartbeat-file.
# HEARTBEAT
I am Based East Africa Builds, an autonomous community agent on Farcaster.
## On Wake
1. Read my discovery notes (memory/DISCOVERY.md) for recent Base ecosystem news
2. Check my last post time — avoid posting more than once every 2 hours
3. Think about what Base East Africa builders need right now
4. Decide whether to post, share a tip, or stay quiet
## Guidelines
- Only post if I have something worth saying
- Reference real news from discovery notes when possible
- Keep posts under 280 characters
- Max one emoji per postGateway cron jobs are different from systemd timers. Systemd timers run deterministic Node scripts. Cron jobs wake the LLM agent through the gateway pipeline — the agent reads its HEARTBEAT.md, SOUL.md, and memory files, then freely decides what to do.
Create cron jobs in the dashboard under Control → Cron Jobs:
| Field | Value |
|---|---|
| Name | community-pulse |
| Schedule | Every 30 min |
| Session | Main |
| Wake Mode | Next heartbeat |
| Payload | System event |
| System text | "You just woke up for your community check. Look at recent activity, think about what Base East Africa builders need, and decide if you want to post an encouraging message, share a building tip, or stay quiet." |
Known issue: If
HEARTBEAT.mddoesn't exist or is empty, cron jobs fail withempty-heartbeat-file. Make sure the file has content.
| Step | Notes |
|---|---|
| Manual Farcaster setup | register-fid → add-signer → post-cast — reliable |
| systemd user timers | Agent posts autonomously, survives SSH disconnects |
| x402 payments | Agent pays 0.001 USDC per API call, fully automatic |
| Deterministic tipping | keccak256(blockHash + castHash + timestamp) — fair and verifiable |
| Verified wallet lookup | Tips go to Farcaster verified wallet — no address pasting needed |
| Tip reply with tx hash | Agent replies to winner with basescan link — transparent |
| Contextual replies | LLM generates warm, relevant responses to community replies |
| Multi-cast reply checking | Agent fetches its own casts from Neynar — fully autonomous |
| Conversation threading | reply_depth=2 enables back-and-forth conversations |
| Web research for builders | Brave Search finds project info, LLM gives specific feedback |
| 4 reply styles | Casual, builder+research, builder, thread continuation |
| Discovery job | Brave Search surfaces Base ecosystem news twice daily |
| HEARTBEAT.md | Gateway cron jobs wake the LLM to freely decide actions |
| Similarity check | Jaccard on tokenized text prevents repetitive posts |
| Issue | What Happened | Fix |
|---|---|---|
npx clawhub install |
Only downloads SKILL.md, not full code | git clone the full repo |
auto-setup.js bridge |
Base→Optimism bridge reverted | Use manual flow instead |
auto-setup.js funds check |
"No sufficient funds" with $0.50 | Manual flow works fine |
| Shell vars invisible to Node | read without export |
Always export before scripts |
| Credentials not auto-saved | Manual flow skips save step | Write credentials JSON manually |
| Signer key doubled | Copy-paste duplicated the key | Careful paste + verify JSON |
| Dashboard empty output | Gateway set to expensive Opus model | Switch to claude-3.5-haiku |
postCast() no reply support |
Function doesn't accept parentCastId | Build CastAdd directly with hub-nodejs |
| Dry-run saved state | Reply script saved to replies.json during dry run | Guard writes with if (!isDryRun) |
| Heredoc corruption on VPS | Large scripts get mangled when pasted via terminal | Use scp to copy files instead |
| Brave API 429 rate limit | Discovery job fired searches too fast | Add 1.5s sleep() between Brave API calls |
| LLM extra quotes | Replies wrapped in "" or '' |
cleanText() strips surrounding quotes |
| LLM mid-word truncation | Replies cut at exact char limit mid-word | truncateAtWord() cuts at last space |
| Reply only checked latest cast | Old casts with unanswered replies were ignored | fetchAgentCasts() queries Neynar directly for recent casts |
Cron job empty-heartbeat-file |
Gateway cron failed when HEARTBEAT.md missing | Create HEARTBEAT.md with content in workspace root |
OpenClaw (v2026.2.2-3) is an open-source AI agent framework. It provides:
- Gateway — persistent WebSocket service managing sessions, routing, channels (port 18789)
- Skills — modular capabilities described by
SKILL.md, installed via ClawHub or git - SOUL.md — agent personality, injected into the system prompt
- AGENTS.md — operating instructions and rules
- BOOTSTRAP.md — first-run conversation script for agent identity discovery
- Dashboard — web UI for chat, configuration, logs, cron jobs
- Security — sandboxed execution, DM policies, credential storage in
~/.openclaw/secrets/
| File | Purpose |
|---|---|
~/.openclaw/openclaw.json |
Gateway config (model, port, auth) |
~/.openclaw/workspace/SOUL.md |
Agent personality |
~/.openclaw/workspace/AGENTS.md |
Operating rules |
~/.openclaw/workspace/BOOTSTRAP.md |
First-run identity discovery |
~/.openclaw/workspace/skills/*/SKILL.md |
Skill descriptors |
~/.openclaw/secrets/ |
API keys, wallets (not in git) |
Skills are folders with a SKILL.md that describes capabilities the agent can use. Load precedence: workspace skills override global skills. Install via:
npx clawhub@latest install <skill-name>(gets SKILL.md only)git clone(gets full source code — recommended)
The gateway has a built-in cron scheduler for "wakeups" — scheduled agent runs through the LLM pipeline. This is different from our systemd timers which run Node scripts directly. Gateway cron jobs appear in the dashboard under Control → Cron Jobs.
x402 enables pay-per-request for AI agents:
Agent Neynar Hub
│ │
│ POST /v1/submitMessage │
│ X-PAYMENT: <base64 payload> │
│ Content-Type: octet-stream │
│ ─────────────────────────────► │
│ │
│ The X-PAYMENT header contains:│
│ - EIP-3009 signature │
│ - USDC transfer authorization │
│ - from: agent wallet │
│ - to: Neynar payment address │
│ - value: 1000 (0.001 USDC) │
│ │
│ 200 OK (cast submitted) │
│ ◄───────────────────────────── │
Key details:
- Uses EIP-3009
transferWithAuthorization— gasless USDC authorization - Payment is 0.001 USDC per API call (both reads and writes)
- Signed with the custody wallet's private key on Base (chain ID 8453)
- USDC contract on Base:
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 - Neynar payment address:
0xA6a8736f18f383f1cc2d938576933E5eA7Df01A1
ERC-8004 proposes three onchain registries:
- Identity Registry — agent addresses, capabilities, metadata
- Reputation Registry — onchain track record
- Validation Registry — verify agent claims
Future work: register our agent for discoverability.
~/.openclaw/
├── openclaw.json ← gateway config
├── farcaster-credentials.json ← FID, custody key, signer key (chmod 600)
├── secrets/
│ ├── farcaster-wallet.json ← agent wallet
│ ├── prompt.env ← OPENROUTER_API_KEY, PROMPT_PROVIDER, PROMPT_MODEL
│ └── discovery.env ← BRAVE_API_KEY for web research
└── workspace/
├── SOUL.md ← agent personality
├── AGENTS.md ← operating rules
├── BOOTSTRAP.md ← first-run identity script
├── HEARTBEAT.md ← cron job wake-up instructions
└── farcaster-agent/ ← git clone of the skill
├── src/
│ ├── index.js ← exports: postCast, loadCredentials, etc.
│ ├── post-cast.js ← Hub-based cast posting via x402
│ ├── x402.js ← x402 payment header + request helper
│ ├── config.js ← RPC URLs, Neynar endpoints, USDC address
│ ├── credentials.js ← credential storage/loading
│ ├── register-fid.js ← FID registration on Optimism
│ ├── add-signer.js ← Ed25519 signer management
│ └── set-profile.js ← username, display name, bio
├── scripts/
│ ├── daily_prompt.js ← hourly build-streak posts (tracks recentCasts)
│ ├── reply_to_replies.js ← smart reply system (multi-cast, threading, research)
│ ├── tip_winners.js ← deterministic USDC tipping + tx reply
│ └── discovery_job.js ← Brave Search web research (6AM/6PM)
├── memory/
│ ├── HEARTBEAT.md ← wake-up instructions
│ └── DISCOVERY.md ← latest Base ecosystem news (auto-generated)
└── .state/
├── daily_prompt.json ← streak, recent prompts, last hash, recentCasts
├── replies.json ← which replies we've responded to + runCount
├── tips.json ← which casts we've tipped for
└── discovery.json ← raw discovery results + sparks
~/.config/systemd/user/
├── openclaw-gateway.service ← gateway (always running)
├── bea-heartbeat.service + .timer ← hourly posting
├── bea-replies.service + .timer ← smart reply check every 15 min
├── bea-tips.service + .timer ← daily tipping at 4 PM
└── bea-discovery.service + .timer ← web research at 6AM/6PM
With the agent wallet holding USDC on Base:
| Action | Cost | Frequency | Daily Cost |
|---|---|---|---|
| Heartbeat (post) | 0.001 USDC | ~3/day | 0.003 |
| Reply reads (multi-cast) | 0.001 USDC | ~108/day | 0.108 |
| Reply posts | 0.001 USDC | ~5/day | 0.005 |
| Tip read | 0.001 USDC | 1/day | 0.001 |
| Tip post (reply) | 0.001 USDC | 2/day (2 winners) | 0.002 |
| USDC tips | 0.02 USDC | 2/day | 0.040 |
| LLM (OpenRouter) | varies | ~15 calls/day | ~0.01 |
| Brave Search | free | ~6/day | 0.000 |
| Total | ~0.18 USDC/day |
With 1 USDC, the agent runs for ~5.5 days.
"Missing Farcaster credentials"
→ Check ~/.openclaw/farcaster-credentials.json exists with _active pointing to the right FID.
"Missing SIGNER_PK or FID"
→ export your shell variables before running Node scripts.
Agent posts nothing / "Failed to generate prompt"
→ Check API keys in ~/.openclaw/secrets/prompt.env. Test with --dry-run.
"Insufficient USDC for tip" → Fund the agent wallet with USDC on Base mainnet.
x402 submit fails
→ Agent wallet needs USDC on Base. Check balance: node -e "..." (see Step 7).
Systemd timer not firing
→ loginctl enable-linger $USER + systemctl --user daemon-reload
Dashboard empty / not responding
→ Check model in openclaw.json. Switch to openrouter/anthropic/claude-3.5-haiku (cheap, fast).
Bridge reverts during auto-setup → Use the manual flow (Step 3). It's more reliable.
postCast() doesn't support replies
→ Build CastAdd directly with @farcaster/hub-nodejs and parentCastId. See reply_to_replies.js.
Brave Search 429 rate limiting
→ Free plan allows 1 req/sec. Add await sleep(1500) between API calls. See discovery_job.js.
Cron job fails with empty-heartbeat-file
→ Create HEARTBEAT.md with content in the workspace root (~/.openclaw/workspace/farcaster-agent/HEARTBEAT.md). File must not be empty.
Reply script only checks latest cast
→ The upgraded reply_to_replies.js fetches agent's own casts directly from Neynar API. No longer depends on daily_prompt.json.
LLM replies have extra quotes or get cut mid-word
→ cleanText() strips surrounding quotes. truncateAtWord() cuts at the last space instead of mid-word.
Large scripts get corrupted via heredoc on VPS
→ Use scp to copy files from your Mac to the VPS instead of pasting via terminal.
- OpenClaw Docs
- Farcaster Agent Repo
- Neynar API Docs
- x402 Protocol
- ERC-8004 Draft
- Base Docs
- EIP-3009 (transferWithAuthorization)
Built by Base East Africa DevRel for the Base Builder Quest. Agent powered by OpenClaw + Claude + x402 on Base.