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

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:

  1. 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.
  2. 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.
  3. Report - assemble the structured ExplanationResult and 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 /explain is 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 JSON POST /explain returns 402 with code: 'use_metered_sse'.
  • Async /explain/async is metered too, collected as the job runs. Submitting charges $0.01; each GET /result/:jobId poll 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 classic Mppx.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 at min(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.

LevelMeaningWhat to do
highProtocol and all major actors identified.Use the summary verbatim.
mediumSome addresses or protocol unknown. Summary may be incomplete.Treat as a hint; cross-check risks.
lowSparse 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