Standalone Node.js/TypeScript service that registers as @reppodant on Virtuals Protocol ACP v2, accepts jobs from other AI agents, fetches X post content, mints pods on Base, and submits metadata to Reppo's API.
- 🤖 ACP v2 Integration — Accepts jobs from any agent in the Virtuals ecosystem
- 🐦 X/Twitter Fetching — Extracts post content, author, and media
- ⛓️ On-chain Minting — Mints pods on Base with automatic REPPO token handling
- 💱 Auto Swap — Swaps USDC → REPPO via Uniswap V3 when balance is low
- 👤 Buyer Profiles — Creates Reppo profiles for buyer agents on-demand
- 🔒 Production-Ready — Retry logic, deduplication, file locking, structured logging
Other AI Agents (Virtuals ecosystem)
│
│ ACP job: {postUrl, subnet, agentName?, agentDescription?}
v
┌──────────────────────────────────────────┐
│ reppo-acp-agent │
│ Identity: @reppodant │
│ │
│ 1. Validate job payload │
│ 2. Check dedup (prevent double-mint) │
│ 3. Accept ACP job │
│ 4. Fetch X post content via X API │
│ 5. Swap USDC → REPPO if needed │
│ 6. Mint pod on Base (on-chain) │
│ 7. Create buyer profile (if provided) │
│ 8. Submit metadata to Reppo API │
│ 9. Deliver result via ACP │
└──────────────────────────────────────────┘
│
│ {postUrl, subnet, txHash, podId, basescanUrl}
v
Buyer agent receives result
npm install
cp .env.example .envFill in .env:
| Variable | Required | Description |
|---|---|---|
PRIVATE_KEY |
Yes | Wallet private key (32-byte hex, with or without 0x) |
ACP_ENTITY_ID |
Yes | Entity ID from Virtuals ACP |
ACP_WALLET_ADDRESS |
Yes | AA wallet address from Virtuals (0x-prefixed) |
REPPO_API_URL |
Yes | Reppo API base URL (https://reppo.ai/api/v1) |
REPPO_AGENT_NAME |
Yes | Agent name for Reppo registration |
REPPO_AGENT_DESCRIPTION |
Yes | Agent description for Reppo registration |
TWITTER_BEARER_TOKEN |
Yes | X API bearer token (app-only, read-only) |
RPC_URL |
No | Custom Base RPC URL (defaults to public) |
POLL_INTERVAL_MS |
No | ACP polling interval in ms (default: 10000) |
ACP_TESTNET |
No | Set to true for Base Sepolia testnet |
HEALTH_PORT |
No | Health check server port (default: 3000) |
LOG_LEVEL |
No | Log level: debug, info, warn, error (default: info) |
# Development
npm run dev
# Production
npm run build
npm startOn startup the agent will:
- Load and validate configuration
- Initialize dedup state from disk
- Start health check server
- Register with Reppo API (first run only)
- Initialize chain clients (Base)
- Connect to ACP and start polling for jobs
The agent exposes health endpoints for monitoring:
# Liveness check (JSON)
curl http://localhost:3000/health
# Readiness check
curl http://localhost:3000/readyResponse:
{
"status": "healthy",
"started": "2025-02-09T22:00:00.000Z",
"lastPoll": "2025-02-09T22:05:00.000Z",
"processedTweets": 42,
"uptime": 3600
}Jobs submitted via ACP must include:
{
"postUrl": "https://x.com/user/status/1234567890",
"subnet": "crypto",
"agentName": "MyAgent",
"agentDescription": "Optional agent bio for new Reppo profiles"
}| Field | Required | Description |
|---|---|---|
postUrl |
Yes | X/Twitter post URL |
subnet |
Yes | Reppo subnet to publish to |
agentName |
No | Create Reppo profile for buyer agent |
agentDescription |
No | Profile description (uses agentName if omitted) |
npm testsrc/
index.ts Entry point — init, health server, polling, shutdown
config.ts Env var loading & validation
constants.ts Contract addresses, ABIs, pool fees
types.ts Shared TypeScript types
acp.ts ACP v2 client setup & callbacks
twitter.ts X API client (fetch tweet by URL)
reppo.ts Reppo API (register agent, submit pod metadata)
chain.ts Viem clients, swap, mintPod, approve
lib/
http.ts fetchJSON, withRetry, isRetryableError
logger.ts Structured logging (pino)
dedup.ts Deduplication with file persistence & locking
handlers/
publish.ts Core job handler (validate → fetch → mint → deliver)
__tests__/ Unit tests (vitest)
The agent persists state to these files (gitignored):
| File | Purpose |
|---|---|
.reppo-session.json |
Reppodant agent credentials |
.reppo-buyer-sessions.json |
Cached buyer agent credentials |
.reppo-dedup.json |
Processed tweet IDs (prevents double-mint) |
| Contract | Address |
|---|---|
| PodManager | 0xcfF0511089D0Fbe92E1788E4aFFF3E7930b3D47c |
| REPPO Token | 0xFf8104251E7761163faC3211eF5583FB3F8583d6 |
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Uniswap SwapRouter02 | 0x2626664c2603336E57B271c5C0b26F421741e481 |
| Uniswap QuoterV2 | 0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a |
The agent includes comprehensive error handling:
- Retry Logic — All API calls and chain operations retry with exponential backoff
- Rate Limits — Twitter API retries with longer delays (5 attempts, 2s base)
- Deduplication — Same tweet won't be minted twice (persisted across restarts)
- Mutex Locks — Prevents concurrent processing of the same tweet
- Graceful Shutdown — SIGINT/SIGTERM handled, waits for in-flight jobs
Structured JSON logging via pino. Set LOG_LEVEL=debug for verbose output.
# Pretty print in development
LOG_LEVEL=debug npm run devMIT