@clawhub-flotapponnier-bdbc7ff7e3
Pay-as-you-go Mobula API access — fetch crypto prices, wallet positions, and market data using a Tempo wallet that pays per call (~$0.0004) in USDC.e. No sub...
---
description: Pay-as-you-go Mobula API access — fetch crypto prices, wallet positions, and market data using a Tempo wallet that pays per call (~$0.0004) in USDC.e. No subscription, no API key.
---
# MPP (Machine Payments Protocol) Skill
Mobula's MPP API is **pay-per-call** : every request is settled on-chain from your own wallet using USDC.e on the Tempo chain (chainId 4217). No signup, no API key — just a funded wallet.
This skill gives you (the agent) everything needed to:
1. Generate a Tempo wallet,
2. Top it up via the public bridge,
3. Make Mobula API calls and pay per-call automatically.
## Setup (one-time, ~2 min)
```bash
# 1. Clone the skill (skip if already installed in your skills dir)
git clone https://github.com/Flotapponnier/mpp-skill.git
cd mpp-skill
bun install
# 2. Create a hot wallet (stored at ~/.mpp-skill/wallet.json, chmod 600)
bun run start wallet-create
# → prints address + bridge link
# 3. Fund the wallet with USDC.e on Tempo
# Open: https://relay.link/bridge/tempo?toAddress=<your-address>
# Bridge a few dollars of USDC from any chain (Base, Ethereum, …).
# Tempo's gas token IS USDC, so $1 of USDC.e is enough to make ~2,500 calls.
# 4. Verify the balance
bun run start balance
```
Once funded, every subsequent call is automatic — no manual signing.
## CLI commands
| Command | What it does |
|---|---|
| `bun run start wallet-create` | Generate a new Tempo wallet (won't overwrite an existing one) |
| `bun run start balance` | Show wallet address, USDC.e balance, bridge link |
| `bun run start price <asset>` | Token price — accepts name, symbol, or contract address |
| `bun run start wallet <addr>` | Wallet portfolio positions |
| `bun run start lighthouse` | Trending tokens |
| `bun run start call <path> k=v…` | Generic call to any `/api/2/*` endpoint |
Examples:
```bash
bun run start price bitcoin
bun run start price 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
bun run start wallet 0xd04b77bb40944110ec9c9e3165f67dadf9d52f21
bun run start lighthouse
bun run start call /api/2/wallet/activity wallet=0xabc...
```
## Programmatic use (inside an agent)
Import directly from your TypeScript code — useful when each end-user has their own wallet (e.g. a Telegram bot where each Telegram ID gets a per-user encrypted wallet).
### Per-user wallets (multi-tenant agents)
```ts
import { createUserWallet, getUserWalletAddress } from "mpp-skill/src/wallet";
import { userMobulaCall, getUserTempoBalance } from "mpp-skill/src/mpp/user-mpp";
// On first interaction with a Telegram user
const userId = 1162998296; // Telegram numeric ID
let address = await getUserWalletAddress(userId);
if (!address) {
const w = await createUserWallet(userId);
address = w.address;
// Tell the user to fund it:
// https://relay.link/bridge/tempo?toAddress=address
}
// Check funds
const bal = await getUserTempoBalance(userId);
console.log(`User has $bal?.usd ?? 0 USDC.e on Tempo`);
// Make an API call (paid from the user's own wallet)
const price = await userMobulaCall(userId, "/api/2/token/price", { asset: "bitcoin" });
```
Per-user wallets are stored at `.claude/claudeclaw/wallets/{userId}.json`, AES-256-GCM encrypted with a key derived from `HMAC-SHA256(WALLET_SECRET, userId)`. The secret is auto-generated once at `.claude/claudeclaw/wallet.secret` (chmod 600). Different users cannot decrypt each other's keys.
### Direct low-level call
If you already have a private key and want full control:
```ts
import { tempoFetch } from "mpp-skill/src/mpp/tempo-client";
import type { Hex } from "viem";
const data = await tempoFetch(
"/api/2/token/price",
{ asset: "ethereum" },
process.env.TEMPO_PRIVATE_KEY as Hex,
);
```
## How the payment works (under the hood)
1. Agent calls `GET https://mpp.mobula.io/api/2/token/price?asset=bitcoin` with no auth.
2. Server returns **HTTP 402** with `WWW-Authenticate: Payment id="…", realm="mpp.mobula.io", method="tempo", request="<base64-json>"`. The decoded request specifies `amount` (in USDC.e atoms), `currency: USDC.e`, `recipient`, `methodDetails.chainId: 4217`.
3. Skill signs and broadcasts `transferWithMemo(recipient, amount, challengeId)` on USDC.e (`0x20c000000000000000000000b9537d11c60e8b50`) on Tempo.
4. Skill retries the same request with `Authorization: Payment <base64url(credential)>`, where `credential` references the tx hash.
5. Server validates the tx and returns the data.
The skill does steps 2–4 automatically — agents only see the data response.
## Key facts
- **Chain**: Tempo (chainId `4217`, RPC `https://rpc.tempo.xyz`)
- **Token**: USDC.e (`0x20c000000000000000000000b9537d11c60e8b50`)
- **Gas**: paid in USDC (Tempo uses USDC as native gas token — no separate ETH needed)
- **Cost per call**: ~$0.0004 typical, never above the amount specified in the 402 challenge
- **Bridge**: https://relay.link/bridge/tempo — bridges from Base, Ethereum, Arbitrum, etc.
## Common errors
| Error | Cause | Fix |
|---|---|---|
| `No wallet found` | First-time setup not done | `bun run start wallet-create` |
| `Insufficient Tempo balance: you have $0.0000…` | Wallet not funded yet | Bridge USDC.e via the printed link |
| `Could not parse challenge from: …` | Server didn't return a Tempo `WWW-Authenticate` header | Likely an upstream issue — retry or report |
| `Tempo tx failed: …` | RPC error or revert on-chain | Check balance and `https://explorer.tempo.xyz` for the address |
## When to use this vs a subscription
Mobula's subscription endpoints (`/agent/x402/subscribe`, etc.) are **not currently configured** in production. Until they are, this pay-per-call flow is the only way to call MPP API endpoints from an agent. The CLI returns a clear error if you try the legacy `subscribe` / `status` / `topup` / `key-create` commands.
FILE:README.md
# mpp-skill
Pay-as-you-go Mobula API skill for ClaudeHub / OpenClaw agents.
Fetch crypto prices, wallet positions, and market data without any signup, API key, or subscription. Each call costs ~$0.0004 in USDC.e on the Tempo chain (chainId 4217), settled directly from a wallet you control.
## Quick start
```bash
git clone https://github.com/Flotapponnier/mpp-skill.git
cd mpp-skill
bun install
# 1. Create a Tempo wallet (saved at ~/.mpp-skill/wallet.json, chmod 600)
bun run start wallet-create
# 2. Fund it with USDC.e via the bridge link printed by step 1
# https://relay.link/bridge/tempo?toAddress=<your-address>
# 3. Make calls
bun run start price bitcoin
bun run start wallet 0xd04b77bb40944110ec9c9e3165f67dadf9d52f21
bun run start lighthouse
```
That's it — no API key, no signup, no subscription.
## How it works
When you hit `https://mpp.mobula.io/api/2/*` Mobula returns HTTP 402 with a payment challenge. This skill:
1. Decodes the challenge,
2. Signs and broadcasts `transferWithMemo` on USDC.e (Tempo, chainId 4217) from your wallet,
3. Retries the request with the resulting tx hash as a payment credential,
4. Returns the data.
Tempo's gas is USDC (no separate ETH required), so a few dollars of USDC.e last for thousands of calls.
## Use as an agent skill
```bash
cd ~/your-agent/skills/
git clone https://github.com/Flotapponnier/mpp-skill.git mpp
cd mpp && bun install
```
Then from your agent code:
```ts
// Per-user encrypted wallets (one wallet per Telegram user, etc.)
import { createUserWallet, getUserWalletAddress } from "mpp-skill/src/wallet";
import { userMobulaCall } from "mpp-skill/src/mpp/user-mpp";
const userId = 1162998296;
if (!(await getUserWalletAddress(userId))) await createUserWallet(userId);
const price = await userMobulaCall(userId, "/api/2/token/price", { asset: "bitcoin" });
```
See [SKILL.md](./SKILL.md) for the full agent-facing docs, payment flow internals, and the per-call vs subscription decision.
## Project structure
```
mpp-skill/
├── src/
│ ├── index.ts # CLI entry
│ ├── cli-wallet.ts # Single hot wallet for the CLI (~/.mpp-skill/wallet.json)
│ ├── wallet.ts # Per-user encrypted wallets (for multi-tenant agents)
│ ├── commands/
│ │ └── mpp.ts # CLI command handler
│ └── mpp/
│ ├── tempo-client.ts # 402 → sign → retry flow on Tempo USDC.e
│ └── user-mpp.ts # Helpers tying per-user wallets to tempoFetch
├── SKILL.md
├── package.json
└── README.md
```
## Requirements
- Bun ≥ 1.0.0
- viem (auto-installed via `bun install`)
## License
MIT
FILE:package.json
{
"name": "mpp-skill",
"version": "2.0.0",
"description": "Machine Payments Protocol (MPP) skill for ClaudeHub/OpenClaw agents — pay-as-you-go Mobula API calls via Tempo",
"type": "module",
"main": "src/index.ts",
"bin": {
"mpp": "./src/index.ts"
},
"scripts": {
"start": "bun src/index.ts"
},
"keywords": [
"mpp",
"mobula",
"tempo",
"x402",
"claudehub",
"openclaw",
"agent",
"skill"
],
"author": "Florent Tapponnier",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Flotapponnier/mpp-skill.git"
},
"dependencies": {
"viem": "^2.48.4"
},
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"bun": ">=1.0.0"
}
}
FILE:src/wallet.ts
/**
* Secure per-user wallet management.
*
* Each user gets an isolated wallet file at:
* .claude/claudeclaw/wallets/{userId}.json
*
* The private key is AES-256-GCM encrypted with a key derived from:
* HMAC-SHA256(WALLET_SECRET, userId)
* where WALLET_SECRET is a server-side secret loaded from env or generated once.
*
* No other user can decrypt another user's private key.
*/
import { join } from "node:path";
import { mkdir } from "node:fs/promises";
import { createHmac, createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
import { execSync } from "node:child_process";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const WALLETS_DIR = join(process.cwd(), ".claude", "claudeclaw", "wallets");
const SECRET_FILE = join(process.cwd(), ".claude", "claudeclaw", "wallet.secret");
let _secret: Buffer | null = null;
async function getSecret(): Promise<Buffer> {
if (_secret) return _secret;
const f = Bun.file(SECRET_FILE);
if (await f.exists()) {
_secret = Buffer.from(await f.text(), "hex");
} else {
_secret = randomBytes(32);
await Bun.write(SECRET_FILE, _secret.toString("hex"));
// Restrict permissions
try { execSync(`chmod 600 "SECRET_FILE"`); } catch {}
}
return _secret;
}
function deriveKey(secret: Buffer, userId: number): Buffer {
return createHmac("sha256", secret).update(String(userId)).digest();
}
function encrypt(key: Buffer, plaintext: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
// Format: iv(24hex) + tag(32hex) + ciphertext(hex)
return iv.toString("hex") + tag.toString("hex") + enc.toString("hex");
}
function decrypt(key: Buffer, data: string): string {
const iv = Buffer.from(data.slice(0, 24), "hex");
const tag = Buffer.from(data.slice(24, 56), "hex");
const enc = Buffer.from(data.slice(56), "hex");
const decipher = createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
}
function walletPath(userId: number): string {
return join(WALLETS_DIR, `userId.json`);
}
export interface UserWallet {
address: string;
createdAt: string;
}
/** Generate a new wallet for a user using viem (correct keccak256 derivation). */
function generateWallet(): { address: string; privateKey: string } {
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
return { address: account.address, privateKey };
}
/** Create a wallet for a user. Throws if one already exists. */
export async function createUserWallet(userId: number): Promise<UserWallet> {
await mkdir(WALLETS_DIR, { recursive: true });
const path = walletPath(userId);
if (await Bun.file(path).exists()) {
throw new Error("Wallet already exists for this user");
}
const secret = await getSecret();
const key = deriveKey(secret, userId);
const { address, privateKey } = generateWallet();
const record = {
address,
encryptedKey: encrypt(key, privateKey),
createdAt: new Date().toISOString(),
};
await Bun.write(path, JSON.stringify(record, null, 2));
try { execSync(`chmod 600 "path"`); } catch {}
return { address, createdAt: record.createdAt };
}
/** Get a user's wallet address (public info only). Returns null if no wallet. */
export async function getUserWalletAddress(userId: number): Promise<string | null> {
const path = walletPath(userId);
if (!await Bun.file(path).exists()) return null;
const record = JSON.parse(await Bun.file(path).text());
return record.address ?? null;
}
/** Get the decrypted private key for a user's wallet. Only call for the user's own operations. */
export async function getUserPrivateKey(userId: number): Promise<string | null> {
const path = walletPath(userId);
if (!await Bun.file(path).exists()) return null;
const secret = await getSecret();
const key = deriveKey(secret, userId);
const record = JSON.parse(await Bun.file(path).text());
return decrypt(key, record.encryptedKey);
}
FILE:src/cli-wallet.ts
/**
* Standalone CLI wallet for the `mpp` command.
*
* Stored at ~/.mpp-skill/wallet.json (chmod 600).
* Plain JSON — this is a hot wallet on the user's own machine,
* intended to hold a few dollars of USDC.e for pay-as-you-go API calls.
*
* For per-user wallets in a multi-tenant agent (e.g. Telegram bot),
* use the encrypted helpers in `./wallet.ts` keyed by user ID.
*/
import { join } from "node:path";
import { homedir } from "node:os";
import { mkdir, chmod } from "node:fs/promises";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";
export const CLI_WALLET_DIR = join(homedir(), ".mpp-skill");
export const CLI_WALLET_FILE = join(CLI_WALLET_DIR, "wallet.json");
export interface CliWallet {
address: string;
privateKey: Hex;
createdAt: string;
}
export async function loadCliWallet(): Promise<CliWallet | null> {
const f = Bun.file(CLI_WALLET_FILE);
if (!(await f.exists())) return null;
return JSON.parse(await f.text()) as CliWallet;
}
export async function createCliWallet(): Promise<CliWallet> {
if (await Bun.file(CLI_WALLET_FILE).exists()) {
throw new Error(
`Wallet already exists at CLI_WALLET_FILE. Delete it first to regenerate.`
);
}
await mkdir(CLI_WALLET_DIR, { recursive: true });
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
const record: CliWallet = {
address: account.address,
privateKey,
createdAt: new Date().toISOString(),
};
await Bun.write(CLI_WALLET_FILE, JSON.stringify(record, null, 2));
try {
await chmod(CLI_WALLET_FILE, 0o600);
} catch {}
return record;
}
export async function requireCliWallet(): Promise<CliWallet> {
const w = await loadCliWallet();
if (!w) {
console.error("No wallet found. Run: bun run start wallet-create");
process.exit(1);
}
return w;
}
FILE:src/index.ts
#!/usr/bin/env bun
/**
* MPP (Machine Payments Protocol) CLI
* Manage Mobula API subscriptions and fetch market data
*/
import { mppCommand } from "./commands/mpp";
const args = process.argv.slice(2);
if (args.length === 0) {
mppCommand(["help"]);
} else {
mppCommand(args);
}
FILE:src/commands/mpp.ts
/**
* MPP (Machine Payments Protocol) CLI.
*
* Pay-as-you-go Mobula API calls via Tempo (chainId 4217) USDC.e.
* Each call costs ~$0.0004 and is settled directly from your wallet —
* no subscription, no API key, no signup.
*/
import { tempoFetch, getTempoBalance, BRIDGE_URL } from "../mpp/tempo-client";
import {
CLI_WALLET_FILE,
createCliWallet,
loadCliWallet,
requireCliWallet,
} from "../cli-wallet";
function bridgeUrl(address: string): string {
return `BRIDGE_URL?toAddress=address`;
}
export async function mppCommand(args: string[]): Promise<void> {
const [subcommand, ...rest] = args;
try {
switch (subcommand) {
case "wallet-create": {
const w = await createCliWallet();
console.log("✓ Wallet created");
console.log(`Address: w.address`);
console.log(`Saved to: CLI_WALLET_FILE`);
console.log("\nNext: fund it with USDC.e on Tempo (chainId 4217):");
console.log(` bridgeUrl(w.address)`);
console.log("\nAny amount works — calls cost ~$0.0004 each.");
break;
}
case "wallet-info":
case "balance": {
const w = await requireCliWallet();
let usd = "?";
try {
const raw = await getTempoBalance(w.address);
usd = (Number(raw) / 1_000_000).toFixed(4);
} catch (e: any) {
console.error("Could not fetch on-chain balance:", e.message);
}
console.log(`Address: w.address`);
console.log(`USDC.e: $usd (Tempo, chainId 4217)`);
console.log(`Bridge: bridgeUrl(w.address)`);
break;
}
case "price": {
const [asset] = rest;
if (!asset) {
console.error("Usage: bun run start price <asset>");
console.error("Example: bun run start price bitcoin");
process.exit(1);
}
const w = await requireCliWallet();
const data = await tempoFetch(
"/api/2/token/price",
{ asset },
w.privateKey,
);
console.log(JSON.stringify(data, null, 2));
break;
}
case "wallet": {
const [address] = rest;
if (!address) {
console.error("Usage: bun run start wallet <address>");
process.exit(1);
}
const w = await requireCliWallet();
const data = await tempoFetch(
"/api/2/wallet/positions",
{ wallet: address },
w.privateKey,
);
console.log(JSON.stringify(data, null, 2));
break;
}
case "lighthouse": {
const w = await requireCliWallet();
const data = await tempoFetch(
"/api/2/market/lighthouse",
{},
w.privateKey,
);
console.log(JSON.stringify(data, null, 2));
break;
}
case "call": {
const [path, ...kvs] = rest;
if (!path) {
console.error('Usage: bun run start call <path> [key=value ...]');
console.error('Example: bun run start call /api/2/wallet/activity wallet=0xabc');
process.exit(1);
}
const params: Record<string, string> = {};
for (const kv of kvs) {
const idx = kv.indexOf("=");
if (idx > 0) params[kv.slice(0, idx)] = kv.slice(idx + 1);
}
const w = await requireCliWallet();
const data = await tempoFetch(path, params, w.privateKey);
console.log(JSON.stringify(data, null, 2));
break;
}
case "subscribe":
case "status":
case "topup":
case "key-create":
case "key-revoke": {
console.error(
`The "subcommand" command is no longer supported.`,
);
console.error(
`Mobula MPP currently only ships pay-as-you-go via Tempo (USDC.e).`,
);
console.error("");
console.error("To get started:");
console.error(" 1. bun run start wallet-create");
console.error(
` 2. fund the wallet at BRIDGE_URL?toAddress=<your-addr>`,
);
console.error(" 3. bun run start price bitcoin");
process.exit(1);
break;
}
case "help":
case undefined:
default: {
const wallet = await loadCliWallet();
console.log(`
MPP — Pay-as-you-go Mobula API client (Tempo / USDC.e)
Setup:
bun run start wallet-create Generate a Tempo hot wallet
bun run start balance Show address + USDC.e balance + bridge link
Data calls (each costs ~$0.0004 from your USDC.e balance):
bun run start price <asset> Token price (e.g. bitcoin, ethereum, 0x...)
bun run start wallet <address> Wallet positions
bun run start lighthouse Trending tokens
bun run start call <path> k=v.. Generic call (any /api/2/* endpoint)
Removed (subscription endpoints not available):
subscribe, status, topup, key-create, key-revoke
Wallet location:
CLI_WALLET_FILE${wallet.address` : " (not yet created)"}
Docs: https://github.com/Flotapponnier/mpp-skill
`.trim());
break;
}
}
} catch (error: any) {
console.error("\n❌ Error:", error.message);
process.exit(1);
}
}
FILE:src/mpp/tempo-client.ts
/**
* Tempo MPP client — per-call payments using user's own wallet.
*
* Flow:
* 1. Call Mobula endpoint → 402 with WWW-Authenticate: Payment header
* 2. Parse challenge (id, request: {amount, currency, recipient})
* 3. Sign + broadcast transferWithMemo(recipient, amount, challengeId) on Tempo
* 4. Retry with Authorization: Payment <base64url(credential)>
* 5. Get 200 response
*
* Token: USDC.e on Tempo (chainId 4217)
* Contract: 0x20c000000000000000000000b9537d11c60e8b50
* Function: transferWithMemo(address to, uint256 amount, bytes32 memo)
*/
import {
createWalletClient,
createPublicClient,
http,
parseAbi,
type Hex,
encodeFunctionData,
keccak256,
toBytes,
toHex,
hexToBytes,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
// Tempo mainnet
const TEMPO_CHAIN = {
id: 4217,
name: "Tempo",
nativeCurrency: { name: "USD", symbol: "USD", decimals: 6 },
rpcUrls: { default: { http: ["https://rpc.tempo.xyz"] } },
} as const;
// USDC.e on Tempo
export const USDC_E = "0x20c000000000000000000000b9537d11c60e8b50" as const;
// Public bridge UI for moving funds onto Tempo
export const BRIDGE_URL = "https://relay.link/bridge/tempo";
const USDC_ABI = parseAbi([
"function transferWithMemo(address to, uint256 amount, bytes32 memo) external returns (bool)",
"function balanceOf(address account) external view returns (uint256)",
]);
const MOBULA_BASE_URL = "https://mpp.mobula.io";
interface MppChallenge {
id: string;
amount: string;
currency: string;
recipient: string;
chainId: number;
expires: string;
}
function parseWwwAuthenticate(header: string): MppChallenge | null {
try {
// Extract fields from: Payment id="...", realm="...", method="tempo", request="base64...", expires="..."
const idMatch = header.match(/\bid="([^"]+)"/);
const requestMatch = header.match(/\brequest="([^"]+)"/);
const expiresMatch = header.match(/\bexpires="([^"]+)"/);
if (!idMatch || !requestMatch) return null;
const id = idMatch[1];
const expires = expiresMatch?.[1] ?? "";
// Decode base64url request
const requestJson = Buffer.from(requestMatch[1], "base64").toString("utf8");
const request = JSON.parse(requestJson);
return {
id,
amount: request.amount,
currency: request.currency,
recipient: request.recipient,
chainId: request.methodDetails?.chainId ?? 4217,
expires,
};
} catch {
return null;
}
}
function challengeIdToBytes32(challengeId: string): Hex {
// Encode challengeId as bytes32 — right-pad with zeros if < 32 bytes
const bytes = Buffer.from(challengeId, "utf8");
const padded = Buffer.alloc(32);
bytes.copy(padded, 0, 0, Math.min(bytes.length, 32));
return `0xpadded.toString("hex")`;
}
function buildCredential(challenge: MppChallenge, txHash: Hex, walletAddress: string): string {
const credential = {
challenge: {
id: challenge.id,
realm: "mpp.mobula.io",
method: "tempo",
intent: "charge",
},
payload: {
type: "hash",
hash: txHash,
},
source: `did:pkh:eip155:challenge.chainId:walletAddress`,
};
return Buffer.from(JSON.stringify(credential)).toString("base64url");
}
/**
* Get USDC.e balance on Tempo for an address.
*/
export async function getTempoBalance(address: string): Promise<bigint> {
const client = createPublicClient({
chain: TEMPO_CHAIN,
transport: http(),
});
return client.readContract({
address: USDC_E,
abi: USDC_ABI,
functionName: "balanceOf",
args: [address as Hex],
});
}
/**
* Make a Mobula API call, paying per-call via MPP/Tempo with the user's wallet.
*
* @param path - e.g. "/api/2/token/price"
* @param params - query params
* @param privateKeyHex - user's private key (0x...)
*/
export async function tempoFetch(
path: string,
params: Record<string, string>,
privateKeyHex: Hex
): Promise<unknown> {
const url = new URL(`MOBULA_BASE_URLpath`);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
// First attempt — no auth
const res1 = await fetch(url.toString());
if (res1.ok) return res1.json();
if (res1.status !== 402) {
throw new Error(`Mobula path: res1.status await res1.text()`);
}
// Parse 402 challenge
const wwwAuth = res1.headers.get("www-authenticate");
if (!wwwAuth) throw new Error("402 but no WWW-Authenticate header");
const challenge = parseWwwAuthenticate(wwwAuth);
if (!challenge) throw new Error(`Could not parse challenge from: wwwAuth`);
if (challenge.chainId !== 4217) {
throw new Error(`Unexpected chain in challenge: challenge.chainId (expected Tempo 4217)`);
}
// Sign and broadcast transferWithMemo
const account = privateKeyToAccount(privateKeyHex);
// Check balance before attempting tx
const balance = await getTempoBalance(account.address);
const required = BigInt(challenge.amount);
if (balance < required) {
const balanceUsd = (Number(balance) / 1_000_000).toFixed(4);
const requiredUsd = (Number(required) / 1_000_000).toFixed(4);
throw new Error(
`Insufficient Tempo balance: you have $balanceUsd USDC.e, need $requiredUsd.\n` +
`Fund your wallet at: https://relay.link/bridge/tempo?toAddress=account.address`
);
}
const walletClient = createWalletClient({
account,
chain: TEMPO_CHAIN,
transport: http(),
});
const memo = challengeIdToBytes32(challenge.id);
let txHash: Hex;
try {
txHash = await walletClient.writeContract({
address: USDC_E,
abi: USDC_ABI,
functionName: "transferWithMemo",
args: [challenge.recipient as Hex, BigInt(challenge.amount), memo],
});
} catch (err: any) {
throw new Error(`Tempo tx failed: err.message ?? err`);
}
// Build credential and retry
const credential = buildCredential(challenge, txHash, account.address);
const res2 = await fetch(url.toString(), {
headers: { Authorization: `Payment credential` },
});
if (!res2.ok) {
const body = await res2.text();
throw new Error(`Mobula payment rejected: res2.status body`);
}
return res2.json();
}
FILE:src/mpp/user-mpp.ts
/**
* Per-user Mobula API calls via MPP/Tempo pay-as-you-go.
*
* Each user pays $0.0004–$0.002 per call from their own Tempo wallet.
* No subscription needed — pure per-call.
*/
import { getUserPrivateKey, getUserWalletAddress } from "../wallet";
import { tempoFetch, getTempoBalance } from "./tempo-client";
import type { Hex } from "viem";
const MOBULA_BASE_URL = "https://mpp.mobula.io";
/**
* Call a Mobula data endpoint using the user's own Tempo wallet.
* Pays ~$0.0004 per call directly from their USDC.e balance.
*/
export async function userMobulaCall(
telegramUserId: number,
path: string,
params: Record<string, string> = {}
): Promise<unknown> {
const privateKey = await getUserPrivateKey(telegramUserId);
if (!privateKey) throw new Error("No wallet found. Create one first with /start.");
return tempoFetch(path, params, privateKey as Hex);
}
/**
* Get user's USDC.e balance on Tempo (in human-readable format).
* Returns null if no wallet.
*/
export async function getUserTempoBalance(telegramUserId: number): Promise<{ raw: bigint; usd: string } | null> {
const address = await getUserWalletAddress(telegramUserId);
if (!address) return null;
try {
const raw = await getTempoBalance(address);
const usd = (Number(raw) / 1_000_000).toFixed(4);
return { raw, usd };
} catch {
return null;
}
}
/**
* Fetch token price for a given contract address + blockchain.
*/
export async function getUserTokenPrice(
telegramUserId: number,
address: string,
blockchain: string
): Promise<unknown> {
return userMobulaCall(telegramUserId, "/api/2/token/price", { address, blockchain });
}
/**
* Fetch wallet portfolio positions.
*/
export async function getUserWalletPositions(
telegramUserId: number,
wallet: string
): Promise<unknown> {
return userMobulaCall(telegramUserId, "/api/2/wallet/positions", { wallet });
}
/**
* Fetch wallet activity / transactions.
*/
export async function getUserWalletActivity(
telegramUserId: number,
wallet: string
): Promise<unknown> {
return userMobulaCall(telegramUserId, "/api/2/wallet/activity", { wallet });
}
Pay-as-you-go Mobula API access — fetch crypto prices, wallet positions, and market data using a Tempo wallet that pays per call (~$0.0004) in USDC.e. No sub...
---
description: Pay-as-you-go Mobula API access — fetch crypto prices, wallet positions, and market data using a Tempo wallet that pays per call (~$0.0004) in USDC.e. No subscription, no API key.
---
# MPP (Machine Payments Protocol) Skill
Mobula's MPP API is **pay-per-call** : every request is settled on-chain from your own wallet using USDC.e on the Tempo chain (chainId 4217). No signup, no API key — just a funded wallet.
This skill gives you (the agent) everything needed to:
1. Generate a Tempo wallet,
2. Top it up via the public bridge,
3. Make Mobula API calls and pay per-call automatically.
## Setup (one-time, ~2 min)
```bash
# 1. Clone the skill (skip if already installed in your skills dir)
git clone https://github.com/Flotapponnier/mpp-skill.git
cd mpp-skill
bun install
# 2. Create a hot wallet (stored at ~/.mpp-skill/wallet.json, chmod 600)
bun run start wallet-create
# → prints address + bridge link
# 3. Fund the wallet with USDC.e on Tempo
# Open: https://relay.link/bridge/tempo?toAddress=<your-address>
# Bridge a few dollars of USDC from any chain (Base, Ethereum, …).
# Tempo's gas token IS USDC, so $1 of USDC.e is enough to make ~2,500 calls.
# 4. Verify the balance
bun run start balance
```
Once funded, every subsequent call is automatic — no manual signing.
## CLI commands
| Command | What it does |
|---|---|
| `bun run start wallet-create` | Generate a new Tempo wallet (won't overwrite an existing one) |
| `bun run start balance` | Show wallet address, USDC.e balance, bridge link |
| `bun run start price <asset>` | Token price — accepts name, symbol, or contract address |
| `bun run start wallet <addr>` | Wallet portfolio positions |
| `bun run start lighthouse` | Trending tokens |
| `bun run start call <path> k=v…` | Generic call to any `/api/2/*` endpoint |
Examples:
```bash
bun run start price bitcoin
bun run start price 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
bun run start wallet 0xd04b77bb40944110ec9c9e3165f67dadf9d52f21
bun run start lighthouse
bun run start call /api/2/wallet/activity wallet=0xabc...
```
## Programmatic use (inside an agent)
Import directly from your TypeScript code — useful when each end-user has their own wallet (e.g. a Telegram bot where each Telegram ID gets a per-user encrypted wallet).
### Per-user wallets (multi-tenant agents)
```ts
import { createUserWallet, getUserWalletAddress } from "mpp-skill/src/wallet";
import { userMobulaCall, getUserTempoBalance } from "mpp-skill/src/mpp/user-mpp";
// On first interaction with a Telegram user
const userId = 1162998296; // Telegram numeric ID
let address = await getUserWalletAddress(userId);
if (!address) {
const w = await createUserWallet(userId);
address = w.address;
// Tell the user to fund it:
// https://relay.link/bridge/tempo?toAddress=address
}
// Check funds
const bal = await getUserTempoBalance(userId);
console.log(`User has $bal?.usd ?? 0 USDC.e on Tempo`);
// Make an API call (paid from the user's own wallet)
const price = await userMobulaCall(userId, "/api/2/token/price", { asset: "bitcoin" });
```
Per-user wallets are stored at `.claude/claudeclaw/wallets/{userId}.json`, AES-256-GCM encrypted with a key derived from `HMAC-SHA256(WALLET_SECRET, userId)`. The secret is auto-generated once at `.claude/claudeclaw/wallet.secret` (chmod 600). Different users cannot decrypt each other's keys.
### Direct low-level call
If you already have a private key and want full control:
```ts
import { tempoFetch } from "mpp-skill/src/mpp/tempo-client";
import type { Hex } from "viem";
const data = await tempoFetch(
"/api/2/token/price",
{ asset: "ethereum" },
process.env.TEMPO_PRIVATE_KEY as Hex,
);
```
## How the payment works (under the hood)
1. Agent calls `GET https://mpp.mobula.io/api/2/token/price?asset=bitcoin` with no auth.
2. Server returns **HTTP 402** with `WWW-Authenticate: Payment id="…", realm="mpp.mobula.io", method="tempo", request="<base64-json>"`. The decoded request specifies `amount` (in USDC.e atoms), `currency: USDC.e`, `recipient`, `methodDetails.chainId: 4217`.
3. Skill signs and broadcasts `transferWithMemo(recipient, amount, challengeId)` on USDC.e (`0x20c000000000000000000000b9537d11c60e8b50`) on Tempo.
4. Skill retries the same request with `Authorization: Payment <base64url(credential)>`, where `credential` references the tx hash.
5. Server validates the tx and returns the data.
The skill does steps 2–4 automatically — agents only see the data response.
## Key facts
- **Chain**: Tempo (chainId `4217`, RPC `https://rpc.tempo.xyz`)
- **Token**: USDC.e (`0x20c000000000000000000000b9537d11c60e8b50`)
- **Gas**: paid in USDC (Tempo uses USDC as native gas token — no separate ETH needed)
- **Cost per call**: ~$0.0004 typical, never above the amount specified in the 402 challenge
- **Bridge**: https://relay.link/bridge/tempo — bridges from Base, Ethereum, Arbitrum, etc.
## Common errors
| Error | Cause | Fix |
|---|---|---|
| `No wallet found` | First-time setup not done | `bun run start wallet-create` |
| `Insufficient Tempo balance: you have $0.0000…` | Wallet not funded yet | Bridge USDC.e via the printed link |
| `Could not parse challenge from: …` | Server didn't return a Tempo `WWW-Authenticate` header | Likely an upstream issue — retry or report |
| `Tempo tx failed: …` | RPC error or revert on-chain | Check balance and `https://explorer.tempo.xyz` for the address |
## When to use this vs a subscription
Mobula's subscription endpoints (`/agent/x402/subscribe`, etc.) are **not currently configured** in production. Until they are, this pay-per-call flow is the only way to call MPP API endpoints from an agent. The CLI returns a clear error if you try the legacy `subscribe` / `status` / `topup` / `key-create` commands.
FILE:README.md
# mpp-skill
Pay-as-you-go Mobula API skill for ClaudeHub / OpenClaw agents.
Fetch crypto prices, wallet positions, and market data without any signup, API key, or subscription. Each call costs ~$0.0004 in USDC.e on the Tempo chain (chainId 4217), settled directly from a wallet you control.
## Quick start
```bash
git clone https://github.com/Flotapponnier/mpp-skill.git
cd mpp-skill
bun install
# 1. Create a Tempo wallet (saved at ~/.mpp-skill/wallet.json, chmod 600)
bun run start wallet-create
# 2. Fund it with USDC.e via the bridge link printed by step 1
# https://relay.link/bridge/tempo?toAddress=<your-address>
# 3. Make calls
bun run start price bitcoin
bun run start wallet 0xd04b77bb40944110ec9c9e3165f67dadf9d52f21
bun run start lighthouse
```
That's it — no API key, no signup, no subscription.
## How it works
When you hit `https://mpp.mobula.io/api/2/*` Mobula returns HTTP 402 with a payment challenge. This skill:
1. Decodes the challenge,
2. Signs and broadcasts `transferWithMemo` on USDC.e (Tempo, chainId 4217) from your wallet,
3. Retries the request with the resulting tx hash as a payment credential,
4. Returns the data.
Tempo's gas is USDC (no separate ETH required), so a few dollars of USDC.e last for thousands of calls.
## Use as an agent skill
```bash
cd ~/your-agent/skills/
git clone https://github.com/Flotapponnier/mpp-skill.git mpp
cd mpp && bun install
```
Then from your agent code:
```ts
// Per-user encrypted wallets (one wallet per Telegram user, etc.)
import { createUserWallet, getUserWalletAddress } from "mpp-skill/src/wallet";
import { userMobulaCall } from "mpp-skill/src/mpp/user-mpp";
const userId = 1162998296;
if (!(await getUserWalletAddress(userId))) await createUserWallet(userId);
const price = await userMobulaCall(userId, "/api/2/token/price", { asset: "bitcoin" });
```
See [SKILL.md](./SKILL.md) for the full agent-facing docs, payment flow internals, and the per-call vs subscription decision.
## Project structure
```
mpp-skill/
├── src/
│ ├── index.ts # CLI entry
│ ├── cli-wallet.ts # Single hot wallet for the CLI (~/.mpp-skill/wallet.json)
│ ├── wallet.ts # Per-user encrypted wallets (for multi-tenant agents)
│ ├── commands/
│ │ └── mpp.ts # CLI command handler
│ └── mpp/
│ ├── tempo-client.ts # 402 → sign → retry flow on Tempo USDC.e
│ └── user-mpp.ts # Helpers tying per-user wallets to tempoFetch
├── SKILL.md
├── package.json
└── README.md
```
## Requirements
- Bun ≥ 1.0.0
- viem (auto-installed via `bun install`)
## License
MIT
FILE:package.json
{
"name": "mpp-skill",
"version": "2.0.0",
"description": "Machine Payments Protocol (MPP) skill for ClaudeHub/OpenClaw agents — pay-as-you-go Mobula API calls via Tempo",
"type": "module",
"main": "src/index.ts",
"bin": {
"mpp": "./src/index.ts"
},
"scripts": {
"start": "bun src/index.ts"
},
"keywords": [
"mpp",
"mobula",
"tempo",
"x402",
"claudehub",
"openclaw",
"agent",
"skill"
],
"author": "Florent Tapponnier",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Flotapponnier/mpp-skill.git"
},
"dependencies": {
"viem": "^2.48.4"
},
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"bun": ">=1.0.0"
}
}
FILE:src/wallet.ts
/**
* Secure per-user wallet management.
*
* Each user gets an isolated wallet file at:
* .claude/claudeclaw/wallets/{userId}.json
*
* The private key is AES-256-GCM encrypted with a key derived from:
* HMAC-SHA256(WALLET_SECRET, userId)
* where WALLET_SECRET is a server-side secret loaded from env or generated once.
*
* No other user can decrypt another user's private key.
*/
import { join } from "node:path";
import { mkdir } from "node:fs/promises";
import { createHmac, createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
import { execSync } from "node:child_process";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const WALLETS_DIR = join(process.cwd(), ".claude", "claudeclaw", "wallets");
const SECRET_FILE = join(process.cwd(), ".claude", "claudeclaw", "wallet.secret");
let _secret: Buffer | null = null;
async function getSecret(): Promise<Buffer> {
if (_secret) return _secret;
const f = Bun.file(SECRET_FILE);
if (await f.exists()) {
_secret = Buffer.from(await f.text(), "hex");
} else {
_secret = randomBytes(32);
await Bun.write(SECRET_FILE, _secret.toString("hex"));
// Restrict permissions
try { execSync(`chmod 600 "SECRET_FILE"`); } catch {}
}
return _secret;
}
function deriveKey(secret: Buffer, userId: number): Buffer {
return createHmac("sha256", secret).update(String(userId)).digest();
}
function encrypt(key: Buffer, plaintext: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
// Format: iv(24hex) + tag(32hex) + ciphertext(hex)
return iv.toString("hex") + tag.toString("hex") + enc.toString("hex");
}
function decrypt(key: Buffer, data: string): string {
const iv = Buffer.from(data.slice(0, 24), "hex");
const tag = Buffer.from(data.slice(24, 56), "hex");
const enc = Buffer.from(data.slice(56), "hex");
const decipher = createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
}
function walletPath(userId: number): string {
return join(WALLETS_DIR, `userId.json`);
}
export interface UserWallet {
address: string;
createdAt: string;
}
/** Generate a new wallet for a user using viem (correct keccak256 derivation). */
function generateWallet(): { address: string; privateKey: string } {
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
return { address: account.address, privateKey };
}
/** Create a wallet for a user. Throws if one already exists. */
export async function createUserWallet(userId: number): Promise<UserWallet> {
await mkdir(WALLETS_DIR, { recursive: true });
const path = walletPath(userId);
if (await Bun.file(path).exists()) {
throw new Error("Wallet already exists for this user");
}
const secret = await getSecret();
const key = deriveKey(secret, userId);
const { address, privateKey } = generateWallet();
const record = {
address,
encryptedKey: encrypt(key, privateKey),
createdAt: new Date().toISOString(),
};
await Bun.write(path, JSON.stringify(record, null, 2));
try { execSync(`chmod 600 "path"`); } catch {}
return { address, createdAt: record.createdAt };
}
/** Get a user's wallet address (public info only). Returns null if no wallet. */
export async function getUserWalletAddress(userId: number): Promise<string | null> {
const path = walletPath(userId);
if (!await Bun.file(path).exists()) return null;
const record = JSON.parse(await Bun.file(path).text());
return record.address ?? null;
}
/** Get the decrypted private key for a user's wallet. Only call for the user's own operations. */
export async function getUserPrivateKey(userId: number): Promise<string | null> {
const path = walletPath(userId);
if (!await Bun.file(path).exists()) return null;
const secret = await getSecret();
const key = deriveKey(secret, userId);
const record = JSON.parse(await Bun.file(path).text());
return decrypt(key, record.encryptedKey);
}
FILE:src/cli-wallet.ts
/**
* Standalone CLI wallet for the `mpp` command.
*
* Stored at ~/.mpp-skill/wallet.json (chmod 600).
* Plain JSON — this is a hot wallet on the user's own machine,
* intended to hold a few dollars of USDC.e for pay-as-you-go API calls.
*
* For per-user wallets in a multi-tenant agent (e.g. Telegram bot),
* use the encrypted helpers in `./wallet.ts` keyed by user ID.
*/
import { join } from "node:path";
import { homedir } from "node:os";
import { mkdir, chmod } from "node:fs/promises";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";
export const CLI_WALLET_DIR = join(homedir(), ".mpp-skill");
export const CLI_WALLET_FILE = join(CLI_WALLET_DIR, "wallet.json");
export interface CliWallet {
address: string;
privateKey: Hex;
createdAt: string;
}
export async function loadCliWallet(): Promise<CliWallet | null> {
const f = Bun.file(CLI_WALLET_FILE);
if (!(await f.exists())) return null;
return JSON.parse(await f.text()) as CliWallet;
}
export async function createCliWallet(): Promise<CliWallet> {
if (await Bun.file(CLI_WALLET_FILE).exists()) {
throw new Error(
`Wallet already exists at CLI_WALLET_FILE. Delete it first to regenerate.`
);
}
await mkdir(CLI_WALLET_DIR, { recursive: true });
const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);
const record: CliWallet = {
address: account.address,
privateKey,
createdAt: new Date().toISOString(),
};
await Bun.write(CLI_WALLET_FILE, JSON.stringify(record, null, 2));
try {
await chmod(CLI_WALLET_FILE, 0o600);
} catch {}
return record;
}
export async function requireCliWallet(): Promise<CliWallet> {
const w = await loadCliWallet();
if (!w) {
console.error("No wallet found. Run: bun run start wallet-create");
process.exit(1);
}
return w;
}
FILE:src/index.ts
#!/usr/bin/env bun
/**
* MPP (Machine Payments Protocol) CLI
* Manage Mobula API subscriptions and fetch market data
*/
import { mppCommand } from "./commands/mpp";
const args = process.argv.slice(2);
if (args.length === 0) {
mppCommand(["help"]);
} else {
mppCommand(args);
}
FILE:src/commands/mpp.ts
/**
* MPP (Machine Payments Protocol) CLI.
*
* Pay-as-you-go Mobula API calls via Tempo (chainId 4217) USDC.e.
* Each call costs ~$0.0004 and is settled directly from your wallet —
* no subscription, no API key, no signup.
*/
import { tempoFetch, getTempoBalance, BRIDGE_URL } from "../mpp/tempo-client";
import {
CLI_WALLET_FILE,
createCliWallet,
loadCliWallet,
requireCliWallet,
} from "../cli-wallet";
function bridgeUrl(address: string): string {
return `BRIDGE_URL?toAddress=address`;
}
export async function mppCommand(args: string[]): Promise<void> {
const [subcommand, ...rest] = args;
try {
switch (subcommand) {
case "wallet-create": {
const w = await createCliWallet();
console.log("✓ Wallet created");
console.log(`Address: w.address`);
console.log(`Saved to: CLI_WALLET_FILE`);
console.log("\nNext: fund it with USDC.e on Tempo (chainId 4217):");
console.log(` bridgeUrl(w.address)`);
console.log("\nAny amount works — calls cost ~$0.0004 each.");
break;
}
case "wallet-info":
case "balance": {
const w = await requireCliWallet();
let usd = "?";
try {
const raw = await getTempoBalance(w.address);
usd = (Number(raw) / 1_000_000).toFixed(4);
} catch (e: any) {
console.error("Could not fetch on-chain balance:", e.message);
}
console.log(`Address: w.address`);
console.log(`USDC.e: $usd (Tempo, chainId 4217)`);
console.log(`Bridge: bridgeUrl(w.address)`);
break;
}
case "price": {
const [asset] = rest;
if (!asset) {
console.error("Usage: bun run start price <asset>");
console.error("Example: bun run start price bitcoin");
process.exit(1);
}
const w = await requireCliWallet();
const data = await tempoFetch(
"/api/2/token/price",
{ asset },
w.privateKey,
);
console.log(JSON.stringify(data, null, 2));
break;
}
case "wallet": {
const [address] = rest;
if (!address) {
console.error("Usage: bun run start wallet <address>");
process.exit(1);
}
const w = await requireCliWallet();
const data = await tempoFetch(
"/api/2/wallet/positions",
{ wallet: address },
w.privateKey,
);
console.log(JSON.stringify(data, null, 2));
break;
}
case "lighthouse": {
const w = await requireCliWallet();
const data = await tempoFetch(
"/api/2/market/lighthouse",
{},
w.privateKey,
);
console.log(JSON.stringify(data, null, 2));
break;
}
case "call": {
const [path, ...kvs] = rest;
if (!path) {
console.error('Usage: bun run start call <path> [key=value ...]');
console.error('Example: bun run start call /api/2/wallet/activity wallet=0xabc');
process.exit(1);
}
const params: Record<string, string> = {};
for (const kv of kvs) {
const idx = kv.indexOf("=");
if (idx > 0) params[kv.slice(0, idx)] = kv.slice(idx + 1);
}
const w = await requireCliWallet();
const data = await tempoFetch(path, params, w.privateKey);
console.log(JSON.stringify(data, null, 2));
break;
}
case "subscribe":
case "status":
case "topup":
case "key-create":
case "key-revoke": {
console.error(
`The "subcommand" command is no longer supported.`,
);
console.error(
`Mobula MPP currently only ships pay-as-you-go via Tempo (USDC.e).`,
);
console.error("");
console.error("To get started:");
console.error(" 1. bun run start wallet-create");
console.error(
` 2. fund the wallet at BRIDGE_URL?toAddress=<your-addr>`,
);
console.error(" 3. bun run start price bitcoin");
process.exit(1);
break;
}
case "help":
case undefined:
default: {
const wallet = await loadCliWallet();
console.log(`
MPP — Pay-as-you-go Mobula API client (Tempo / USDC.e)
Setup:
bun run start wallet-create Generate a Tempo hot wallet
bun run start balance Show address + USDC.e balance + bridge link
Data calls (each costs ~$0.0004 from your USDC.e balance):
bun run start price <asset> Token price (e.g. bitcoin, ethereum, 0x...)
bun run start wallet <address> Wallet positions
bun run start lighthouse Trending tokens
bun run start call <path> k=v.. Generic call (any /api/2/* endpoint)
Removed (subscription endpoints not available):
subscribe, status, topup, key-create, key-revoke
Wallet location:
CLI_WALLET_FILE${wallet.address` : " (not yet created)"}
Docs: https://github.com/Flotapponnier/mpp-skill
`.trim());
break;
}
}
} catch (error: any) {
console.error("\n❌ Error:", error.message);
process.exit(1);
}
}
FILE:src/mpp/tempo-client.ts
/**
* Tempo MPP client — per-call payments using user's own wallet.
*
* Flow:
* 1. Call Mobula endpoint → 402 with WWW-Authenticate: Payment header
* 2. Parse challenge (id, request: {amount, currency, recipient})
* 3. Sign + broadcast transferWithMemo(recipient, amount, challengeId) on Tempo
* 4. Retry with Authorization: Payment <base64url(credential)>
* 5. Get 200 response
*
* Token: USDC.e on Tempo (chainId 4217)
* Contract: 0x20c000000000000000000000b9537d11c60e8b50
* Function: transferWithMemo(address to, uint256 amount, bytes32 memo)
*/
import {
createWalletClient,
createPublicClient,
http,
parseAbi,
type Hex,
encodeFunctionData,
keccak256,
toBytes,
toHex,
hexToBytes,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
// Tempo mainnet
const TEMPO_CHAIN = {
id: 4217,
name: "Tempo",
nativeCurrency: { name: "USD", symbol: "USD", decimals: 6 },
rpcUrls: { default: { http: ["https://rpc.tempo.xyz"] } },
} as const;
// USDC.e on Tempo
export const USDC_E = "0x20c000000000000000000000b9537d11c60e8b50" as const;
// Public bridge UI for moving funds onto Tempo
export const BRIDGE_URL = "https://relay.link/bridge/tempo";
const USDC_ABI = parseAbi([
"function transferWithMemo(address to, uint256 amount, bytes32 memo) external returns (bool)",
"function balanceOf(address account) external view returns (uint256)",
]);
const MOBULA_BASE_URL = "https://mpp.mobula.io";
interface MppChallenge {
id: string;
amount: string;
currency: string;
recipient: string;
chainId: number;
expires: string;
}
function parseWwwAuthenticate(header: string): MppChallenge | null {
try {
// Extract fields from: Payment id="...", realm="...", method="tempo", request="base64...", expires="..."
const idMatch = header.match(/\bid="([^"]+)"/);
const requestMatch = header.match(/\brequest="([^"]+)"/);
const expiresMatch = header.match(/\bexpires="([^"]+)"/);
if (!idMatch || !requestMatch) return null;
const id = idMatch[1];
const expires = expiresMatch?.[1] ?? "";
// Decode base64url request
const requestJson = Buffer.from(requestMatch[1], "base64").toString("utf8");
const request = JSON.parse(requestJson);
return {
id,
amount: request.amount,
currency: request.currency,
recipient: request.recipient,
chainId: request.methodDetails?.chainId ?? 4217,
expires,
};
} catch {
return null;
}
}
function challengeIdToBytes32(challengeId: string): Hex {
// Encode challengeId as bytes32 — right-pad with zeros if < 32 bytes
const bytes = Buffer.from(challengeId, "utf8");
const padded = Buffer.alloc(32);
bytes.copy(padded, 0, 0, Math.min(bytes.length, 32));
return `0xpadded.toString("hex")`;
}
function buildCredential(challenge: MppChallenge, txHash: Hex, walletAddress: string): string {
const credential = {
challenge: {
id: challenge.id,
realm: "mpp.mobula.io",
method: "tempo",
intent: "charge",
},
payload: {
type: "hash",
hash: txHash,
},
source: `did:pkh:eip155:challenge.chainId:walletAddress`,
};
return Buffer.from(JSON.stringify(credential)).toString("base64url");
}
/**
* Get USDC.e balance on Tempo for an address.
*/
export async function getTempoBalance(address: string): Promise<bigint> {
const client = createPublicClient({
chain: TEMPO_CHAIN,
transport: http(),
});
return client.readContract({
address: USDC_E,
abi: USDC_ABI,
functionName: "balanceOf",
args: [address as Hex],
});
}
/**
* Make a Mobula API call, paying per-call via MPP/Tempo with the user's wallet.
*
* @param path - e.g. "/api/2/token/price"
* @param params - query params
* @param privateKeyHex - user's private key (0x...)
*/
export async function tempoFetch(
path: string,
params: Record<string, string>,
privateKeyHex: Hex
): Promise<unknown> {
const url = new URL(`MOBULA_BASE_URLpath`);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
// First attempt — no auth
const res1 = await fetch(url.toString());
if (res1.ok) return res1.json();
if (res1.status !== 402) {
throw new Error(`Mobula path: res1.status await res1.text()`);
}
// Parse 402 challenge
const wwwAuth = res1.headers.get("www-authenticate");
if (!wwwAuth) throw new Error("402 but no WWW-Authenticate header");
const challenge = parseWwwAuthenticate(wwwAuth);
if (!challenge) throw new Error(`Could not parse challenge from: wwwAuth`);
if (challenge.chainId !== 4217) {
throw new Error(`Unexpected chain in challenge: challenge.chainId (expected Tempo 4217)`);
}
// Sign and broadcast transferWithMemo
const account = privateKeyToAccount(privateKeyHex);
// Check balance before attempting tx
const balance = await getTempoBalance(account.address);
const required = BigInt(challenge.amount);
if (balance < required) {
const balanceUsd = (Number(balance) / 1_000_000).toFixed(4);
const requiredUsd = (Number(required) / 1_000_000).toFixed(4);
throw new Error(
`Insufficient Tempo balance: you have $balanceUsd USDC.e, need $requiredUsd.\n` +
`Fund your wallet at: https://relay.link/bridge/tempo?toAddress=account.address`
);
}
const walletClient = createWalletClient({
account,
chain: TEMPO_CHAIN,
transport: http(),
});
const memo = challengeIdToBytes32(challenge.id);
let txHash: Hex;
try {
txHash = await walletClient.writeContract({
address: USDC_E,
abi: USDC_ABI,
functionName: "transferWithMemo",
args: [challenge.recipient as Hex, BigInt(challenge.amount), memo],
});
} catch (err: any) {
throw new Error(`Tempo tx failed: err.message ?? err`);
}
// Build credential and retry
const credential = buildCredential(challenge, txHash, account.address);
const res2 = await fetch(url.toString(), {
headers: { Authorization: `Payment credential` },
});
if (!res2.ok) {
const body = await res2.text();
throw new Error(`Mobula payment rejected: res2.status body`);
}
return res2.json();
}
FILE:src/mpp/user-mpp.ts
/**
* Per-user Mobula API calls via MPP/Tempo pay-as-you-go.
*
* Each user pays $0.0004–$0.002 per call from their own Tempo wallet.
* No subscription needed — pure per-call.
*/
import { getUserPrivateKey, getUserWalletAddress } from "../wallet";
import { tempoFetch, getTempoBalance } from "./tempo-client";
import type { Hex } from "viem";
const MOBULA_BASE_URL = "https://mpp.mobula.io";
/**
* Call a Mobula data endpoint using the user's own Tempo wallet.
* Pays ~$0.0004 per call directly from their USDC.e balance.
*/
export async function userMobulaCall(
telegramUserId: number,
path: string,
params: Record<string, string> = {}
): Promise<unknown> {
const privateKey = await getUserPrivateKey(telegramUserId);
if (!privateKey) throw new Error("No wallet found. Create one first with /start.");
return tempoFetch(path, params, privateKey as Hex);
}
/**
* Get user's USDC.e balance on Tempo (in human-readable format).
* Returns null if no wallet.
*/
export async function getUserTempoBalance(telegramUserId: number): Promise<{ raw: bigint; usd: string } | null> {
const address = await getUserWalletAddress(telegramUserId);
if (!address) return null;
try {
const raw = await getTempoBalance(address);
const usd = (Number(raw) / 1_000_000).toFixed(4);
return { raw, usd };
} catch {
return null;
}
}
/**
* Fetch token price for a given contract address + blockchain.
*/
export async function getUserTokenPrice(
telegramUserId: number,
address: string,
blockchain: string
): Promise<unknown> {
return userMobulaCall(telegramUserId, "/api/2/token/price", { address, blockchain });
}
/**
* Fetch wallet portfolio positions.
*/
export async function getUserWalletPositions(
telegramUserId: number,
wallet: string
): Promise<unknown> {
return userMobulaCall(telegramUserId, "/api/2/wallet/positions", { wallet });
}
/**
* Fetch wallet activity / transactions.
*/
export async function getUserWalletActivity(
telegramUserId: number,
wallet: string
): Promise<unknown> {
return userMobulaCall(telegramUserId, "/api/2/wallet/activity", { wallet });
}
24/7 autonomous monitoring for crypto portfolios, whales, and market conditions. Multi-condition alerts via OpenClaw heartbeat.
---
name: mobula-alerts
displayName: Mobula - Smart Alerts & Monitoring
description: 24/7 autonomous monitoring for crypto portfolios, whales, and market conditions. Multi-condition alerts via OpenClaw heartbeat.
version: 1.0.0
author: Mobula
category: crypto
tags:
- crypto
- alerts
- monitoring
- automation
- heartbeat
requiredEnvVars:
- MOBULA_API_KEY
homepage: https://mobula.io
docs: https://docs.mobula.io
repository: https://github.com/Flotapponnier/Crypto-date-openclaw
---
# Mobula - Smart Alerts & Monitoring
24/7 autonomous crypto monitoring using OpenClaw's heartbeat system. Set conditions once, let your agent monitor continuously and alert you on Telegram/WhatsApp/Discord.
## Quick Start
**1. Get your free API key** (no credit card required)
- Go to [mobula.io](https://mobula.io) and sign up
- Free tier: 100 requests/minute
- Copy your API key from the dashboard
**2. Set your API key**
```bash
export MOBULA_API_KEY="your_api_key_here"
```
**3. Set up monitoring**
Ask your agent:
- "Alert me if BTC moves more than 5% in 1 hour"
- "Monitor my wallet and alert if any token drops 15%"
- "Watch these whale wallets and alert on large buys"
- "Find new tokens on Base matching my criteria every 6 hours"
- "Send me a market brief every morning at 7am"
---
## Security
✅ **Read-only access** - No trading, no transactions
✅ **No private keys** - Only public blockchain data
✅ **Open source** - [View code on GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
✅ **VirusTotal verified** - Benign scan results
---
## How It Works
Unlike ChatGPT/Claude chat, your OpenClaw agent runs continuously. The heartbeat system (~30min intervals) allows your agent to:
1. **Check conditions** you've set (price thresholds, portfolio changes, whale movements)
2. **Fetch data** from Mobula API when needed
3. **Analyze** using multi-condition logic (not just "price > X")
4. **Alert** you on Telegram/WhatsApp/Discord only when conditions are met
This skill provides **monitoring patterns** using Mobula endpoints. Combine with **mobula-prices** and **mobula-wallet** skills for complete functionality.
---
## Monitoring Patterns
### Pattern 1: Portfolio Guardian
Monitor your wallet 24/7 for concentration risks and drops.
**Setup:**
> "Monitor my wallet 0x... and alert me on Telegram if any token drops more than 15% or my allocation exceeds 40% on one asset. Daily summary at 9am."
**How it works:**
Every heartbeat (~30min):
1. Fetch portfolio via `mobula_wallet_portfolio`
2. Calculate allocation percentages
3. Check conditions:
- Any token >40% of portfolio → suggest rebalancing
- Any token down >15% in 24h → alert with context
- Total portfolio changed >10% → notify
4. Store previous values in memory to detect changes
5. Send daily summary at user's preferred time
**Example alert:**
```
⚠️ Portfolio Alert
Your ETH allocation is now 47% (was 38% yesterday).
Consider rebalancing to reduce concentration risk.
Current holdings:
- ETH: $12,340 (47%)
- BRETT: $8,200 (31%) up 12% 24h
- DEGEN: $5,890 (22%) down 3% 24h
```
---
### Pattern 2: Whale Watching
Track smart money and get alerted when whales move.
**Setup:**
> "Watch wallets 0xabc, 0xdef, 0x123. Alert if any buy/sell more than $50K. If multiple whales buy the same token within 6h, priority alert with full analysis."
**How it works:**
Every heartbeat:
1. Check transactions via `mobula_wallet_transactions`
2. If new significant transaction (>$50K):
- Get token details via `mobula_market_data`
- Check recent trades via `mobula_market_trades`
- Cross-check if other tracked whales bought same token
3. If multiple whales buying → **priority alert**
**Example alert:**
```
🚨 Whale Alert - HIGH PRIORITY
Wallet 0x742d... bought $150K of BRETT on Base (2h ago)
Token: BRETT | Price: $0.089 (up 23% 24h)
Mcap: $2.3M | Volume: $1.2M (8x normal)
Cross-signal: 2 other tracked whales also bought BRETT:
- 0x888... bought $80K (4h ago)
- 0x111... bought $120K (1h ago)
⚠️ Possible coordinated accumulation.
```
---
### Pattern 3: Token Scout (Autonomous Discovery)
Agent autonomously finds new tokens matching your criteria.
**Setup:**
> "Find tokens on Base/Arbitrum every 6h: mcap under $5M, liquidity over $100K, volume up 50%+ 24h, verified contract. Send top 3 with analysis."
**How it works:**
Every 4-6 hours on heartbeat:
1. User defines criteria (stored in memory):
- Chains: Base, Arbitrum
- Market cap: <$5M
- Liquidity: >$100K
- Volume change 24h: >+50%
2. Search/filter tokens via `mobula_market_data`
3. For each match:
- Get 7-day history via `mobula_market_history`
- Check metadata via `mobula_metadata`
- Calculate risk score
4. Send top 3 with analysis
**Example alert:**
```
🔍 Token Scout - 3 New Matches
1. BOOP on Base
- Price: $0.0042 (up 156% 24h, up 340% 7d)
- Mcap: $2.1M | Liquidity: $280K
- Volume: $890K (12x average)
- Contract: Verified ✅
- Risk: Medium
2. ZORP on Arbitrum
- Price: $0.0089 (up 78% 24h)
- Mcap: $3.4M | Liquidity: $450K
- Whale buy: $60K (3h ago)
- Risk: Medium-High
3. [...]
```
---
### Pattern 4: Smart Price Alerts
Contextual alerts with multi-condition logic (not just "price > X").
**Setup:**
> "Alert me if BTC moves more than 5% in 1 hour, BUT only if volume is 2x above 24h average. Real moves, not noise."
**How it works:**
Every heartbeat:
1. Get current price and volume via `mobula_market_data`
2. Compare to price from 1 hour ago (stored in memory)
3. Check if volume condition met
4. Only alert if **BOTH** conditions true
**Other smart alert examples:**
- "Alert if ETH breaks $4K after consolidating $3,800-$3,950 for 3+ days"
- "Alert if any of my watchlist tokens pump >20% with volume >5x average"
- "Alert if BTC drops >3% but ETH is green (divergence signal)"
---
### Pattern 5: Market Brief
Automated morning/evening market overviews.
**Setup:**
> "Send market overview at 7am and 7pm on Telegram. Include BTC, ETH, SOL, and any top 100 token that moved more than 10%."
**How it works:**
Every scheduled time (7am, 7pm):
1. Check major tokens (BTC, ETH, SOL, BNB) via `mobula_market_multi`
2. Identify major moves (>5% in 24h)
3. Check volume leaders
4. Provide concise overview
**Example brief:**
```
📊 Crypto Market Brief - Feb 20, 7:00 AM
Majors:
- BTC: $67,234 (up 2.1% 24h)
- ETH: $3,456 (up 4.3% 24h)
- SOL: $123.45 (down 1.2% 24h)
Big Movers (24h):
- PEPE: up 23%
- ARB: up 15%
- AVAX: down 12%
Sentiment: Cautiously bullish. ETH leading, memes pumping.
```
---
## Setting Up Heartbeat Monitoring
**Step 1: Tell your agent what to monitor**
Be specific about:
- What to track (wallets, tokens, conditions)
- When to alert (thresholds, multi-condition logic)
- How often to check (30min, 1h, 6h)
- Where to alert (Telegram, Discord, WhatsApp)
**Step 2: Agent stores config in memory**
Your agent remembers:
- Monitoring tasks
- Previous values (for change detection)
- Alert preferences
- Scheduling
**Step 3: Agent monitors on heartbeat**
Every heartbeat interval:
- Fetches only necessary data (respects rate limits)
- Compares to previous values
- Evaluates conditions
- Sends alerts when triggered
---
## Combining Endpoints for Rich Analysis
### Full Token Analysis
User asks: "Should I buy this token?"
1. `mobula_market_data` → current price, mcap, volume, liquidity
2. `mobula_market_history` → 7d and 30d trend
3. `mobula_market_trades` → recent activity (accumulation or distribution?)
4. `mobula_metadata` → project info, socials, verification
**Response format:**
```
TOKEN Analysis
Price: $0.042 (down 8% 24h, up 156% 7d)
- Near 7d high, down from $0.051
- Still up 400% from 30d low
Fundamentals:
- Mcap: $2.1M (small cap, high risk/reward)
- Liquidity: $280K (decent for size)
- Volume: $1.2M 24h (healthy)
On-chain:
- Recent trades show buying pressure (3:1 buy/sell ratio)
- 2 large buys ($50K+) in last 2h
Project:
- Contract verified ✅
- Active Twitter (12K followers)
Risk: Small cap, high volatility. Don't ape more than you can lose.
```
---
## Authentication
All requests require your API key in the `Authorization` header:
```
Authorization: MOBULA_API_KEY
```
**If authentication fails (401/403):**
- Verify: `echo $MOBULA_API_KEY`
- Regenerate at [mobula.io](https://mobula.io) if expired
- Check rate limits (100 req/min free tier)
---
## Rate Limits & Best Practices
### Respect Rate Limits
- **Free tier:** 100 requests/minute
- Use `mobula_market_multi` for batch queries
- Cache data in memory (metadata doesn't change often)
### Efficient Heartbeat Usage
- Don't call every endpoint on every heartbeat
- Only fetch what's needed based on active monitoring
- Batch requests when possible
- Store previous values to detect changes
---
## Error Handling
**API Key Issues:**
- "I need a Mobula API key. Get one free at https://mobula.io then set it: `export MOBULA_API_KEY='your_key'`"
- "Invalid API key. Check it at https://mobula.io"
- "Rate limit hit. Upgrade at https://mobula.io or retry later"
**Monitoring Setup:**
- "What conditions should I monitor for?"
- "How often should I check? (30min, 1h, 6h)"
- "Where should I send alerts? (Telegram, Discord, WhatsApp)"
---
## When to Use This Skill
**✅ USE WHEN the user:**
- Wants to set up alerts or monitoring
- Asks for 24/7 tracking
- Wants autonomous discovery
- Needs scheduled reports or briefs
- Mentions "alert me when", "monitor", "track", "watch"
**❌ DON'T USE WHEN:**
- User wants one-time price check (use mobula-prices)
- User wants to check portfolio once (use mobula-wallet)
---
## Related Skills
- **mobula-prices** - Token prices and market data (required for alerts)
- **mobula-wallet** - Portfolio tracking (required for wallet monitoring)
---
## Ready-to-Use Templates
Copy these templates to `~/openclaw/heartbeat/`:
- **[`heartbeat-portfolio-guardian.md`](../../examples/heartbeat-portfolio-guardian.md)**
- **[`heartbeat-whale-tracker.md`](../../examples/heartbeat-whale-tracker.md)**
- **[`heartbeat-token-scout.md`](../../examples/heartbeat-token-scout.md)**
- **[`heartbeat-market-brief.md`](../../examples/heartbeat-market-brief.md)**
---
## Resources
- **Get API Key:** [mobula.io](https://mobula.io)
- **API Docs:** [docs.mobula.io](https://docs.mobula.io)
- **GitHub:** [Mobula OpenClaw Skills](https://github.com/Flotapponnier/Crypto-date-openclaw)
- **Support:** Open issue on GitHub
---
## Version History
**1.0.0** (2024-03-25): Initial release
- Portfolio guardian pattern
- Whale watching pattern
- Autonomous token scout
- Smart price alerts
- Market brief automation
- Heartbeat optimization tips
FILE:README.md
# Mobula Alerts - 24/7 Smart Crypto Monitoring
> 24/7 autonomous monitoring for crypto portfolios, whales, and market conditions. Multi-condition alerts via OpenClaw heartbeat.
[](https://clawhub.ai)
[](https://opensource.org/licenses/MIT)
## What This Skill Does
**mobula-alerts** transforms your OpenClaw agent into a 24/7 crypto monitoring system. Set conditions once, and your agent continuously monitors prices, portfolios, whales, and market conditions — alerting you only when something important happens.
### Key Features
✅ **Portfolio Guardian** - Alert on concentration risks, drops, allocation changes
✅ **Whale Watching** - Track smart money movements across wallets
✅ **Token Scout** - Autonomously discover new tokens matching your criteria
✅ **Smart Alerts** - Multi-condition logic (price + volume + on-chain signals)
✅ **Market Briefs** - Automated morning/evening summaries
### Perfect For
- Portfolio managers wanting 24/7 risk monitoring
- Traders tracking whale movements
- Alpha hunters discovering new tokens
- Anyone wanting proactive (not reactive) crypto monitoring
---
## How This Is Different
**ChatGPT/Claude chat:**
- You ask → it answers
- You need to remember to check
- No continuous monitoring
**OpenClaw + mobula-alerts:**
- Set conditions once → agent monitors 24/7
- Agent alerts you on Telegram/Discord/WhatsApp
- No prompting needed, it works autonomously
**Example:**
```
You: "Alert me if BTC moves >5% in 1h with volume 2x above average"
Agent: [Monitors every 30min via heartbeat]
[Only alerts when BOTH conditions met]
[You do other things while it watches]
```
---
## Quick Start (5 minutes)
### 1. Install All Required Skills
This skill orchestrates monitoring using data from:
- **[mobula-prices](../mobula-prices/)** - For price and market data
- **[mobula-wallet](../mobula-wallet/)** - For portfolio tracking
Install all 3:
```
Tell your agent: "Install mobula-prices, mobula-wallet, and mobula-alerts from ClawHub"
```
### 2. Get Your Free API Key
1. Go to [mobula.io](https://mobula.io)
2. Sign up (takes 30 seconds)
3. Copy your API key from the dashboard
**Free tier includes:**
- 100 requests/minute
- No credit card required
- Sufficient for most monitoring tasks
### 3. Set Your API Key
```bash
export MOBULA_API_KEY="your_api_key_here"
```
**Make it permanent:**
```bash
echo 'export MOBULA_API_KEY="your_key"' >> ~/.zshrc
source ~/.zshrc
```
### 4. Set Up Your First Alert
Ask your agent:
```
"Alert me if BTC moves more than 5% in 1 hour"
```
Your agent will:
1. Acknowledge the monitoring task
2. Store the condition in memory
3. Check every heartbeat (~30min)
4. Alert you only when the condition is met
---
## Monitoring Patterns
### Pattern 1: Portfolio Guardian
**What it does:** Monitors your wallet 24/7 for concentration risks and significant drops.
**Setup:**
```
"Monitor my wallet 0x... and alert me on Telegram if:
- Any token drops more than 15%
- My allocation exceeds 40% on one asset
- Total portfolio value changes by more than 10%
Send me a daily summary at 9am."
```
**How it works:**
- Every heartbeat (~30min):
- Fetches your portfolio
- Calculates allocation percentages
- Compares to previous values
- Alerts on threshold breaches
**Example alert:**
```
⚠️ Portfolio Alert
Your ETH allocation is now 47% (was 38% yesterday).
Consider rebalancing to reduce concentration risk.
Current holdings:
- ETH: $12,340 (47%)
- BRETT: $8,200 (31%) up 12% 24h
- DEGEN: $5,890 (22%) down 3% 24h
```
---
### Pattern 2: Whale Watching
**What it does:** Tracks smart money movements and alerts on coordinated activity.
**Setup:**
```
"Watch these whale wallets: 0xabc, 0xdef, 0x123
Alert me if:
- Any wallet buys/sells more than $50K
- Multiple whales buy the same token within 6h (priority alert)
- Any whale dumps a token I'm holding
```
**How it works:**
- Every heartbeat:
- Checks transactions for tracked wallets
- If significant trade (>$50K):
- Gets token details
- Checks if other whales bought same token
- Cross-references with your holdings
**Example alert:**
```
🚨 Whale Alert - HIGH PRIORITY
Wallet 0x742d... bought $150K of BRETT on Base (2h ago)
Token: BRETT | Price: $0.089 (up 23% 24h)
Mcap: $2.3M | Volume: $1.2M (8x normal)
Cross-signal: 2 other tracked whales also bought BRETT:
- 0x888... bought $80K (4h ago)
- 0x111... bought $120K (1h ago)
⚠️ Possible coordinated accumulation.
```
---
### Pattern 3: Token Scout
**What it does:** Autonomously discovers new tokens matching your criteria.
**Setup:**
```
"Find new tokens every 6 hours with these criteria:
- Chains: Base, Arbitrum
- Market cap: under $5M
- Liquidity: over $100K
- Volume: up 50%+ in 24h
- Contract: verified only
Send me top 3 matches with full analysis."
```
**How it works:**
- Every 6 hours (on heartbeat):
- Searches tokens matching criteria
- For each match:
- Gets 7-day price history
- Checks metadata and verification
- Calculates risk score
- Sends top 3 with analysis
**Example alert:**
```
🔍 Token Scout - 3 New Matches
1. BOOP on Base
- Price: $0.0042 (up 156% 24h, up 340% 7d)
- Mcap: $2.1M | Liquidity: $280K
- Volume: $890K (12x average)
- Contract: Verified ✅
- Risk: Medium
2. ZORP on Arbitrum
- Price: $0.0089 (up 78% 24h)
- Mcap: $3.4M | Liquidity: $450K
- Whale buy: $60K (3h ago)
- Risk: Medium-High
3. [...]
```
---
### Pattern 4: Smart Price Alerts
**What it does:** Multi-condition alerts (not just "price > X").
**Setup:**
```
"Alert me if BTC moves more than 5% in 1 hour,
BUT only if volume is 2x above 24h average.
I want real moves, not noise."
```
**Other examples:**
```
"Alert if ETH breaks $4K after consolidating
between $3,800-$3,950 for 3+ days"
"Alert if any of my watchlist tokens pump >20%
with volume >5x average"
"Alert if BTC drops >3% but ETH is green
(divergence signal)"
```
**How it works:**
- Every heartbeat:
- Fetches current data
- Compares to stored previous values
- Evaluates ALL conditions
- Only alerts if ALL conditions true
---
### Pattern 5: Market Brief
**What it does:** Automated morning/evening market summaries.
**Setup:**
```
"Send market overview at 7am and 7pm on Telegram.
Include:
- BTC, ETH, SOL performance
- Any top 100 token that moved >10%
- Biggest volume gainers
- Overall sentiment analysis
```
**Example brief:**
```
📊 Crypto Market Brief - Feb 20, 7:00 AM
Majors:
- BTC: $67,234 (up 2.1% 24h)
- ETH: $3,456 (up 4.3% 24h)
- SOL: $123.45 (down 1.2% 24h)
Big Movers (24h):
- PEPE: up 23%
- ARB: up 15%
- AVAX: down 12%
Sentiment: Cautiously bullish. ETH leading, memes pumping.
```
---
## Ready-to-Use Templates
Copy these heartbeat templates to `~/openclaw/heartbeat/`:
**Available templates:**
- [Portfolio Guardian](../../examples/heartbeat-portfolio-guardian.md)
- [Whale Tracker](../../examples/heartbeat-whale-tracker.md)
- [Token Scout](../../examples/heartbeat-token-scout.md)
- [Market Brief](../../examples/heartbeat-market-brief.md)
**How to use:**
1. Copy template to `~/openclaw/heartbeat/`
2. Edit wallet addresses, criteria, timing
3. Agent executes automatically on schedule
---
## Security
✅ **Read-only access** - No trading, no transactions
✅ **No private keys** - Only public blockchain data
✅ **Open source** - [View code on GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
✅ **VirusTotal verified** - Benign scan results
This skill only reads public data. It cannot:
- Execute trades
- Access wallets
- Sign transactions
- Transfer funds
---
## Rate Limits & Best Practices
**Free tier:** 100 requests/minute
**Optimization tips:**
- Set heartbeat to 30-60min intervals (not every minute)
- Use batch endpoints for multiple tokens
- Cache metadata (rarely changes)
- Only fetch data for active monitoring tasks
**Example efficient setup:**
- Portfolio checks: every 30min
- Whale watching: every 30min
- Token scout: every 6 hours
- Market brief: twice daily
---
## Troubleshooting
### Agent not monitoring
**Check:**
1. All 3 skills installed (mobula-prices, mobula-wallet, mobula-alerts)
2. API key set: `echo $MOBULA_API_KEY`
3. Agent restarted: `openclaw restart`
4. Monitoring task confirmed by agent
### Alerts not firing
**Possible causes:**
- Conditions not met (verify thresholds are reasonable)
- Rate limit hit (reduce monitoring frequency)
- Agent not running (check `openclaw status`)
### Too many alerts
**Adjust conditions:**
- Increase thresholds (5% → 10%)
- Add multi-condition logic (price + volume)
- Reduce monitoring frequency
---
## Related Skills
This skill requires:
- **[mobula-prices](../mobula-prices/)** - For price and market data
- **[mobula-wallet](../mobula-wallet/)** - For portfolio tracking
---
## Resources
- **Get API Key:** [mobula.io](https://mobula.io)
- **API Documentation:** [docs.mobula.io](https://docs.mobula.io)
- **Main Repository:** [GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
- **Report Issues:** [GitHub Issues](https://github.com/Flotapponnier/Crypto-date-openclaw/issues)
- **Skill Documentation:** [SKILL.md](./SKILL.md)
---
## FAQ
**Q: How often does the agent check?**
A: Every heartbeat interval (default ~30min). You can adjust this in your agent settings.
**Q: Can I get instant alerts?**
A: Current heartbeat system checks every ~30min. For instant alerts, WebSocket support is on the roadmap.
**Q: Where do I receive alerts?**
A: Configure your agent to send alerts via Telegram, Discord, WhatsApp, or Slack.
**Q: How many monitoring tasks can I set?**
A: Unlimited, but stay within rate limits (100 req/min free tier). Each monitoring task may use 1-5 requests per heartbeat.
**Q: Can I monitor without being alerted?**
A: Yes. Set up daily/weekly summaries instead of real-time alerts.
---
## License
MIT License - See [LICENSE](../../LICENSE)
---
## Credits
- **Built by:** [Mobula](https://mobula.io)
- **For:** [OpenClaw](https://openclaw.ai)
- **Powered by:** OpenClaw heartbeat system
Track wallet portfolios and transaction history across 88+ blockchains. Monitor holdings, analyze trades, and follow whale wallets.
---
name: mobula-wallet
displayName: Mobula - Wallet Portfolio & Transactions
description: Track wallet portfolios and transaction history across 88+ blockchains. Monitor holdings, analyze trades, and follow whale wallets.
version: 1.0.0
author: Mobula
category: crypto
tags:
- crypto
- wallet
- portfolio
- blockchain
- multi-chain
requiredEnvVars:
- MOBULA_API_KEY
homepage: https://mobula.io
docs: https://docs.mobula.io
repository: https://github.com/Flotapponnier/Crypto-date-openclaw
---
# Mobula - Wallet Portfolio & Transactions
Track wallet portfolios and analyze transaction history across **88+ blockchains** in real-time. Perfect for portfolio tracking, whale watching, and wallet analytics.
## Quick Start
**1. Get your free API key** (no credit card required)
- Go to [mobula.io](https://mobula.io) and sign up
- Free tier: 100 requests/minute
- Copy your API key from the dashboard
**2. Set your API key**
```bash
export MOBULA_API_KEY="your_api_key_here"
```
**3. Try it**
Ask your agent:
- "Show portfolio for wallet 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
- "What tokens does vitalik.eth hold?"
- "Check my wallet balance"
- "What did this wallet buy recently?"
- "Track this whale's activity"
---
## Security
✅ **Read-only access** - No trading, no transactions
✅ **No private keys** - Only public blockchain data
✅ **Open source** - [View code on GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
✅ **VirusTotal verified** - Benign scan results
⚠️ **Privacy Note:**
- Only query public wallet addresses (e.g., vitalik.eth, well-known addresses)
- Wallet addresses are sent to Mobula's API for analysis
- On-chain data is already public, but querying it reveals your interest
- Don't query wallets you want to keep private
---
## What This Skill Does
### Wallet Portfolio (`mobula_wallet_portfolio`)
**Endpoint:** `GET https://api.mobula.io/api/1/wallet/portfolio`
Get complete portfolio for any wallet across all 88 chains in one call.
**Parameters:**
- `wallet` (required): Wallet address or ENS name
- Format: "0x..." or "vitalik.eth"
- `blockchains` (optional): Filter specific chains (comma-separated)
- `cache` (optional): Use cached data (faster, slightly less fresh)
**Returns:**
- All tokens held with:
- Token name, symbol, address
- Balance (amount and USD value)
- Current price
- Price change 24h
- Estimated profit/loss
- Chain
- Total portfolio value (USD)
- Portfolio allocation by token (percentages)
- NFTs (if present)
**Example prompts:**
- "Show the portfolio for wallet 0x123..."
- "What tokens does vitalik.eth hold?"
- "Check my wallet balance"
- "What's the total value of this wallet?"
- "Show me the top 5 holdings in this wallet"
**Use cases:**
- Portfolio tracking
- Wallet analysis
- Checking holdings before/after trades
- Monitoring allocation
- Setting up portfolio alerts
---
### Wallet Transactions (`mobula_wallet_transactions`)
**Endpoint:** `GET https://api.mobula.io/api/1/wallet/transactions`
Full transaction history for any wallet across all chains.
**Parameters:**
- `wallet` (required): Wallet address
- `from` (optional): Start timestamp (Unix seconds)
- `to` (optional): End timestamp (Unix seconds)
- `asset` (optional): Filter by specific token
- `limit` (optional): Number of transactions (default: 100)
**Returns:**
- Array of transactions:
- Type (swap, transfer, mint, burn)
- Tokens involved (from/to)
- Amounts
- USD values at time of transaction
- Timestamp
- Chain
- Transaction hash
**Example prompts:**
- "What did this wallet buy recently?"
- "Show me the last 10 transactions for 0x123..."
- "When did this wallet last sell ETH?"
- "Track this whale's activity"
**Use cases:**
- Wallet monitoring
- Whale tracking
- Pattern detection (what they buy/sell)
- Transaction verification
- Analyzing trading strategies
---
## Authentication
All requests require your API key in the `Authorization` header:
```
Authorization: MOBULA_API_KEY
```
**If authentication fails (401/403):**
- Verify: `echo $MOBULA_API_KEY`
- Regenerate at [mobula.io](https://mobula.io) if expired
- Check rate limits (100 req/min free tier)
---
## Response Formatting Tips
### Portfolio Overview
```
Total Portfolio: $24,430
Holdings:
1. ETH: $12,340 (47%) — up 4% 24h
2. BRETT: $8,200 (31%) — up 12% 24h
3. DEGEN: $5,890 (22%) — down 3% 24h
⚠️ ETH allocation is high (47%). Consider rebalancing.
```
### Transaction History
```
Recent Activity:
2h ago: Bought $150K BRETT on Base
- Price: $0.089 (now up 23%)
- Size: Large buy (whale signal)
4h ago: Sold $80K ETH on Ethereum
- Price: $3,456
Note: Wallet has rotated from ETH to BRETT.
```
---
## Error Handling
**API Key Issues:**
- "I need a Mobula API key. Get one free at https://mobula.io then set it: `export MOBULA_API_KEY='your_key'`"
- "Invalid API key. Check it at https://mobula.io"
- "Rate limit hit. Upgrade at https://mobula.io or retry in a few minutes"
**Wallet Issues:**
- "Invalid wallet address. Should be 0x... (42 characters) or ENS name like vitalik.eth"
- "This wallet has no activity or balance. Is this the correct address and chain?"
---
## Common Use Cases
### Portfolio Guardian Pattern
Monitor your wallet 24/7 and get alerts on concentration risks:
1. Fetch portfolio via `mobula_wallet_portfolio`
2. Calculate allocation percentages
3. Check conditions:
- Any token >40% of portfolio → suggest rebalancing
- Any token down >15% in 24h → alert
- Total portfolio changed >10% → notify
4. Store previous values to detect changes
5. Send daily summary
**Example prompt:**
> "Monitor my wallet 0x... and alert me on Telegram if any token drops more than 15% or my allocation exceeds 40% on one asset. Daily summary at 9am."
---
### Whale Watching Pattern
Track smart money movements:
1. Check transactions via `mobula_wallet_transactions`
2. If new significant transaction (>$50K):
- Get token details
- Check if other tracked whales bought same token
3. If multiple whales buying → priority alert
**Example prompt:**
> "Watch wallets 0xabc, 0xdef, 0x123. Alert if any buy/sell more than $50K. If multiple whales buy the same token within 6h, priority alert with full analysis."
---
## Rate Limits
- **Free tier:** 100 requests/minute
- Cache portfolio data when possible (doesn't change every second)
- Upgrade at [mobula.io](https://mobula.io) for higher limits
---
## Supported Blockchains
**88+ chains including:**
- Ethereum, Base, Arbitrum, Optimism, Polygon
- BNB Chain, Avalanche, Solana, Fantom, Cronos
- And many more...
Full list: [docs.mobula.io/blockchains](https://docs.mobula.io/blockchains)
---
## When to Use This Skill
**✅ USE WHEN the user:**
- Asks to check a wallet's holdings or portfolio value
- Wants cross-chain portfolio overview
- Wants to track whale wallets or monitor transactions
- Needs transaction history for analysis
- Asks about wallet activity or trading patterns
**❌ DON'T USE WHEN:**
- User wants token prices only (use mobula-prices skill)
- User wants to execute trades (use trading skills)
- User wants swap quotes (use DEX skills)
---
## Related Skills
- **mobula-prices** - Token prices and market data
- **mobula-alerts** - Smart monitoring and price alerts
---
## Resources
- **Get API Key:** [mobula.io](https://mobula.io)
- **API Docs:** [docs.mobula.io](https://docs.mobula.io)
- **GitHub:** [Mobula OpenClaw Skills](https://github.com/Flotapponnier/Crypto-date-openclaw)
- **Support:** Open issue on GitHub
- **Privacy Policy:** [mobula.io/privacy](https://mobula.io/privacy)
---
## Version History
**1.0.0** (2024-03-25): Initial release
- Wallet portfolio tracking across all chains
- Transaction history analysis
- Whale watching support
- Privacy guidelines
FILE:README.md
# Mobula Wallet - Portfolio Tracking & Whale Analytics
> Track wallet portfolios and transaction history across 88+ blockchains. Monitor holdings, analyze trades, and follow whale wallets.
[](https://clawhub.ai)
[](https://opensource.org/licenses/MIT)
## What This Skill Does
**mobula-wallet** gives your OpenClaw agent the ability to analyze any wallet across all major blockchains, track portfolios in real-time, and monitor whale activity.
### Key Features
✅ **Cross-chain portfolio** - See all holdings across 88+ chains in one call
✅ **Transaction history** - Full on-chain activity analysis
✅ **Whale tracking** - Monitor large wallet movements
✅ **Portfolio analytics** - Allocation, profit/loss, trends
✅ **ENS support** - Use vitalik.eth instead of 0x addresses
### Perfect For
- Tracking your own portfolio across chains
- Analyzing whale wallet holdings
- Monitoring wallet transactions
- Following smart money
- Portfolio allocation analysis
---
## Quick Start (5 minutes)
### 1. Install the Skill
**Via ClawHub (recommended):**
```
Tell your agent: "Install mobula-wallet from ClawHub"
```
**Via URL:**
```
Tell your agent: "Install skill from https://raw.githubusercontent.com/Flotapponnier/Crypto-date-openclaw/main/skills/mobula-wallet/SKILL.md"
```
### 2. Get Your Free API Key
1. Go to [mobula.io](https://mobula.io)
2. Sign up (takes 30 seconds)
3. Copy your API key from the dashboard
**Free tier includes:**
- 100 requests/minute
- No credit card required
- Access to all 88+ blockchains
- Unlimited wallet queries
### 3. Set Your API Key
```bash
export MOBULA_API_KEY="your_api_key_here"
```
**Make it permanent:**
```bash
echo 'export MOBULA_API_KEY="your_key"' >> ~/.zshrc
source ~/.zshrc
```
### 4. Test It
Ask your agent:
- "Show portfolio for wallet vitalik.eth"
- "What did this wallet buy recently: 0x742d35Cc..."
- "Track this whale wallet's activity"
---
## Example Prompts
### Portfolio Tracking
```
"Show portfolio for wallet vitalik.eth"
"What's the total value of wallet 0x742d35..."
"What tokens does this wallet hold?"
"Show me the top 5 holdings in this wallet"
```
### Transaction Analysis
```
"What did this wallet buy recently?"
"Show me the last 10 transactions for 0x123..."
"When did this wallet last sell ETH?"
"What tokens has this wallet been accumulating?"
```
### Whale Watching
```
"Track this whale's activity: 0xabc..."
"Show me large transactions from this wallet"
"What's this whale buying right now?"
"Has this wallet made any big moves today?"
```
### Portfolio Analysis
```
"What's the allocation breakdown of this wallet?"
"Show me profit/loss for this portfolio"
"Which tokens in this wallet are up today?"
"Is this wallet concentrated in one asset?"
```
---
## What Data You Get
### Portfolio Overview
- Total portfolio value (USD)
- All tokens held across all chains
- Token balances (amount + USD value)
- Current prices for each holding
- 24h price changes
- Estimated profit/loss per token
- Portfolio allocation percentages
- NFTs (if present)
### Transaction History
- Type (swap, transfer, mint, burn)
- Tokens involved (from/to)
- Trade amounts
- USD values at transaction time
- Timestamps
- Chain information
- Transaction hashes
- Wallet addresses (sender/receiver)
---
## Supported Blockchains
**88+ chains including:**
- Ethereum
- Base
- Arbitrum
- Optimism
- Polygon
- BNB Chain
- Avalanche
- Solana
- Fantom
- Cronos
- And many more...
Full list: [docs.mobula.io/blockchains](https://docs.mobula.io/blockchains)
---
## Security & Privacy
✅ **Read-only access** - No trading, no transactions
✅ **No private keys** - Only public blockchain data
✅ **Open source** - [View code on GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
✅ **VirusTotal verified** - Benign scan results
### Privacy Important Notes
⚠️ **Wallet addresses are public data:**
- On-chain data is already public on blockchain explorers
- This skill queries that public data via Mobula API
- Wallet addresses you query are sent to Mobula's servers
⚠️ **Best practices:**
- Only query public wallet addresses (e.g., vitalik.eth, known whales)
- Don't query wallets you want to keep private
- Don't query wallets that link to your identity if you value privacy
- Use a separate API key for testing vs production
**Your API key cannot:**
- Access private keys or seed phrases
- Execute trades or transactions
- Sign anything on your behalf
- Transfer funds
---
## Use Cases
### 1. Portfolio Guardian
Monitor your own wallet 24/7 for concentration risks:
```
"Monitor my wallet 0x... and alert me if any token
drops more than 15% or my allocation exceeds 40%
on one asset. Daily summary at 9am."
```
Combine with [mobula-alerts](../mobula-alerts/) skill for automation.
### 2. Whale Tracking
Follow smart money and get insights:
```
"Watch these whale wallets: 0xabc, 0xdef, 0x123.
Show me whenever they make a transaction over $50K."
```
### 3. Portfolio Analysis
Understand your allocation and risk:
```
"Analyze my wallet 0x... and tell me if I'm too
concentrated in any single token. Suggest rebalancing
if needed."
```
### 4. Transaction Forensics
Track where money is flowing:
```
"Show me all ETH transactions from this wallet in
the last 30 days. Who are they sending to?"
```
---
## Rate Limits
**Free tier:** 100 requests/minute
**Tips to stay within limits:**
- Cache portfolio data (doesn't change every second)
- Use transaction filters to limit results
- Batch wallet queries when possible
- Upgrade to higher tier at [mobula.io](https://mobula.io) if needed
---
## Troubleshooting
### "API key not found" error
```bash
# Check if key is set
echo $MOBULA_API_KEY
# Set it if missing
export MOBULA_API_KEY="your_key_here"
# Restart your agent
openclaw restart
```
### "Invalid wallet address" error
- Wallet addresses should be 42 characters starting with 0x
- Or use ENS names like vitalik.eth
- Check for typos in the address
### "No activity found" error
- Wallet may have no balance or transactions
- Try a different blockchain (use `blockchains` parameter)
- Verify the wallet address is correct
---
## Related Skills
Want more Mobula features? Check out:
- **[mobula-prices](../mobula-prices/)** - Token prices and market data
- **[mobula-alerts](../mobula-alerts/)** - 24/7 monitoring and smart alerts
---
## Resources
- **Get API Key:** [mobula.io](https://mobula.io)
- **API Documentation:** [docs.mobula.io](https://docs.mobula.io)
- **Main Repository:** [GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
- **Privacy Policy:** [mobula.io/privacy](https://mobula.io/privacy)
- **Report Issues:** [GitHub Issues](https://github.com/Flotapponnier/Crypto-date-openclaw/issues)
- **Skill Documentation:** [SKILL.md](./SKILL.md)
---
## FAQ
**Q: Can I track my own wallet?**
A: Yes, but remember that wallet addresses you query are sent to Mobula's API. Only query wallets you're comfortable being associated with publicly.
**Q: Does this work across all blockchains?**
A: Yes. One query returns holdings across all 88+ supported chains.
**Q: Can I see NFTs?**
A: Yes. The portfolio endpoint includes NFT holdings if present.
**Q: Can this skill execute trades?**
A: No. This skill is read-only. It can only view public on-chain data.
**Q: How do I track multiple wallets?**
A: Ask your agent to query each wallet. Combine with [mobula-alerts](../mobula-alerts/) for automated monitoring.
**Q: Can I filter transactions by token?**
A: Yes. Use the `asset` parameter to filter by specific token.
---
## License
MIT License - See [LICENSE](../../LICENSE)
---
## Credits
- **Built by:** [Mobula](https://mobula.io)
- **For:** [OpenClaw](https://openclaw.ai)
- **Coverage:** 88+ blockchains, cross-chain portfolio tracking
Real-time token prices, market caps, volume, and analytics across 88+ blockchains. Free tier, no credit card required.
---
name: mobula-prices
displayName: Mobula - Crypto Prices & Market Data
description: Real-time token prices, market caps, volume, and analytics across 88+ blockchains. Free tier, no credit card required.
version: 1.0.0
author: Mobula
category: crypto
tags:
- crypto
- prices
- market-data
- defi
- real-time
requiredEnvVars:
- MOBULA_API_KEY
homepage: https://mobula.io
docs: https://docs.mobula.io
repository: https://github.com/Flotapponnier/Crypto-date-openclaw
---
# Mobula - Crypto Prices & Market Data
Get real-time crypto prices, market data, and analytics across **88+ blockchains**. Oracle-grade pricing trusted by Chainlink, Supra, and API3.
## Quick Start
**1. Get your free API key** (no credit card required)
- Go to [mobula.io](https://mobula.io) and sign up
- Free tier: 100 requests/minute
- Copy your API key from the dashboard
**2. Set your API key**
```bash
export MOBULA_API_KEY="your_api_key_here"
```
**3. Try it**
Ask your agent:
- "What's the price of Bitcoin?"
- "Show me ETH market cap and volume"
- "Is PEPE pumping or dumping right now?"
- "Compare BTC, ETH, and SOL performance today"
- "What was the price of this token on January 1st?"
---
## Security
✅ **Read-only access** - No trading, no transactions
✅ **No private keys** - Only public blockchain data
✅ **Open source** - [View code on GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
✅ **VirusTotal verified** - Benign scan results
---
## What This Skill Does
### Get Token Prices (`mobula_market_data`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/data`
Get current price, volume, market cap, and liquidity for any token.
**Parameters:**
- `asset` (required): Token name, symbol, or contract address
- Examples: "Bitcoin", "ETH", "0x532f27101965dd16442e59d40670faf5ebb142e4"
- `blockchain` (optional): Specific chain ("base", "ethereum", "solana", etc.)
**Returns:**
- Current price (USD)
- Price changes: 1h, 24h, 7d, 30d
- Volume (24h)
- Market cap & fully diluted valuation
- Liquidity
- ATH/ATL with dates
- Supply metrics
**Example prompts:**
- "What's the price of Bitcoin?"
- "Show me BRETT's market data on Base"
- "Is ETH pumping or dumping?"
- "What's the market cap of PEPE?"
---
### Compare Multiple Tokens (`mobula_market_multi`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/multi-data`
Get market data for up to 500 tokens in one request.
**Parameters:**
- `assets` (required): Comma-separated list of tokens
- Example: "Bitcoin,Ethereum,Solana"
**Returns:** Same data as `mobula_market_data` but for multiple tokens
**Example prompts:**
- "Compare BTC, ETH, and SOL performance today"
- "Show me the top movers from my watchlist"
- "Get prices for these 10 tokens: [list]"
---
### Historical Price Data (`mobula_market_history`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/history`
Get historical price data for charts and trend analysis.
**Parameters:**
- `asset` (required): Token name, symbol, or address
- `from` (optional): Start timestamp (Unix seconds)
- `to` (optional): End timestamp (Unix seconds)
- `period` (optional): "1h", "1d", or "1w"
**Returns:** Array of price points with timestamps, volume, and market cap
**Example prompts:**
- "Show me ETH price for the last 30 days"
- "What was this token's price on January 1st?"
- "Has this token been pumping this week?"
- "Chart BTC price movement for the last 7 days"
---
### Recent Trades (`mobula_market_trades`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/trades/pair`
Live trade feed for a specific liquidity pool.
**Parameters:**
- `address` (required): Liquidity pool address (not token address)
- `limit` (optional): Number of trades (default: 50, max: 300)
**Returns:**
- Recent trades with timestamps
- Buy/sell type
- Amounts (tokens and USD)
- Wallet addresses
- DEX and chain info
- Transaction hashes
**Note:** This endpoint requires the **pool/pair address**, not the token address. Pool addresses are not easily accessible via Mobula API. Consider using wallet transactions endpoint instead for trade history.
**Example prompts:**
- "Show recent trades for this pool: [pool_address]"
---
### Token Metadata (`mobula_metadata`)
**Endpoint:** `GET https://api.mobula.io/api/1/metadata`
Get detailed token information.
**Parameters:**
- `asset` (required): Token name, symbol, or address
**Returns:**
- Name, symbol, logo
- Description
- Official links (website, Twitter, Telegram, Discord)
- Contract addresses across all chains
- Launch date
- Categories/tags
**Example prompts:**
- "Tell me about this token"
- "What's the website for this project?"
- "Where can I find their community?"
---
## Authentication
All requests require your API key in the `Authorization` header:
```
Authorization: MOBULA_API_KEY
```
**If authentication fails (401/403):**
- Verify: `echo $MOBULA_API_KEY`
- Regenerate at [mobula.io](https://mobula.io) if expired
- Check rate limits (100 req/min free tier)
---
## Response Formatting Tips
### Prices
✅ "Price $0.042 (up 12% 24h, down 8% from ATH)"
❌ "Price is $0.003"
### Large Numbers
✅ "$1.23M", "$456K", "$45.6B"
❌ "$1234567"
### Context
Always show direction and timeframe:
- "up 12.4% (24h)"
- "down from ATH of $0.089 on Dec 1st"
---
## Error Handling
**API Key Issues:**
- "I need a Mobula API key. Get one free at https://mobula.io then set it: `export MOBULA_API_KEY='your_key'`"
- "Invalid API key. Check it at https://mobula.io"
- "Rate limit hit. Upgrade at https://mobula.io or retry in a few minutes"
**Token Not Found:**
- "Couldn't find that token. Try the contract address or check spelling?"
- Suggest similar tokens if possible
---
## Rate Limits
- **Free tier:** 100 requests/minute
- Use `mobula_market_multi` for batch queries (1 request for 500 tokens)
- Upgrade at [mobula.io](https://mobula.io) for higher limits
---
## Supported Blockchains
**88+ chains including:**
- Ethereum, Base, Arbitrum, Optimism, Polygon
- BNB Chain, Avalanche, Solana, Fantom, Cronos
- And many more...
Full list: [docs.mobula.io/blockchains](https://docs.mobula.io/blockchains)
---
## When to Use This Skill
**✅ USE WHEN the user:**
- Asks about token prices, volume, market cap, or changes
- Mentions a contract address and wants info
- Wants historical price data
- Needs batch data on multiple tokens
- Asks about liquidity, ATH, ATL, or trading volume
- Wants to compare token performance
**❌ DON'T USE WHEN:**
- User wants to execute trades (use trading skills)
- User wants swap quotes (use DEX skills)
- User asks about wallets or portfolio (use mobula-wallet skill)
---
## Related Skills
- **mobula-wallet** - Portfolio tracking and wallet analytics
- **mobula-alerts** - Smart monitoring and price alerts
---
## Resources
- **Get API Key:** [mobula.io](https://mobula.io)
- **API Docs:** [docs.mobula.io](https://docs.mobula.io)
- **GitHub:** [Mobula OpenClaw Skills](https://github.com/Flotapponnier/Crypto-date-openclaw)
- **Support:** Open issue on GitHub
---
## Version History
**1.0.1** (2026-03-28): Updated documentation
- Fixed trades endpoint: requires pool address (`/api/1/market/trades/pair`)
**1.0.0** (2024-03-25): Initial release
- Market data endpoint
- Multi-token batch queries
- Historical price data
- Token metadata
FILE:README.md
# Mobula Prices - Real-Time Crypto Market Data
> Get real-time token prices, market caps, volume, and analytics across 88+ blockchains. Free tier, no credit card required.
[](https://clawhub.ai)
[](https://opensource.org/licenses/MIT)
## What This Skill Does
**mobula-prices** gives your OpenClaw agent instant access to crypto market data for 200M+ tokens across 88+ blockchains.
### Key Features
✅ **Real-time prices** - Current price, 24h change, volume, market cap
✅ **Historical data** - Charts and trends for any timeframe
✅ **Batch queries** - Compare up to 500 tokens in one request
✅ **Token metadata** - Project info, social links, contract addresses
### Perfect For
- Checking token prices quickly
- Comparing multiple tokens
- Analyzing price trends and charts
- Monitoring trading volume
- Researching new tokens
---
## Quick Start (5 minutes)
### 1. Install the Skill
**Via ClawHub (recommended):**
```
Tell your agent: "Install mobula-prices from ClawHub"
```
**Via URL:**
```
Tell your agent: "Install skill from https://raw.githubusercontent.com/Flotapponnier/Crypto-date-openclaw/main/skills/mobula-prices/SKILL.md"
```
### 2. Get Your Free API Key
1. Go to [mobula.io](https://mobula.io)
2. Sign up (takes 30 seconds)
3. Copy your API key from the dashboard
**Free tier includes:**
- 100 requests/minute
- No credit card required
- Access to all 88+ blockchains
- 200M+ token coverage
### 3. Set Your API Key
```bash
export MOBULA_API_KEY="your_api_key_here"
```
**Make it permanent:**
```bash
echo 'export MOBULA_API_KEY="your_key"' >> ~/.zshrc
source ~/.zshrc
```
### 4. Test It
Ask your agent:
- "What's the price of Bitcoin?"
- "Compare BTC, ETH, and SOL performance today"
- "Show me ETH price history for the last 30 days"
---
## Example Prompts
### Basic Price Checks
```
"What's the price of Bitcoin?"
"Show me PEPE's market cap"
"Is ETH pumping or dumping right now?"
```
### Multi-Token Comparisons
```
"Compare BTC, ETH, and SOL performance today"
"Show me the top 10 tokens by volume"
"Which is up more in the last 24h: DOGE or SHIB?"
```
### Historical Analysis
```
"Show me ETH price for the last 30 days"
"What was Bitcoin's price on January 1st?"
"Has PEPE been trending up or down this week?"
```
### Advanced Queries
```
"Get market data for BRETT on Base chain"
"Show recent trades for this contract: 0x532f27..."
"Find me the project website for this token"
```
---
## What Data You Get
### Market Data
- Current price (USD)
- Price changes: 1h, 24h, 7d, 30d
- Volume (24h)
- Market cap & fully diluted valuation
- Liquidity
- All-time high/low with dates
- Supply metrics
### Historical Charts
- Price points with timestamps
- Volume at each point
- Market cap over time
- Customizable timeframes
### Live Trade Feed
- Recent buy/sell activity
- Trade amounts (tokens + USD)
- Wallet addresses
- DEX and chain info
- Transaction hashes
### Token Information
- Name, symbol, logo
- Project description
- Official website
- Social links (Twitter, Telegram, Discord)
- Contract addresses across all chains
- Launch date
---
## Supported Blockchains
**88+ chains including:**
- Ethereum
- Base
- Arbitrum
- Optimism
- Polygon
- BNB Chain
- Avalanche
- Solana
- Fantom
- Cronos
- And many more...
Full list: [docs.mobula.io/blockchains](https://docs.mobula.io/blockchains)
---
## Security
✅ **Read-only access** - No trading, no transactions
✅ **No private keys** - Only public blockchain data
✅ **Open source** - [View code on GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
✅ **VirusTotal verified** - Benign scan results
Your API key only provides access to public market data. It cannot:
- Execute trades
- Access your wallets
- Sign transactions
- Access private information
---
## Rate Limits
**Free tier:** 100 requests/minute
**Tips to stay within limits:**
- Use batch queries (`mobula_market_multi`) for multiple tokens
- Cache results when appropriate
- Upgrade to higher tier at [mobula.io](https://mobula.io) if needed
---
## Troubleshooting
### "API key not found" error
```bash
# Check if key is set
echo $MOBULA_API_KEY
# Set it if missing
export MOBULA_API_KEY="your_key_here"
# Restart your agent
openclaw restart
```
### "Token not found" error
- Try using the contract address instead of name
- Check spelling of token name
- Verify the token exists on the specified blockchain
### Rate limit errors
- Wait a few minutes and retry
- Use batch endpoints for multiple tokens
- Consider upgrading your plan at [mobula.io](https://mobula.io)
---
## Related Skills
Want more Mobula features? Check out:
- **[mobula-wallet](../mobula-wallet/)** - Portfolio tracking and wallet analytics
- **[mobula-alerts](../mobula-alerts/)** - 24/7 monitoring and smart alerts
---
## Resources
- **Get API Key:** [mobula.io](https://mobula.io)
- **API Documentation:** [docs.mobula.io](https://docs.mobula.io)
- **Main Repository:** [GitHub](https://github.com/Flotapponnier/Crypto-date-openclaw)
- **Report Issues:** [GitHub Issues](https://github.com/Flotapponnier/Crypto-date-openclaw/issues)
- **Skill Documentation:** [SKILL.md](./SKILL.md)
---
## FAQ
**Q: Do I need to pay for the API key?**
A: No. The free tier (100 req/min) is sufficient for most users. No credit card required.
**Q: Can I check prices for any token?**
A: Yes. Mobula covers 200M+ tokens across 88+ blockchains.
**Q: Does this work with Solana tokens?**
A: Yes. Mobula supports Ethereum, Base, Solana, and 85+ other chains.
**Q: Can I use this to execute trades?**
A: No. This skill is read-only market data. For trading, use dedicated trading skills.
**Q: How often is the data updated?**
A: Real-time. Prices update continuously with sub-second latency.
---
## License
MIT License - See [LICENSE](../../LICENSE)
---
## Credits
- **Built by:** [Mobula](https://mobula.io)
- **For:** [OpenClaw](https://openclaw.ai)
- **Coverage:** 88+ blockchains, 200M+ tokens
- **Trusted by:** Chainlink, Supra, API3 (oracle-grade pricing)
Real-time crypto market data, wallet portfolio tracking, and token analytics across 88+ blockchains. Use when the user wants to check any token price (by nam...
---
name: mobula
displayName: Mobula - Crypto Market Data & Wallet Intelligence
description: >
Real-time crypto market data, wallet portfolio tracking, and token
analytics across 88+ blockchains. Use when the user wants to check
any token price (by name, symbol, or contract address), look up wallet
balances and holdings across all chains, get historical price data,
track portfolio PnL, monitor whale wallets, find new tokens, or get
oracle-grade pricing. Requires free API key from mobula.io.
version: 1.0.1
author: Mobula
category: crypto
tags:
- crypto
- market-data
- wallet
- portfolio
- defi
- blockchain
- price-api
- token-analytics
- multi-chain
- real-time
requiredEnvVars:
- MOBULA_API_KEY
homepage: https://mobula.io
docs: https://docs.mobula.io
repository: https://github.com/Flotapponnier/Crypto-date-openclaw
---
# Mobula - Multi-Chain Crypto Data Intelligence
Real-time crypto market data, wallet tracking, and on-chain analytics across **88+ blockchains**. Oracle-grade pricing trusted by Chainlink, Supra, and API3.
## When to Use This Skill
**USE WHEN** the user:
- Asks about any token price, volume, market cap, or price change
- Wants to check a wallet's holdings or portfolio value
- Needs historical price data for charts or analysis
- Mentions a contract address and wants token info
- Asks about tokens on specific chains (Base, Arbitrum, Solana, etc.)
- Wants cross-chain portfolio overview
- Needs batch data on multiple tokens at once
- Asks about token liquidity, ATH, ATL, or trading volume
- Wants to track whale wallets or monitor significant transactions
- Needs to find new tokens matching specific criteria
**DON'T USE WHEN**:
- User wants to execute trades (use bankr skill instead)
- User wants DEX swap quotes (use defi skill)
- User wants exchange-specific data (use okx/binance skills)
---
## Core Capabilities
### 1. Market Data (`mobula_market_data`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/data`
Get real-time price, volume, market cap, and liquidity for any token across all supported chains.
**Parameters:**
- `asset` (required): Token name, symbol, or contract address
- Examples: "Bitcoin", "ETH", "0x532f27101965dd16442e59d40670faf5ebb142e4"
- `blockchain` (optional): Specific chain to query
- Examples: "base", "arbitrum", "ethereum", "solana", "polygon"
**Returns:**
- Current price (USD)
- Price changes: 1h, 24h, 7d, 30d (percentage and absolute)
- Volume (24h)
- Market cap
- Fully diluted valuation
- Liquidity
- All-time high (ATH) and all-time low (ATL) with dates
- Total supply, circulating supply
**Usage examples:**
- "What's the price of Bitcoin?"
- "Show me BRETT's market data on Base"
- "Get data for contract 0x532f27101965dd16442e59d40670faf5ebb142e4"
- "Is ETH pumping or dumping right now?"
- "What's the market cap of PEPE?"
**When to use:**
- User asks for price of any token
- User wants to know if something is pumping/dumping
- Analyzing token fundamentals (mcap, liquidity, volume)
- Comparing tokens
- Setting up price alerts
---
### 2. Wallet Portfolio (`mobula_wallet_portfolio`)
**Endpoint:** `GET https://api.mobula.io/api/1/wallet/portfolio`
Get complete portfolio for any wallet across all 88 chains in a single call.
**Parameters:**
- `wallet` (required): Wallet address
- Format: "0x..." or ENS name (e.g., "vitalik.eth")
- `blockchains` (optional): Comma-separated list to filter specific chains
- Default: all chains
- `cache` (optional): Use cached data (faster, slightly less fresh)
**Returns:**
- All tokens held with:
- Token name, symbol, address
- Balance (amount and USD value)
- Current price
- Price change 24h
- Estimated profit/loss
- Chain
- Total portfolio value (USD)
- Portfolio allocation by token (percentages)
- NFTs (if present)
**Usage examples:**
- "Show the portfolio for wallet 0x123..."
- "What tokens does vitalik.eth hold?"
- "Check my wallet balance"
- "What's the total value of this wallet?"
- "Show me the top 5 holdings in this wallet"
**When to use:**
- Portfolio tracking
- Wallet analysis
- Checking holdings before/after trades
- Monitoring allocation
- Setting up portfolio alerts
---
### 3. Historical Price Data (`mobula_market_history`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/history`
Get historical price data for any token with flexible timeframes.
**Parameters:**
- `asset` (required): Token name, symbol, or address
- `from` (optional): Start timestamp (Unix seconds)
- `to` (optional): End timestamp (Unix seconds)
- `period` (optional): Data granularity
- Options: "1h", "1d", "1w"
- Default: auto-selected based on timeframe
**Returns:**
- Array of price points with timestamps
- Volume at each point
- Market cap at each point
**Usage examples:**
- "Show me ETH price for the last 30 days"
- "What was this token's price on January 1st?"
- "Has this token been pumping or dumping this week?"
- "Chart the price movement of BTC in the last 7 days"
**When to use:**
- Analyzing trends
- Calculating historical PnL
- Comparing price action across timeframes
- Identifying patterns (breakouts, supports, resistance)
---
### 4. Recent Trades (`mobula_market_trades`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/trades`
Live trade feed for any token across all DEXs and chains.
**Parameters:**
- `asset` (required): Token name, symbol, or address
- `limit` (optional): Number of trades to return (default: 50, max: 300)
**Returns:**
- Array of recent trades with:
- Timestamp
- Type (buy/sell)
- Amount (tokens and USD)
- Price at trade
- Wallet address (buyer/seller)
- DEX and chain
- Transaction hash
**Usage examples:**
- "Show recent trades for this token"
- "Who's buying PEPE right now?"
- "Any whale movements on this token?"
- "What's the last 10 trades on this token?"
**When to use:**
- Whale watching
- Detecting unusual activity (large buys/sells)
- Volume verification
- Sentiment analysis (more buys vs sells)
---
### 5. Wallet Transaction History (`mobula_wallet_transactions`)
**Endpoint:** `GET https://api.mobula.io/api/1/wallet/transactions`
Full transaction history for any wallet across all chains.
**Parameters:**
- `wallet` (required): Wallet address
- `from` (optional): Start timestamp
- `to` (optional): End timestamp
- `asset` (optional): Filter by specific token
- `limit` (optional): Number of transactions (default: 100)
**Returns:**
- Array of transactions:
- Type (swap, transfer, mint, burn)
- Tokens involved (from/to)
- Amounts
- USD values at time of transaction
- Timestamp
- Chain
- Transaction hash
**Usage examples:**
- "What did this wallet buy recently?"
- "Show me the last 10 transactions for 0x123..."
- "When did this wallet last sell ETH?"
- "Track this whale's activity"
**When to use:**
- Wallet monitoring
- Whale tracking
- Pattern detection (what they buy/sell)
- Transaction verification
---
### 6. Multi-Asset Data (`mobula_market_multi`)
**Endpoint:** `GET https://api.mobula.io/api/1/market/multi-data`
Get market data for multiple tokens in a single request (batch endpoint).
**Parameters:**
- `assets` (required): Comma-separated list of token names/symbols/addresses
- Example: "Bitcoin,Ethereum,Solana" or "BTC,ETH,SOL"
- Max: 500 tokens per request
**Returns:**
- Same data as `mobula_market_data` but for multiple tokens
- Efficient for portfolio analysis, watchlists, market overviews
**Usage examples:**
- "Compare BTC, ETH, and SOL performance today"
- "Show me the top movers from my watchlist"
- "Get prices for these 10 tokens: [list]"
**When to use:**
- Portfolio valuation
- Watchlist updates
- Market overview (top coins)
- Batch price checks
---
### 7. Token Metadata (`mobula_metadata`)
**Endpoint:** `GET https://api.mobula.io/api/1/metadata`
Get detailed metadata for any token.
**Parameters:**
- `asset` (required): Token name, symbol, or address
**Returns:**
- Name, symbol, logo
- Description
- Website, Twitter, Telegram, Discord links
- Contract addresses across all chains
- Launch date
- Categories/tags
**Usage examples:**
- "Tell me about this token"
- "What's the website for this project?"
- "Where can I find their community?"
**When to use:**
- Research on new tokens
- Verifying legitimacy
- Finding official links
---
## Authentication
**Required:** All API requests require authentication via API key.
**Setup:**
1. Get a free API key at **https://mobula.io** (100 requests/minute free tier)
2. Set environment variable:
```bash
export MOBULA_API_KEY="your_key_here"
```
3. Restart OpenClaw agent
**Header format:**
```
Authorization: MOBULA_API_KEY
```
**If authentication fails (401/403):**
- Verify key is set: `echo $MOBULA_API_KEY`
- Regenerate key at https://mobula.io if expired
- Check rate limits (100 req/min free, upgrade for more)
---
## Privacy & Security
**IMPORTANT - Read Before Using:**
### What This Skill Accesses
- **Public blockchain data only**: Wallet addresses, token prices, transaction history
- **No private keys**: Never provide private keys, seed phrases, or passwords to this skill
- **No sensitive credentials**: Skill only requires Mobula API key (public data access)
### Wallet Address Privacy
- Wallet addresses queried via this skill are sent to Mobula's API (https://api.mobula.io)
- Querying a wallet address reveals its holdings publicly (on-chain data is already public)
- **Only use public wallet addresses** you're comfortable sharing
- **Never enter addresses** you want to keep private or that link to your identity
### API Key Scope
- Mobula API keys provide **read-only** access to public blockchain data
- Keys can be scoped, rotated, and revoked at https://mobula.io
- Free tier: 100 requests/minute
- Consider using a separate API key for testing vs production
### Data Retention
- Mobula may log API requests for analytics and rate limiting
- Refer to Mobula's privacy policy: https://mobula.io/privacy
- Do not query wallets or data you consider sensitive
### Best Practices
1. Use throwaway/test API keys for initial testing
2. Only query public wallet addresses (e.g., vitalik.eth, well-known addresses)
3. Avoid querying your personal wallets if you want privacy
4. Rotate API keys periodically
5. Monitor rate limits and usage at https://mobula.io
---
## Smart Monitoring Patterns with Heartbeat
OpenClaw's heartbeat system checks conditions every ~30 minutes. Use this for proactive monitoring.
### Pattern 1: Portfolio Guardian
Every heartbeat:
1. Fetch user's wallet via `mobula_wallet_portfolio`
2. Calculate allocation percentages
3. Check conditions:
- Any token >40% of portfolio → suggest rebalancing
- Any token down >15% in 24h → alert with context
- Total portfolio changed >10% → notify
4. Store previous values in memory to detect changes
5. Send daily summary at user's preferred time
### Pattern 2: Whale Watching
Every heartbeat:
1. Check transactions for tracked wallets via `mobula_wallet_transactions`
2. If new significant transaction (>$50K):
- Get token details via `mobula_market_data`
- Check recent trades via `mobula_market_trades`
- Cross-check if other tracked whales bought the same token
3. If multiple whales buying → priority alert
### Pattern 3: Token Scout (Autonomous Discovery)
Every 4-6 hours on heartbeat:
1. User defines criteria (store in memory):
- Chains: Base, Arbitrum
- Market cap: <$5M
- Liquidity: >$100K
- Volume change 24h: >+50%
2. Search/filter tokens via `mobula_market_data` queries
3. For each match:
- Get 7-day history via `mobula_market_history`
- Check metadata via `mobula_metadata`
- Calculate risk score
4. Send top 3 with analysis
### Pattern 4: Smart Price Alerts
Instead of simple "price > X" alerts, create contextual ones:
Example: "Alert me if Bitcoin moves >5% in 1 hour BUT only if volume is 2x above 24h average"
Every heartbeat:
1. Get current price and volume via `mobula_market_data`
2. Compare to price from 1 hour ago (stored in memory)
3. Check if volume condition met
4. Only alert if BOTH conditions true
---
## Combining Multiple Endpoints for Rich Insights
### Example: Full Token Analysis
User asks: "Should I buy this token?"
1. `mobula_market_data` → current price, mcap, volume, liquidity
2. `mobula_market_history` → 7d and 30d trend
3. `mobula_market_trades` → recent activity (accumulation or distribution?)
4. `mobula_metadata` → project info, socials, contract verification
Response format:
```
TOKEN Analysis
Price: $0.042 (down 8% 24h, up 156% 7d)
- Near 7d high, down from $0.051
- Still up 400% from 30d low
Fundamentals:
- Mcap: $2.1M (small cap, high risk/reward)
- Liquidity: $280K (decent for size)
- Volume: $1.2M 24h (healthy)
On-chain:
- Recent trades show buying pressure (3:1 buy/sell ratio)
- 2 large buys ($50K+) in last 2h
Project:
- Contract verified
- Active Twitter (12K followers)
Risk: Small cap, high volatility. Don't ape more than you can lose.
```
---
## Response Formatting Guidelines
### Prices
- Always show direction: "up 12.4%" or "down 3.2%"
- Include timeframe: "up 12.4% (24h)"
- Add context: "Price $0.042 (up 12% 24h, down 8% from ATH)"
### Large Numbers
- Format clearly: "$1.23M", "$456K", "$45.6B"
- Not: "$1234567"
### Percentages
- Use for allocations: "ETH: 42% of portfolio"
- Use for changes: "down 15.3% in 24h"
### Context is Key
Don't just say "price is $0.003"
Say: "Price $0.003 (down 8% 24h, down 65% from ATH of $0.089 on Dec 1st, but up 12% from 7d low)"
---
## Common User Requests & How to Handle
### "Set up alerts for [token]"
1. Acknowledge the request
2. Ask for specific conditions (price threshold? percentage move? volume spike?)
3. Confirm you'll monitor via heartbeat
4. Store the alert config in memory
5. Confirm: "I'll check [token] every 30min and alert you on [channel] if [condition]"
### "Track this wallet"
1. Add wallet to monitoring list
2. Ask what to watch for:
- All activity?
- Only large trades (>$X)?
- Specific tokens?
3. Store in memory
4. Confirm monitoring is active
### "Find tokens matching [criteria]"
1. Clarify criteria:
- Chains?
- Market cap range?
- Volume requirements?
- Liquidity minimum?
2. Set up periodic checks (suggest 4-6h interval)
3. Ask how many results they want
4. Start monitoring on next heartbeat
### "What's happening in crypto right now?"
1. Check major tokens (BTC, ETH, SOL, BNB) via `mobula_market_multi`
2. Identify any major moves (>5% in 24h)
3. Check volume leaders
4. Provide concise overview
---
## Error Handling
### API Key Issues
- **No key set:** "I need a Mobula API key to fetch crypto data. Get one free at https://mobula.io then add it to your environment with `export MOBULA_API_KEY='your_key'`"
- **Invalid key:** "Your API key seems invalid. Please check it at https://mobula.io"
- **Rate limited:** "You've hit the API rate limit. Upgrade your plan at https://mobula.io for higher limits, or I'll retry in a few minutes."
### Token Not Found
- "I couldn't find that token by name. Could you provide the contract address? Or check the spelling?"
- Suggest similar tokens if possible
- Offer to search by contract address
### Wallet Issues
- **Invalid address:** "That doesn't look like a valid wallet address. Should be 0x... (42 characters) or an ENS name like vitalik.eth"
- **No activity:** "This wallet has no activity or balance. Is this the correct address and chain?"
---
## Rate Limits & Best Practices
### Respect Rate Limits
- Free tier: 100 requests/minute
- Use `mobula_market_multi` for batch queries instead of multiple `mobula_market_data` calls
- Cache data in memory when appropriate (metadata doesn't change often)
### Efficient Heartbeat Usage
- Don't call every endpoint on every heartbeat
- Only fetch what's needed based on active monitoring tasks
- Batch requests when possible
- Store previous values to detect changes
---
## Supported Blockchains
88+ chains including: Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche, Solana, Fantom, Cronos, and many more.
Full list: https://docs.mobula.io/blockchains
---
## Resources
- **Mobula Website:** https://mobula.io
- **API Documentation:** https://docs.mobula.io
- **Skill Repository:** https://github.com/Flotapponnier/Crypto-date-openclaw
- **Support:** Open an issue on GitHub or visit Mobula Discord
---
## Version History
- **1.0.1** (2024-02-20): Security & clarity improvements
- Added comprehensive Privacy & Security section
- Clarified API key requirement (removed confusing "No API key needed for dev/testing")
- Enhanced authentication documentation
- Added best practices for API key management
- Explicit warnings about wallet address privacy
- **1.0.0** (2024-02-20): Initial release
- 7 core endpoints (market data, portfolio, history, trades, transactions, multi-data, metadata)
- Heartbeat monitoring patterns
- Smart alert examples
- Comprehensive documentation