Escrow a deal end to end

One agent buys from another: lock the funds, deliver, attest, and watch the contract settle it.

The full path of a conditional deal, sandbox or production — the shape is the same. The buyer's agent locks funds against a condition; the seller delivers; the contract settles. Saifuro evaluates and records, and the funds never touch us.

Create the deal

curl -X POST https://api.saifuro.com/v1/escrows \
  -H "Authorization: Bearer ak_live_..." \
  -d '{
    "seller": "agt_datamart_02",
    "amount": 1250.00,
    "currency": "USDC",
    "chain": "base",
    "condition": {
      "type": "attestation",
      "attestor": "role:procurement_lead",
      "deadline": "2027-09-15T00:00:00Z"
    }
  }'

Two things happen in order. First the full policy evaluation — if 1250.00 breaks the buyer's mandate, the response is that denial and no contract exists. Then the contract deploys, and the response is an escrow in state created, carrying decision (the authorization that allowed it) and contract — the on-chain address, which either side can watch on the chain itself without asking us.

Fund it

The buyer's funds go to the contract address. When they arrive, the state moves to funded and the escrow.funded webhook fires. From this moment the outcome is decided by the condition, not by anyone's goodwill: attested by the deadline means release, anything else means refund.

Listen, don't poll

Every transition arrives as a webhook — an ES256 JWT, verified the same way as a verdict:

import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://verdicts.saifuro.com/.well-known/jwks.json'),
);

app.post('/saifuro', express.text({ type: 'application/jwt' }), async (req, res) => {
  const { payload } = await jwtVerify(req.body, JWKS, {
    issuer: 'https://verdicts.saifuro.com',
    algorithms: ['ES256'],
  });
  if (!seenBefore(payload.jti)) {
    handle(payload.type, payload.data); // escrow.funded, escrow.delivered, ...
  }
  res.sendStatus(200);
});

jti stays the same across redeliveries, so the seenBefore check makes the handler idempotent.

Deliver and attest

The seller does the work. The named attestor — and only the named attestor — confirms it:

curl -X POST https://api.saifuro.com/v1/escrows/esc_2f8d/attest \
  -H "Authorization: Bearer sk_live_..."

State moves to delivered, release runs on the contract, escrow.released fires. The 0.25% escrow fee is charged now, at release — not before.

When it goes wrong

The seller misses the deadline: nothing to do. The contract refunds the buyer, escrow.refunded fires, and the failed deal carries no escrow fee and no settlement fee.

The buyer disputes mid-deal: POST /v1/escrows/esc_2f8d/dispute sets the flag and fires escrow.disputed — and changes nothing else. Without an arbiter the deadline still decides, which is the neutral default; a deal that wanted a judgment should have named an arbiter before funding. Both sides keep the decision log and the signed verdict, so the disagreement stays reconstructable either way.

What to build on your side

A webhook endpoint with the JWT check above, a state column on your deals keyed by escrow id, and handlers for the five events. There is no release call to build and no refund call to forget — the contract owns both ends, and your integration only ever observes and attests.

On this page