About the project

Inspiration

Every evening, independent bakers in Strasbourg throw away bread they could not sell. The people who would gladly buy it are at work, at home, or simply not looking at that moment. A markdown at 18:30 only reaches whoever happens to walk past. An app only reaches whoever opens it.

Agent payments change that. If my own agent can hold a signed instruction such as "an organic sourdough loaf, twice a week, under €5, after six", then I am present at every closing without being there. Read that way, an intent mandate is a limit order, and a city's leftovers are a market waiting to be cleared.

I audit payment systems at a bank, so I built the money side the way I would want to audit it: every hold, capture and payout tied to a signed event, and every dispute answered from that record.

What it does

Criée is a clearing house for what small food sellers have left at closing.

  • A wish, signed once. I describe what I want in my own words, in French or English. Criée reads it back as a sentence generated from the exact terms I am about to sign. I sign the SHA-256 of those terms with a passkey and save PayPal once through the Vault. My AI agent can draft wishes for me over MCP, but only I can sign them.
  • A shelf, read by its labels. At closing, the baker photographs the shelf and can add a voice note. Gemini lists each item with its count and price. A fact such as gluten-free is kept only when it points to the label crop it was read from, or to the baker's own words. The baker confirms each lot and picks a clearing mode.
  • A market that clears. A falling clock steps the price down until every unit is sold. A closing auction gives everyone one fair price. A group order goes ahead only if enough people commit.
  • PayPal settles each match. When a match clears, Criée authorizes the buyer's saved wallet with nobody at the screen. At the counter, the six-digit pickup code captures the hold and records the pickup on the PayPal order as tracking. PayPal Payouts pays the baker minus a 4% fee; in a measured sandbox run, the payout reached SUCCESS 26 seconds after sending. A no-show releases the hold, and the loaf goes back on the clock.
  • Forty holds at once. When a farm's group order reaches 40 boxes, Criée takes all 40 PayPal holds in one burst: 2.63 seconds in the fastest measured sandbox run, 4.9 seconds in the run shown in the video. Capture follows once enough successful holds fund the target. Below the funded target, nobody is charged.
  • Disputes answered from the ledger. Every step is an event in a signed, hash-chained ledger. When a buyer tells PayPal "I never ordered this", Criée sends one evidence pack through the Disputes API: the signed wish, the clearing round, the pickup scan and PayPal's own tracking record.
  • A console and an assistant for the seller. An AG Grid console lists every sale with its PayPal authorization and capture, refunds, payouts, disputes and the anonymised demand around the shop. The seller's assistant uses the PayPal Agent Toolkit to invoice a café and show the payment QR code. Refunds wait for the seller's tap.

How I built it

The engine decides, the model proposes. The clearing engine is a pure fold over ledger events. It reads only enums, integers, coordinates, timestamps and identifiers, and a test fails if any free-text field could reach it. Property tests generate random markets with revocations, refused holds, pickups and scheduler wake-ups, and check that units never oversell, that every match lies inside its signed wish, that every hold ends captured or voided, and that replaying the ledger reproduces every match. Each lot commits to a hashed seed when listed and reveals it when it closes, so every lottery can be replayed.

PayPal, end to end, with Criée as merchant of record. The JavaScript SDK v6 Save PayPal button opens PayPal's own approval screen without a purchase. Vault v3 setup and payment tokens; Orders v2 AUTHORIZE with vault_id and a merchant-initiated stored credential; Payments v2 capture, void and partial refund; order tracking with carrier OTHER ("Collected in store"); Payouts with batch status; Customer Disputes with provide-evidence; webhooks with signature verification and de-duplication; the PayPal Agent Toolkit for disputes and invoices. Each settlement mutation carries a stable PayPal-Request-Id, so a retry never takes a second payment.

One sale through Criée and the PayPal sandbox

One sale, end to end, as recorded on the ledger. Criée's steps on top, the PayPal calls below, with the times measured on the sandbox.

The 40 PayPal holds of one group order

The 40 PayPal holds of one group order, taken concurrently on the sandbox. All 40 were held 2.63 s after the burst started. Their individual measured durations sum to 90.78 s; that sum is a serial-time estimate, not a separately measured sequential run.

Gemini 3.7 Flash through the Gemini API, inside guardrails. Structured output for wishes, vision with box_2d boxes and audio for shelves, number-checked sentences for demand advice, and function calling with thought signatures for the assistant. Live calls stop past a daily cap, a per-visitor limit and a fixed end date. The demo inputs are recorded, and every component has a deterministic fallback, so the product runs with no key at all.

Stack. Python 3.12, FastAPI, Pydantic and SQLite with an Ed25519-signed hash chain; React, TypeScript and Vite with AG Grid and AG Charts; WebAuthn passkeys; the MCP Python SDK; ReportLab for evidence PDFs. The hosted demo runs on AWS EC2 (Graviton) behind a Cloudflare tunnel.

Evidence judges can inspect. The guided tour switches buyer and seller roles without a login. CI passes 82 tests on Python 3.12 and 3.13, with up to 500 generated examples per property, and drives the production buyer-to-payout journey in Chromium. The Ledger offers a quick recent-200 signature check and a full-chain audit with visible progress. The repository includes a threat model, and labels simulated actors, recorded AI responses and sandbox measurements.

Impact

I simulated a week of 12 bakeries and 10,000 residents in Strasbourg, 20 seeds, with Criée running through the real engine. With 3% of residents holding a bread wish, Criée cut leftover bread from 6.81 to 3.96 loaves per bakery-evening against a fixed markdown, and raised what each baker keeps from €15.27 to €26.75 (simulated). When every resident is assumed to see every markdown, Criée still wastes less (4.06 against 4.42 loaves) and earns more (€27.05 against €20.86), because the clock sells each loaf near its buyer's limit. Every assumption is listed in the repository.

Simulated loaves sold and wasted per bakery-evening

Simulated loaves sold and wasted per bakery-evening: a fixed markdown at 18:30 (A), a surprise bag (B) and Criée's falling clock (C). 12 bakeries, 10,000 residents, 20 seeds. Whiskers are 95% intervals of the mean.

Challenges I ran into

Charging a buyer who is not at the screen needs the right PayPal flow: a vaulted wallet, an authorization flagged as merchant-initiated and unscheduled, and an idempotency key per match. A group order needs every hold taken at the same moment, so the settlement worker fires them concurrently and records the burst time on the ledger. A model must never decide who pays what, so the engine's inputs are typed down to enums and integers, and the sentence a person signs is generated from those types rather than from model text.

Accomplishments that I'm proud of

  • One wish from a judge can complete the farm's group order. A measured sandbox run placed 40 authorizations in 2.63 seconds; capture followed after the funding target was met.
  • A dispute raised on PayPal's own site is answered automatically from the signed ledger, with PayPal's tracking record of the pickup in the evidence.
  • The same product runs with live keys, with no keys, and after the model budget ends, because every AI step has a recorded and a deterministic path.

What I learned

Standing wishes turn presence into a property of the buyer rather than of the moment. Once the wish is signed and the wallet is saved, PayPal's authorization and capture split fits a counter pickup exactly: the hold proves intent, the scan proves delivery.

What's next for Criée

Pilots with two bakeries and a farm near Strasbourg, multiparty settlement so sellers are paid directly as PayPal payees, and wishes that agents from other platforms can hold through the same MCP tools.

Built With

  • ag-charts
  • ag-grid
  • aws-ec2
  • fastapi
  • gemini
  • hypothesis
  • mcp
  • passkeys
  • paypal
  • paypal-agent-toolkit
  • paypal-disputes
  • paypal-orders
  • paypal-payouts
  • paypal-vault
  • paypal-webhooks
  • playwright
  • pydantic
  • python
  • react
  • reportlab
  • sqlite
  • typescript
  • vertex-ai
  • vite
  • webauthn
Share this project:

Updates

Submission history