Skip to main content
Source: packages/oracle-runtime/src/plugins/oracle-payments/

Summary

Turns the user’s Matrix DM room with the oracle into a place they can discover, contract, and pay for work. The oracle plays two roles in the same room:
  • Support (free) — explains what the oracle sells, what it costs, and whether the user is contracted.
  • Work (paid) — performs one contracted service and hands over the result.
A cheap classifier routes every incoming Matrix message to one of the two. Once a paid job starts in a thread, routing is sticky for that thread until the job ends. Work only starts if the user has contracted the oracle on-chain; otherwise the oracle posts an interactive contract card into the chat and stays in support mode. Nothing here is a subscription. The user authorizes a quota of jobs at a per-job price ceiling, and each job is settled individually against a claim that an independent evaluator judges.

The moving parts

Agent Card

The list of services the oracle sells. It is a signed document anchored on the oracle’s entity as the #acard LinkedResource, and each service declares: Publish one with qiforge-cli agent-card. That command anchors the card on the entity, writes a local copy into the project, and sets AGENT_CARD_PATH in .env — worth keeping, because the local file self-describes the plugin manifest so the model knows its own services and prices without a tool call.
No card anywhere means no paid lane: the classifier stays off and the oracle behaves exactly as it did before. Support chat still works.

Contract

The user grants an on-chain SubmitClaimAuthorization over their claim collection, scoped to the services they picked, carrying:
  • a quota — how many jobs the oracle may bill for;
  • a max amount — the ceiling on a single job;
  • an intent duration — how long a reservation may sit before the chain releases it on its own. It defaults to 1 hour (DEFAULT_INTENT_DURATION_NS in @ixo/oracles-chain-client) and is set per grant at contracting time. The oracle reads this value back — it cannot set or shorten it.

Engagement

One paid job, keyed to a Matrix thread: thread = session = engagement = claim. Only one engagement can be active per user at a time — the chain permits one active intent per (agent, claim collection), so a second concurrent job could never reserve its payment. A work request while another job is running is refused at the gate and the oracle says which job is running and offers to wait or cancel.

Reservation (escrow)

When a job starts, the oracle reserves the service price on-chain (MsgClaimIntent). This is unconditional — there is no unreserved path and no flag. The reservation is released when the job is delivered, cancelled, or the intent duration lapses.

Delivery

deliver_work does two things at once: it puts the file in the room for the user, and it submits a work claim recording the service, the request, the work summary, the honest result status, the deliverable, and any proofs. An independent evaluation engine judges the claim against the service’s doneMeans. Approval releases the escrow to the oracle; rejection returns it to the user. Honesty is structural, not a matter of prompt discipline: the claim’s request and workSummary are extracted from the thread by a separate model the work agent does not control, and the evaluator inspects the real deliverable.

Cancellation

cancel_work submits a release claim — resultStatus: 'unable', no deliverables, the user’s reason in proofs. Submitting it frees the reservation immediately, so the user can start a new job right away; the evaluator then rejects it (deterministically, because there are no deliverables), which returns the escrow. A cancellation still costs one quota slot — the chain decrements quota on submit regardless of outcome.

How a user contracts

1

They ask what the oracle does

The classifier routes to support. The model calls list_services, which posts a service catalog card into the room.
2

They pick a service

The model calls show_contract with that serviceId. The card carries the price, the deliverables, and the doneMeans bullets, plus a Contract this agent action.
3

They approve the grant in the IXO Portal

The card opens the Portal’s agent-contract flow with the service preselected. Approving mints the SubmitClaimAuthorization grant and registers the contract with the evaluation engine.
4

The oracle notices immediately

The Portal posts an ixo.oracle.contracted event into the room. The oracle treats it purely as a cache-buster — it re-queries the engine rather than trusting the event — so “contract in the modal, then say go” works without waiting out a cache.
5

The next work request starts a job

The gate passes, the oracle reserves the price on-chain, the thread flips to work mode, and it stays there until deliver_work or cancel_work.
When the gate fails, the turn never errors — it proceeds in support mode with the reason attached, so the model can explain and offer the right next step:

Environment variables

Everything is optional — but the paid lane needs EVAL_ENGINE_URL. Read but not owned: ORACLE_ENTITY_DID (which entity’s card to resolve), ORACLE_DID + SECP_MNEMONIC + MATRIX_VALUE_PIN (claim signing), MATRIX_ACCOUNT_ROOM_ID (where the pending-claims index lives), NETWORK (price → denom), BLOCKSYNC_GRAPHQL_URL (card + evaluation reads) — all from the core base schema.

What it contributes

  • Tools: six, built per request. Which ones the model sees depends on the thread’s mode.
  • Sub-agents: none.
  • Middleware: none. (Routing happens on the Matrix ingress lane, before the graph.)
  • Nest modules: one — the contract-record lookup and its ixo.oracle.contracted cache-bust listener, the thread-engagement store, the contract gate, the escrow-first engagement start, the delivery lane, the registrar that plugs this plugin’s knowledge into the core message router, and a cron that reports each submitted claim’s evaluation outcome back into its thread.
  • HTTP routes: none.
  • Shared state: oraclePayments.services() and oraclePayments.engagement(roomId, threadId).

Delivering a file

deliver_work takes either written content (kind: 'text', materialized as markdown) or a file the oracle produced in the sandbox (kind: 'file', read from a path under /workspace/data/). Either way real bytes are attached to the claim — a claim whose deliverables do not resolve to bytes is rejected by design.

Matrix events

The plugin (and the core router) post ixo.oracle.component timeline events. The IXO Portal renders each component as an interactive card; clients without a renderer (Element, for one) show the plain-text body fallback. The Portal posts one event into the room: ixo.oracle.contracted, after a successful contract registration. The oracle treats it as an untrusted cache-buster only.

Deployment prerequisites

Chat and support work with none of this. Only the paid lane is gated.
1

Publish an Agent Card

qiforge-cli agent-card anchors the #acard LinkedResource on the oracle entity and writes the local copy AGENT_CARD_PATH points at. Without a card there are no services to sell.
2

Point EVAL_ENGINE_URL at an evaluation engine

It is the source of truth for “is this user contracted, for what, with how much quota left”. Unset means no user can ever pass the gate.
3

Make sure that engine accepts intent-backed agent-work claims

Every job reserves its payment before work starts and settles with useIntent: true. An oracle pointed at an engine that rejects those claims will lock escrow it can never settle.
4

Keep credits enabled

See below — “free support” means “no work claim”, not “free LLM”.

How this relates to credits

Two different lanes that do not overlap: Token metering is the credits plugin. With credits off there is no metering at all, so a commerce oracle should run with it on: the free support persona is then metered as ordinary LLM usage. “Free” describes the absence of a work claim, not the absence of cost.

Opt out / Opt in

When to use it

  • The oracle sells something concrete a user would pay for, delivered as a file or a document.
  • Users talk to the oracle over Matrix (the IXO Portal chat surface or a Matrix client).
  • You want the payment to depend on the work being judged good, not on the oracle asserting it was.

When NOT to use it

  • Metering LLM usage or enforcing a subscription — that is credits.
  • Oracles that only serve HTTP. Routing, engagements, and the cards are Matrix-side; over HTTP only the support tools bind, and the cards have no room to post into.
  • Free oracles with nothing to sell. Leave the plugin loaded and publish no card — it stays a no-op.

credits

The other billing lane — LLM usage metering.

sandbox

Where a file deliverable is produced and read from.

CLI reference

Every qiforge-cli command, including the entity the card is anchored on.

Environment variables

Every var in one table.