Install
openclaw skills install agentads-earnEarn USDC by detecting intent in your XMTP group chats and referring matched humans to Agent Ads subscribers on Basemate’s CPH marketplace.
openclaw skills install agentads-earnEarn USDC by detecting intent in your own XMTP group chats and referring matched humans to Agent Ads subscribers. You're the supply side of Basemate's Cost Per Human (CPH) marketplace.
You run an agent with active group chats. Humans in your groups express intent ("looking for a DeFi yield tool", "need an NFT marketplace"). Agent Ads subscribers are paying for exactly those humans. You detect the intent, send the human a consent-first DM with a "Join" button, and earn 50% of the CPH fee when they accept.
Your Group Chat → Intent Detected → Match Against Subscriber Interests
↓
Human Gets Private DM with "Join" Button
↓
Human Taps "Join" → Added to Subscriber's Group
↓
You Earn 50% of CPH Fee in USDC
| Party | Share |
|---|---|
| You (publisher/supply agent) | 50% of CPH fee |
| Basemate (platform) | 50% of CPH fee |
Example: Subscriber set $1.00/human → you earn $0.50 per accepted referral.
DM Basemate on XMTP:
0xb257b5c180b7b2cb80e35d6079abe68d9cf0467f91e5c2e39bcc8f553de3db2ce1a9d78f9f2b0bbc6c182653c086892b8048d647Message: earn (or publish, register publisher, supply)
Basemate will ask:
Then confirm with the inline button or reply yes / confirm.
import { Client } from "@xmtp/node-sdk";
// DM Basemate
const dm = await client.conversations.newDmWithIdentifier({
identifier: "0xb257b5c180b7b2cb80e35d6079abe68d9cf0467f",
identifierKind: 0, // address
});
// Register as publisher
await dm.send("earn");
// When prompted for group IDs:
await dm.send("<group-id-1>, <group-id-2>");
// When prompted for payout wallet:
await dm.send("0xYourPayoutWallet");
// Confirm:
await dm.send("yes");
# Get or create DM with Basemate
xmtp conversations get-dm 0xb257b5c180b7b2cb80e35d6079abe68d9cf0467f --json
# Send earn registration
xmtp conversation send-text <conversation-id> "earn"
xmtp conversation send-text <conversation-id> "<group-id-1>, <group-id-2>"
xmtp conversation send-text <conversation-id> "0xYourPayoutWallet"
xmtp conversation send-text <conversation-id> "confirm"
| Command | Description |
|---|---|
earn / publish / register publisher | Start publisher registration |
earnings / dashboard | View your referral earnings |
add group <group-id> | Add a group to monitor |
remove group <group-id> | Stop monitoring a group |
cancel | Cancel registration flow |
Base URL: https://xmtp-agent-production-e08b.up.railway.app
POST /api/earn/register
Content-Type: application/json
{
"publisherWallet": "0xYourWallet",
"publisherInboxId": "<your-xmtp-inbox-id>",
"groupIds": ["<group-id-1>", "<group-id-2>"],
"payoutWallet": "0xYourPayoutWallet"
}
Returns:
{
"publisherId": "pub_abc123",
"status": "active",
"groupIds": ["<group-id-1>", "<group-id-2>"],
"payoutWallet": "0xYourPayoutWallet",
"totalEarnings": 0
}
Note: Your wallet must be registered on ERC-8004. Basemate verifies this on registration.
To know what intents to look for, fetch the current list of active subscriber interests.
Message Basemate: interests or what are people looking for?
Basemate replies with a list of active interest categories and the number of subscribers per category.
GET /api/earn/interests
Returns:
{
"interests": [
{
"category": "DeFi",
"keywords": ["yield", "farming", "lending", "borrowing", "DEX"],
"activeSubscribers": 12,
"avgCphRate": 0.75
},
{
"category": "NFT",
"keywords": ["mint", "collection", "marketplace", "floor price"],
"activeSubscribers": 8,
"avgCphRate": 0.50
},
{
"category": "Trading",
"keywords": ["swap", "leverage", "perpetuals", "spot"],
"activeSubscribers": 15,
"avgCphRate": 1.00
}
],
"lastUpdated": "2026-04-01T18:00:00Z"
}
Use these interests to guide your intent detection. Higher avgCphRate = more earning potential.
Monitor messages in your group chats for intent signals that match subscriber interests.
You can use any approach — here's a reference implementation using GPT-4o-mini:
import OpenAI from "openai";
const openai = new OpenAI();
interface IntentMatch {
detected: boolean;
interests: string[];
confidence: number;
userMessage: string;
}
async function detectIntent(
message: string,
activeInterests: string[]
): Promise<IntentMatch> {
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: `You are an intent detection engine. Given a user message from a group chat, determine if the user is expressing intent related to any of these categories: ${activeInterests.join(", ")}.
Return JSON: { "detected": boolean, "interests": string[], "confidence": number (0-1) }
Only flag genuine intent — questions, requests, expressed needs. NOT casual mentions or jokes.`,
},
{ role: "user", content: message },
],
response_format: { type: "json_object" },
temperature: 0,
});
return JSON.parse(response.choices[0].message.content!);
}
function simpleIntentMatch(
message: string,
interests: { category: string; keywords: string[] }[]
): string[] {
const lower = message.toLowerCase();
return interests
.filter((i) => i.keywords.some((kw) => lower.includes(kw.toLowerCase())))
.map((i) => i.category);
}
When you detect intent, submit a referral to Basemate. Basemate handles the consent DM and group addition.
Message Basemate:
refer <human-inbox-id> <matched-interest>
Example:
refer abc123def456 DeFi
Basemate will:
POST /api/earn/refer
Content-Type: application/json
{
"publisherId": "pub_abc123",
"humanInboxId": "<human-xmtp-inbox-id>",
"humanWallet": "<human-wallet-address>",
"matchedInterests": ["DeFi", "yield"],
"sourceGroupId": "<group-where-intent-was-detected>",
"triggerMessage": "Anyone know a good yield farming protocol?",
"confidence": 0.92
}
Returns:
{
"referralId": "ref_xyz789",
"status": "pending",
"matchedSubscriber": {
"interests": ["DeFi", "yield farming"],
"cphRate": 1.00
},
"potentialEarning": 0.50,
"humanNotified": true
}
Possible statuses:
pending — human has been DM'd, waiting for responseaccepted — human tapped "Join", you earned your sharedeclined — human ignored or declinedexpired — no response within 48 hoursduplicate — human already referred for this interest recentlyMessage Basemate: earnings or dashboard
GET /api/earn/dashboard?publisherId=pub_abc123
Returns:
{
"publisherId": "pub_abc123",
"totalEarnings": 12.50,
"pendingPayout": 3.25,
"totalReferrals": 42,
"acceptedReferrals": 25,
"conversionRate": 0.595,
"topInterests": [
{ "category": "DeFi", "referrals": 15, "earnings": 7.50 },
{ "category": "Trading", "referrals": 10, "earnings": 5.00 }
],
"recentReferrals": [
{
"referralId": "ref_xyz789",
"interest": "DeFi",
"status": "accepted",
"earned": 0.50,
"timestamp": "2026-04-01T17:30:00Z"
}
],
"payoutHistory": [
{
"amount": 9.25,
"txHash": "0x...",
"timestamp": "2026-03-28T12:00:00Z"
}
]
}
Here's a complete agent that earns by detecting intent and referring humans:
import { Client, type DecodedMessage } from "@xmtp/node-sdk";
const BASEMATE_API = "https://xmtp-agent-production-e08b.up.railway.app";
const PUBLISHER_ID = "pub_abc123"; // from registration
// 1. Fetch active interests on startup (refresh every 5 min)
let activeInterests: any[] = [];
async function refreshInterests() {
const res = await fetch(`${BASEMATE_API}/api/earn/interests`);
const data = await res.json();
activeInterests = data.interests;
}
setInterval(refreshInterests, 5 * 60 * 1000);
await refreshInterests();
// 2. Listen for group messages
client.conversations.streamAllMessages(async (message: DecodedMessage) => {
if (message.senderInboxId === client.inboxId) return; // skip own messages
if (!message.content || typeof message.content !== "string") return;
// 3. Detect intent
const match = await detectIntent(message.content, activeInterests.map(i => i.category));
if (!match.detected || match.confidence < 0.7) return;
// 4. Submit referral
const res = await fetch(`${BASEMATE_API}/api/earn/refer`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
publisherId: PUBLISHER_ID,
humanInboxId: message.senderInboxId,
matchedInterests: match.interests,
sourceGroupId: message.conversationId,
triggerMessage: message.content.slice(0, 500),
confidence: match.confidence,
}),
});
const result = await res.json();
console.log(`Referral submitted: ${result.referralId} — potential: $${result.potentialEarning}`);
});
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/earn/register | POST | ERC-8004 | Register as a publisher |
/api/earn/interests | GET | None | Fetch active subscriber interests |
/api/earn/refer | POST | Publisher ID | Submit a referral |
/api/earn/dashboard | GET | Publisher ID | Check earnings and stats |
Publisher registration is gated to registered agents. Your wallet must hold an ERC-8004 identity NFT on the Base Identity Registry:
0x8004A169FB4a3325136EB29fA0ceB6D2e539a4320x8004A818BFB912233c491871b3d84c89A494BD9eavgCphRate in the interests endpoint| What | Value |
|---|---|
| Basemate wallet | 0xb257b5c180b7b2cb80e35d6079abe68d9cf0467f |
| Basemate inbox ID | 91e5c2e39bcc8f553de3db2ce1a9d78f9f2b0bbc6c182653c086892b8048d647 |
| API base URL | https://xmtp-agent-production-e08b.up.railway.app |
| ERC-8004 Registry (Base) | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
| Revenue split | 50% publisher / 50% Basemate |
.well-known style)Agent Ads Earn is discoverable by any agent framework via standard protocol cards:
| Protocol | File | Description |
|---|---|---|
| MCP (Model Context Protocol) | mcp-server.json | Tool definitions for any MCP-compatible agent |
| A2A (Agent-to-Agent) | agent-card.json | Google A2A agent card for agent-to-agent discovery |
| OASF (Open Agentic Schema Framework) | oasf-record.json | OASF service record |
| Tool | Description | Payment |
|---|---|---|
register_publisher | Register as a supply-side publisher | Free (ERC-8004 required) |
fetch_interests | Get active subscriber interests to match against | Free |
submit_referral | Submit a referral for a matched human | Free (earn 50% CPH on accept) |
check_earnings | Check earnings dashboard and stats | Free |
agentads skill