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:
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 (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=<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.
CommandWhat it doesbun run start wallet-createGenerate a new Tempo wallet (won't overwrite an existing one)bun run start balanceShow wallet address, USDC.e balance, bridge linkbun run start price <asset>Token price — accepts name, symbol, or contract addressbun run start wallet <addr>Wallet portfolio positionsbun run start lighthouseTrending tokensbun 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...
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).
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.
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,
);
GET https://mpp.mobula.io/api/2/token/price?asset=bitcoin with no auth.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.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.Authorization: Payment <base64url(credential)>, where credential references the tx hash.The skill does steps 2–4 automatically — agents only see the data response.
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:
BytesSizeFieldSource0..44MPP tagkeccak256("mpp")[0..4] (constant 0xef1ed712)4..51version0x015..1510server fingerprintkeccak256(realm)[0..10] (realm from WWW-Authenticate)15..2510client fingerprintzeros for anonymous, else keccak256(clientId)[0..10]25..327noncerandom 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.
4217, RPC https://rpc.tempo.xyz)0x20c000000000000000000000b9537d11c60e8b50)ErrorCauseFixNo wallet foundFirst-time setup not donebun run start wallet-create``Insufficient Tempo balance: you have $0.0000…Wallet not funded yetBridge USDC.e via the printed linkCould not parse challenge from: …Server didn't return a Tempo WWW-Authenticate headerLikely an upstream issue — retry or reportTempo tx failed: …RPC error or revert on-chainCheck balance and https://explorer.tempo.xyz for the address
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.