Core concepts
Mental models for using oculr effectively - the analysis pipeline, MPP/x402 payments, confidence levels, and sync vs async.
The analysis pipeline
oculr runs a three-phase pipeline for every transaction:
- Pre-flight - fetch the trace, detect the chain, resolve address labels and contract source. Runs in parallel before any LLM iteration so the agent starts with as much context as possible.
- Agent loop - Claude orchestrates tool calls to resolve unknowns: token prices, wallet labels, contract behaviour, web context for novel protocols. The agent picks the tool set per transaction based on what it sees in the trace.
- Report - assemble the structured
ExplanationResultand return it.
The depth of analysis scales with the transaction: a simple transfer resolves in one or two passes; a novel exploit may take several.
MPP/x402 payments
oculr charges per request - no API key, no signup. When you call the API without an active payment session, the server returns 402 Payment Required with a payment challenge. The mppx client handles the whole exchange on Tempo transparently.
You can read the protocol spec at mpp.dev. For oculr specifically:
- Payment uses MPP sessions (the protocol's session intent). Your client opens a payment channel against the API with
maxDeposit(a per-channel cap), signs cumulative vouchers, and the server redeems the highest voucher on-chain. One settlement covers many requests. - Sync
/explainis metered. Vouchers are signed incrementally during the analysis as cost accrues, so it requires a session client consuming SSE -tempo.session.manager().sse(). A plain JSONPOST /explainreturns402withcode: 'use_metered_sse'. - Async
/explain/asyncis metered too, collected as the job runs. Submitting charges $0.01; eachGET /result/:jobIdpoll collects what has accrued since your previous poll, and the first poll after the job finishes charges the true-up - the cumulative total matches the sync SSE price exactly. Every call works with the classicMppx.create()+fetch()pattern, so keep the polyfill active for the polls too: 90 seconds with nothing collected aborts the run. See Pricing. - Your spend cap is the client's
maxDeposit. It is a signing ceiling, not the amount escrowed - two different numbers. The channel opens atmin(suggestedDeposit, maxDeposit)and oculr suggests $16, so the recommended cap of'32'still escrows $16. Escrowed is not spent: unused deposit is refunded on close, and the headroom above the deposit is what lets you top the channel up between analyses. See The channel deposit. - Settlement is in USDC.e on Tempo. Token contract
0x20C000000000000000000000b9537d11c60E8b50.
Confidence levels
Every result carries a confidence rating. Branch on it.
| Level | Meaning | What to do |
|---|---|---|
high | Protocol and all major actors identified. | Use the summary verbatim. |
medium | Some addresses or protocol unknown. Summary may be incomplete. | Treat as a hint; cross-check risks. |
low | Sparse trace or mostly unknown contracts. | Investigate further or escalate. |
const { confidence, summary, risks, txType } = result
if (confidence === 'high') {
// Use the summary verbatim in your output.
} else if (confidence === 'medium') {
// Cross-check risks before acting.
} else {
// Escalate or investigate.
}Partial results
oculr never 5xx's mid-analysis. When an upstream service (RPC, the model provider, etc.) has a transient issue, you get HTTP 200 with confidence: 'low' and a summary that starts with "Partial result" and explains which phase failed - real but incomplete. Always check for that before trusting the body.
const result = await res.json()
if (result.summary.startsWith('**Partial result')) {
// The summary names the failed phase. A failed trace fetch is usually
// safe to retry after 30s; a failed agent loop is safe to retry once.
return handlePartial(result)
}Sync vs async
Sync (POST /explain, SSE) - streams until complete; the final { type: 'result' } event is the full result. Metered pricing, requires tempo.session.manager().sse(). Use when you want the result in one call, live progress, or exact metered cost.
Async (POST /explain/async + GET /result/:jobId) - returns a jobId immediately; plain fetch() works with the Mppx.create() polyfill active. Metered to the same total as sync: $0.01 at submit, the rest collected by your polls as the analysis accrues it. Use for UIs, batch processing, sub-agent loops, and anywhere with a short HTTP timeout. Poll every 5-15 seconds with the polyfill active - 90 seconds with nothing collected aborts the run; results expire after 1 hour.
// Sub-agent friendly - non-blocking start. Requires an active mppx polyfill:
// the submit and every poll settle payment as the analysis accrues cost.
const { jobId } = await fetch('https://mpp.oculr.xyz/explain/async', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ txHash }),
}).then(r => r.json())
// Poll until complete - each poll collects what has accrued since the last one.
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') return job.result
if (job.status === 'error') throw new Error(job.error)
}SSE streaming (web UIs)
SSE is how sync POST /explain works: set Accept: text/event-stream and each agent step emits its own SSE message - preflight_start, preflight_done, preflight_status, iteration, agent_text (token-by-token reasoning), tool_call, tool_result, skill_call, tokens, complete, then a final result (JSON) or report (HTML); fatal failures arrive as an error frame. The stream is also what makes metered payment possible - vouchers renew as cost accrues.
If you don't want to consume a stream, use the async path - single-event polling is easier to integrate into a tool-use loop, it's metered to the same total, and plain fetch() works.
Related
- Under the hood - how oculr buys traces, labels, and compute over MPP per request
- Glossary - definitions for every term used across oculr
- Endpoints reference - full API surface with request/response schemas
- Use as an agent - sub-agent integration with
/tool-spec.json