Skip to main content
Special EditionFull Stack

"CardBridge" — Card Discount Marketplace

Live capture

A marketplace that connects requesters who want a specific card discount with cardholders who own that card and fulfill the purchase for a fee. Features escrow-protected transactions, a database-enforced state machine with an immutable event log, KYC verification with tiered transaction limits, dispute resolution, and an admin review panel — all secured with Supabase Row Level Security.

Tech Stack
ReactTypeScriptSupabasePostgreSQLRow Level SecurityEscrowKYCTailwind CSS
My role
Solo — schema, Row Level Security, state machine, React front end
Built for
Personal project · Jun–Aug 2026

“When money moves, the rules belong in the database — not in whichever client happens to call it.”

I.

The Brief

You find a great card discount on something you want, but you don’t own that card. Someone else does and would buy it for you for a fee — but neither of you can trust the other with the money.

That makes CardBridge a trust problem more than a UI problem: requesters need to know they will get the product, cardholders need to know they will be paid, and the platform needs every transition to be auditable.

II.

The Approach

I put the business rules in Postgres. A transaction moves through MATCHED → FUNDED → PURCHASED → SHIPPED → DELIVERED → RELEASED (plus CANCELLED, DISPUTED and REFUNDED), and a BEFORE-UPDATE trigger checks every change against a legal-transition map, the caller’s role and the invariants before it is allowed. An AFTER-UPDATE trigger writes an immutable event log, syncs the request’s status and credits the user’s tier.

Trust is layered on top: a KYC lifecycle (UNVERIFIED → PENDING → VERIFIED/REJECTED) with private document storage, tiered transaction limits enforced in both the UI and the database, disputes that freeze escrow, and an admin panel gated by RLS that resolves them by release, refund or a request for more information.

III.

How It Works

  1. 01Requesterposts a request, funds escrow
  2. 02MatchingOPEN requests × cardholder’s cards
  3. 03State machinePostgres triggers + RPC
  4. 04Event logimmutable, per transition
  5. 05Realtimenotifications to both sides
IV.

Key Decisions

The database is the referee

Transitions go through a transition_transaction RPC and are validated by triggers, so a buggy or malicious client cannot skip a state, self-deal, or exceed a tier cap — the database refuses.

RLS on every table

Only the anon key ships to the browser; Row Level Security scopes every read and write to the people in a transaction. A guard trigger blocks users from promoting their own profile privileges.

Timeouts on two clocks

A configurable timeout policy runs on page load and on a scheduled Edge Function (escrow-timeout-sweep), and disputed transactions are skipped by every automatic action.

Fourteen ordered migrations

The whole schema — enums, RLS, views, state machine, storage buckets, reviews and notifications — lives as versioned SQL, including the fixes (an enum cast, and breaking an RLS recursion between requests and transactions).

V.

Learned the Hard Way

BrokePostgres can’t use a new enum value in the same transaction that adds it.

FixedAdding MATCHED and SHIPPED got its own migration that must commit before the state machine’s.

BrokeThe SELECT/UPDATE policies on requests and transactions referenced each other and recursed.

FixedBroke the cycle with SECURITY DEFINER helper functions.

6
steps from match to release
14
SQL migrations
3
private storage buckets
6
build phases, feature-complete
Known limits — stated plainly
  • Escrow and KYC are stubbed integrations — the lifecycle is real, the payment and identity providers are not.
  • There is no automated test suite in the repository yet.
  • The first admin must be set by hand in SQL; there is deliberately no UI for self-promotion.