Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Call the oculr MPP

Go from zero to your first paid transaction analysis in under 5 minutes - with mppx handling MPP/x402 payment transparently inside fetch().

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 Code
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 viem

mppx 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_KEY

Prefer not to put a private key in an env var? mppx account create stores 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