Mpp Mobula
by @flotapponnier
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...
clawhub install mpp-mobula📖 About This Skill
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)
# 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 install2. Create a hot wallet (AES-256-GCM encrypted at ~/.mpp-skill/wallet.json,
encryption key at ~/.mpp-skill/.secret, both 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=
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 | Token price — accepts name, symbol, or contract address |
| bun run start wallet | Wallet portfolio positions |
| bun run start lighthouse | Trending tokens |
| bun run start call | Generic call to any /api/2/* endpoint |
Examples:
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)
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:
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=". The decoded request specifies amount (in USDC.e atoms), currency: USDC.e, recipient, methodDetails.chainId: 4217, and optionally methodDetails.memo if the server pre-computes it.
3. Skill signs and broadcasts transferWithMemo(recipient, amount, attributionMemo) on USDC.e (0x20c000000000000000000000b9537d11c60e8b50) on Tempo. attributionMemo is NOT the raw challenge id — it's a structured 32-byte memo (see "Memo layout" below) that the server's mppx lib uses to recognize and bind the payment to the challenge.
4. Skill retries the same request with Authorization: Payment , where credential references the tx hash.
5. Server validates the tx (memo layout + tx hash + amount + recipient) and returns the data.
The skill does steps 2–4 automatically — agents only see the data response.
Memo layout (32 bytes)
Mobula uses the official mppx lib server-side. The bytes32 memo passed to transferWithMemo must follow the MPP attribution format, otherwise the server rejects with memo is not bound to this challenge:
| Bytes | Size | Field | Source |
|-------|------|-------|--------|
| 0..4 | 4 | MPP tag | keccak256("mpp")[0..4] (constant 0xef1ed712) |
| 4..5 | 1 | version | 0x01 |
| 5..15 | 10 | server fingerprint | keccak256(realm)[0..10] (realm from WWW-Authenticate) |
| 15..25 | 10 | client fingerprint | zeros for anonymous, else keccak256(clientId)[0..10] |
| 25..32 | 7 | nonce | random 7 bytes, or keccak256(challengeId)[0..7] for a deterministic challenge-bound nonce |
Common mistakes that produce a "memo is not bound" error: passing the raw challengeId (UTF-8 padded to 32), the base64url-decoded challengeId, keccak256(challengeId), or any value that doesn't start with the 4-byte MPP tag.
If methodDetails.memo is present in the challenge JSON, use that hex value directly (server pre-computed it) and skip the layout build.
Key facts
4217, RPC https://rpc.tempo.xyz)0x20c000000000000000000000b9537d11c60e8b50)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.