Call the oculr MPP
Go from zero to your first paid transaction analysis in under 5 minutes - with
mppxhandling MPP/x402 payment transparently insidefetch().
The setup below is for apps, scripts, and backends calling the MPP from code.
Run it from an agent
No code needed - an agent CLI can make the same call for you in one line. Replace 0xYOUR_TX_HASH with the real hash:
claude -p "Use the oculr MPP at https://mpp.oculr.xyz/SKILL.md to analyse EVM transaction 0xYOUR_TX_HASH on Ethereum - what happened, any risks?"The agent fetches SKILL.md, sets up the MPP client, and runs the analysis. For deeper agent integration (persistent skill install, typed tool-use schemas, sub-agent patterns), see Use as an agent.
1. Install mppx
npm install mppx viemmppx is the MPP/x402 client library. It intercepts 402 payment challenges, settles them on Tempo, and replays the request - your code sees a single round-trip.
viem is an mppx peer dependency (>=2.54.0), and the samples below import from it directly (privateKeyToAccount) - so install it explicitly rather than relying on npm's peer auto-install, which pnpm and yarn don't do.
2. Fund a wallet on Tempo
Bring an EVM wallet holding USDC.e on Tempo (contract 0x20C000000000000000000000b9537d11c60E8b50). Top up the wallet however you normally move tokens on Tempo.
export WALLET_PRIVATE_KEY=0xYOUR_PRIVATE_KEYPrefer not to put a private key in an env var?
mppx account createstores keys in your OS keychain - see the agent quickstart.
3. Analyse a transaction (sync, metered)
Synchronous POST /explain uses metered pricing - the payment is signed incrementally as the analysis accrues cost, which requires a session client and SSE. tempo.session.manager() handles the whole lifecycle: it opens the payment channel on first use, signs vouchers in the background as the stream charges, and reuses the channel across calls.
import { tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`)
// One manager per process. `maxDeposit` caps the channel total - your hard spend
// ceiling. It is NOT what you escrow: the channel opens at
// min(suggestedDeposit, maxDeposit), so '32' still escrows the $16 oculr suggests
// (refunded on close) while leaving room to top the channel up BETWEEN analyses.
// Set equal to the suggestion it could never grow at all; see
// /pricing#the-channel-deposit.
const session = tempo.session.manager({ account, maxDeposit: '32' })
const stream = await session.sse('https://mpp.oculr.xyz/explain', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
body: JSON.stringify({ txHash: '0x4e4b8ed4…' }),
})
for await (const payload of stream) {
const msg = JSON.parse(payload)
if (msg.type !== 'result') continue // progress events - ignore or log
console.log(msg.summary)
// → "Uniswap V3 swap: 1,000 USDC → 0.42 WETH via the 0.05% fee pool"
console.log(msg.txType) // "swap" | "exploit" | "mev" | …
console.log(msg.confidence) // "high" | "medium" | "low"
console.log(msg.risks) // [] or ["High gas price: 3× base fee", …]
}The final { type: 'result', … } message is the full ExplanationResult - shape at Endpoints reference. The other stream events (preflight_status, agent_text, tool_call, tool_result, tokens, …) are progress you can surface or ignore.
4. Or: plain-fetch async (metered, same total)
POST /explain/async is metered to the same total as the sync stream, collected across the job lifecycle: $0.01 is charged at submit, each poll of GET /result/:jobId collects what the analysis has accrued since your previous poll, and the first poll after it finishes charges the true-up. A poll with nothing yet to collect is free, as is re-fetching an already-paid finished result. This works with the classic mppx boot: call Mppx.create() once at startup and every fetch() auto-pays. Keep polling until the job is finished - 90 seconds with nothing collected aborts the analysis and leaves a partial result.
import { Mppx, tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`)
await Mppx.create({ methods: [tempo({ account, maxDeposit: '32' })] })
const { jobId } = await fetch('https://mpp.oculr.xyz/explain/async', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ txHash: '0x4e4b8ed4…' }),
}).then(r => r.json())
// Poll - each poll auto-pays what the analysis accrued since the previous one
// through the mppx polyfill, and trues up on the first poll after it finishes.
while (true) {
await new Promise(r => setTimeout(r, 5000))
const job = await fetch(`https://mpp.oculr.xyz/result/${jobId}`).then(r => r.json())
if (job.status === 'complete') { console.log(job.result.summary); break }
if (job.status === 'error') throw new Error(job.error)
}5. Pass a context hint to improve accuracy
context is free-form natural language passed straight to the analysis agent. Use it whenever you already know something useful about the transaction - it works identically on both endpoints:
body: JSON.stringify({
txHash: '0x…',
context: 'check if this is a reentrancy exploit',
})Quick test from the shell
If you have mppx installed globally (npm install -g mppx) and a funded account, you can kick off an async job from the command line - useful when prototyping:
mppx https://mpp.oculr.xyz/explain/async \
-J '{"txHash":"0x4e4b8ed4de38be29e3a7a15e2b14b5d8262e3c5b3f1e9d6a7c8b9e0f1a2d3c4e"}'
# → {"jobId":"…"}
# Poll with mppx - each poll collects the cost accrued since the previous one,
# and the first poll after the job finishes charges the true-up:
mppx https://mpp.oculr.xyz/result/YOUR_JOB_ID \
| jq '{status, result: {summary, txType, confidence}}'mppx handles the 402 payments behind the scenes - bare curl against a paid endpoint will just return the payment challenge, so every poll needs an MPP client. Stop polling for 90 seconds and the analysis is aborted, leaving a partial result.
Related
- Endpoints reference - full request/response schemas
- Use as an agent - agent-loop integration via
/tool-spec.jsonor/SKILL.md - Analyze a transaction - worked example with the result schema explained