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:
- Generate a Tempo wallet,
- Top it up via the public bridge,
- 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 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:
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)
- Agent calls
GET https://mpp.mobula.io/api/2/token/price?asset=bitcoin with no auth.
- 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, and optionally methodDetails.memo if the server pre-computes it.
- 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.
- Skill retries the same request with
Authorization: Payment <base64url(credential)>, where credential references the tx hash.
- 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
- 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.