Skip to content

6. Proof before release

With escrow, the seller is paid only after it writes a result hash on chain: SubmitResult sets result_hash in the escrow datum to the SHA-256 of the response body it delivered. That is a public, signed commitment. ZyroPay holds it against an independent measurement: the MCP worker computes its own SHA-256 over the exact bytes your agent received, without trusting any hash the seller sent.

Whenever the API reads an escrow, it compares the two and returns the verdict as x402.proof on the request and escrow.proof on the escrow: {state, receivedHash, onChainHash, resultTx, checkedAt, message}. The verdict is stored, and each change of state is audited once.

Proof decision chart: no on-chain result before the deadline is pending; past the deadline is not_delivered; a result but no hashable body is unverifiable or pending; equal hashes are verified; different hashes are mismatch; mismatch recommends request_refund and not_delivered recommends withdraw_refund.Proof decision chart: no on-chain result before the deadline is pending; past the deadline is not_delivered; a result but no hashable body is unverifiable or pending; equal hashes are verified; different hashes are mismatch; mismatch recommends request_refund and not_delivered recommends withdraw_refund.
How the proof state is decided, and the refund it recommends.
State Means What you can do
pending No on-chain result yet, or no delivery report yet Wait, or request a refund
verified The SHA-256 of what your agent received equals the on-chain result_hash Nothing: the seller collects after unlockTime
mismatch They differ: the seller recorded one result and delivered another Recommended: request a refund
not_delivered submitResultTime passed with no result on chain Recommended: withdraw your refund
unverifiable The seller recorded a result, but the body could not be hashed (truncated or not text) Judge for yourself; a refund is still possible
receipt_only A direct payment: nothing is recorded on chain, only the receipt speaks Nothing to release

On mismatch or not_delivered, escrow.refund.recommended names the refund to take (request_refund, or withdraw_refund once it is possible) and refund.reason says why. The audit events are x402.proof_verified, security.x402_proof_mismatch, x402.proof_not_delivered and x402.proof_unverifiable.

The escrow payment on the previous page delivered a report; the seller then recorded its hash on chain, and ZyroPay’s received hash was equal: verified.

Open ee7b142e73…865361 on Cardanoscan (preprod) and check:

WhereWhat you should see
InputsThe escrow UTxO 630f2012…#0 (5 ADA, script) and a 20 ADA seller UTxO
ScriptOne Plutus V3 execution (the 10,003-byte vested_pay script, inline)
Output #05 ADA back to the escrow, same datum except the fields below
Datum: result_hashd80a4d02d13d08a45d4662e432c5a5a6550ac9c2a093549f5dbbd0c6afbfbfce
Datum: stateResultSubmitted (constructor 1); seller cooldown set
CollateralThe same 20 ADA seller UTxO, with a 15 ADA collateral return
Fee0.688128 ADA (11,052-byte transaction, paid by the seller)

The hash in the datum, d80a4d02d13d08a45d4662e432c5a5a6550ac9c2a093549f5dbbd0c6afbfbfce, is the same value the seller’s delivery receipt signs as contentSha256, so the receipt and the chain agree with each other as well as with the buyer.

A live mismatch, using the seller’s deliberately dishonest demo mode, is awaiting its run. The comparison and every state are covered offline Tested offline.

  • That the content is good. A seller that commits to a useless report and delivers exactly that report is verified. The proof is about integrity: you received what the seller committed to.
  • Anything before the escrow is read. The verdict is computed when the API reads the escrow, for example when you or the agent open the request; nothing pushes it to you.
  • Release. ZyroPay does not block the seller’s withdrawal on chain: after unlockTime the script lets a seller with a result withdraw unless a refund was requested. The proof tells you, before that time, whether to request one.

Code: apps/api/src/services/escrow-view.ts, packages/shared/src/domain/requests.ts (X402ProofStateSchema), apps/mcp/src/mcp/x402-delivery.ts.