Inspiration

Honestly, I just wanted to build something engaging for people around the world. Then I started learning the differences between the AWS databases. Reading the Aurora DSQL docs, I hit the part about optimistic concurrency — two writes touch the same row, race, and one just gets rejected at commit time — and thought: that's a winner and a loser. That's what gave me the idea of an auction. A traditional auction isn't engaging enough on its own, but a reverse — or Dutch — auction is (the price falls until someone grabs it).

They've run since the 1600s flower markets, and they work for one reason: everyone sits in the same room watching everyone else flinch. Online, that room is gone. You stare at a falling number with no idea what anyone else is thinking — so you either grab too early and overpay, or wait and watch it vanish. I wanted to put the room back, except the room is the whole internet and the referee is a database that physically can't pick two winners. The one thing I really wanted to add: make the information loop visible and accurate in real time, so genuinely interested bidders get a better shot at securing their claim. And again, it's Aurora DSQL's strong consistency that makes that possible.

What it does

A price falls, every second, the same number for everyone watching — anywhere on Earth. Anyone can claim it, but only one person ever lands it.

The part I'm proudest of is the information. You don't bid blind. The seller drips three highlights during the countdown, and you "arm" yourself by voting on each. Everyone sees the live armed count — how many rivals are fully loaded and ready to pounce versus just warming up. That count is your read on the room: ten people armed means if you want this, you move now, before the price drops any further and someone beats you to it. A blind guess becomes a timed decision.

And demand bites back. The more people arm, the slower the price falls — visible demand literally holds the price up, so hot items stay expensive and the seller earns more. If nobody claims before the floor, the seller already chose what happens: a lottery among the fully-armed bidders, or pull it and re-list it. Listing itself takes about ten seconds, and the AI drafts the highlight description.

How we built it

  • The price never goes in the database. It's a pure function — start, floor, duration, the demand brake, server clock — that every browser computes itself. A million people watching is a million calculators, not a million queries.
  • The claim is the one line that matters: UPDATE auctions SET winner_user_id = ... WHERE winner_user_id IS NULL AND status = 'live'. A pile of people fire it in the same instant; DSQL commits exactly one and aborts the rest on a concurrency conflict. We don't catch that abort and retry — we show it. The abort is the loss screen.
  • A double-entry ledger settles every sale (buyer debit, seller credit, platform fee), and the debit lives inside the claim transaction — so you can't win something you can't pay for.
  • Votes are insert-only rows, deduped by a unique index; armed counts aggregate on read. No two voters fight over a shared counter, so the high-traffic path never conflicts. Tier claim delays are enforced server-side, before any transaction opens.
  • Listing copy comes from fal's any-llm endpoint (Gemini Flash). For demos, tooling spins up real bot bidders, so the DSQL race is genuine even when I'm the only human testing.
  • Scaffolded in v0; Next.js 14 on Vercel; an IAM-authenticated DSQL connection pool through the AWS DSQL signer.

Challenges we ran into

  • DSQL looks like Postgres but it isn't: no foreign keys, no SERIAL, no triggers, one schema change per transaction, async index builds. We stopped fighting it — pushed referential integrity and "triggers" into the app, and ran migrations one auto-committed statement at a time.
  • My favourite bug: the price looked frozen. Not slow — stopped. Turned out the client re-synced its clock offset on every render, pinning "now" back to the page-load instant. Capture the offset once at mount, and it fell like it should. An hour lost to a number that wouldn't move.
  • One we did to ourselves: we enforced the per-tier claim delay by sleeping inside the claim transaction. That pinned a pooled connection for five seconds and widened the conflict window — exactly backwards. Moving the wait outside the transaction fixed it.
  • Keeping "visible demand" honest: the counts are real votes, and the brake is a real parameter in the price function with an effective-from timestamp — not a number we invented to look busy.

What we learned

The big one: a database error can be a feature. We wrote zero "who won" logic, because OCC already answers that — atomically, every time. We just show the abort instead of swallowing it. And determinism beat coordination: a pure price function killed any need for web sockets and made the price provably identical for everyone. Strongly-consistent SQL — the kind DSQL gives you, and scales active-active across regions — turns out to be exactly the primitive a fairness-critical marketplace needs, and it's rarer than you'd think. (We run a single us-east-1 cluster for the demo; the guarantee is identical, we're just not paying for three regions to prove a hackathon point.)

What's next

Real money over the same ledger, via Stripe Connect — so the platform commission on every completed sale becomes real revenue, not demo coins. A live map of where demand is arming around the world. And seller analytics showing exactly where on the falling curve their claims land — because that number is the entire business.

Built With

Share this project:

Updates