Swaps
A swap is a structured card-for-card proposal between two collectors: each side lists catalog cards (sku, condition, quantity), optionally records a cash difference, and adds a note. Cambridge TCG facilitates and records the swap; payment of any cash difference and shipping happen between the two collectors directly.
Where this lives in code. Data layer atapps/storefront/src/lib/swaps/. Pages at/account/swaps,/account/swaps/new,/account/swaps/[id]. Schema:drizzle/0109_swap_proposals.sql. Audit log:swap_lifecycle_log(append-only).
1. The v1 boundary — read this first
Version 1 is a witnessed handshake, not an escrowed trade. Concretely:
- Settlement is off-platform.The platform never holds your cards or your money. You post cards to each other; any cash difference is paid however the two of you choose (bank transfer, cash in hand). The “cash difference” field is a record of what you agreed, not a payment instruction the platform executes.
- No escrow, no verification. Unlike a market trade, nothing passes through CTCG for inspection. Carrier and tracking numbers you enter are your own records — the platform displays them but does not verify them.
- No trust-score coupling.Completing (or abandoning) a swap does not change either party's trust score in v1, and no
market_tradesrow is created — swap activity does not appear in trade counts, volume, or the market tape. - Disputes are between you. There is no swap dispute process in v1. The lifecycle record (who proposed what, when, who confirmed what) exists precisely so both parties keep an honest shared history — but the platform cannot recover cards or money that moved outside it. Swap with collectors you have reason to trust.
2. What the platform does record
- The proposal itself— both sides' card lists with condition and quantity, the recorded cash difference (sign convention: positive means the proposer pays), the note, and the response deadline.
- Name/image/price snapshots. Card names and images are resolved from the catalog by sku — never taken from the proposer — and indicative prices from the guidance sources, all frozen at proposal time (
snapshot_*columns) so the record stays legible even after the catalog or market moves. A sku the catalog can't resolve shows as the raw sku with no image. They are labelled “snapshot” on the swap page. - An append-only lifecycle log. Every transition — proposed, countered, accepted, declined, cancelled, expired, address entered, shipped, receipt confirmed, completed — is written to
swap_lifecycle_logwith who (or which automatic rule) did it. Entries are never edited. - Ship-to addresses, entered by each party after acceptance. Visible only to the swap's two participants, never on public surfaces, and never written into the lifecycle log.
3. The lifecycle
draft → proposed → accepted → shipping → completed, with three terminal branches (declined, cancelled, expired) and one supersession state (countered).
- Proposed. The recipient can accept, decline, or counter. A counter is a new proposal linked back via
counter_of; the original moves tocounteredand can no longer be acted on. - Expiry. Every proposal carries its own deadline. The proposer can pick one; the default is the recipient's response-window setting— a collector who has declared a slow cadence gets their declared window, not a platform constant. The expiry sweep reads each row's own
expires_at. - Accepted → shipping. After acceptance each party enters a ship-to address. When both are in, the swap moves to
shipping— that transition is automatic and logged as system-derived, not human-pressed. - Completed.Each party confirms the other's cards arrived. When both have confirmed, the swap completes (again system-derived from the two confirmations).
- Cancellation.Before acceptance, the proposer can withdraw unilaterally. After acceptance, cancellation is mutual: the first party's request is recorded, and the swap only cancels when the other party agrees.
4. Price guidance — indicative, never enforced
The composer and the swap page show an indicative value per side and a suggested cash difference. Per card, the figure is:
- the median of recent trades — up to the 10 most recent
market_tradesfor that sku in the last 90 days where money actually moved (completed / shipped / verified states); or, when no such trades exist, - the latest daily CTCG spot snapshot from
card_price_history; or, when neither exists, - nothing— the line shows as unpriced and the side's total says it understates. Missing data is never silently zeroed in.
Condition is not priced in: guidance is per-sku, so a HP copy shows the same indicative figure as an NM copy. That is a known v1 limitation — treat the numbers as orientation, not appraisal. Every figure carries a source label and an as-of time. The suggested cash difference is their side − your side; you are free to ignore it — the platform never blocks a lopsided swap.
5. The trust gate
Both parties must pass the same canTrade()gate used when placing a market order — checked once at proposal time and again at acceptance, at the value of the larger side plus the recorded cash difference. A suspended account, or a swap larger than either party's trade limit, can't propose or accept. When the cards involved have no price data, the gate value is correspondingly low — the gate is only as informed as the guidance underneath it.
6. Notifications and reach
Proposals, counters, accepts, declines, cancellations, shipping marks, receipt confirmations, and expiries each notify the other party in-app (subject to your Sabbath setting). v1 sends no swap emails.
A fresh proposal can only reach a collector the proposer could also message: the same block list and messages opt-out that gate direct messagesgate new swap proposals, and each collector can create at most 10 fresh proposals per hour. Counters inside an existing negotiation are exempt from the block gate — the counter's recipient opened that negotiation by proposing.
Known v1 gaps (named, not hidden)
- No dispute process; no platform recourse after cards/cash move.
- Guidance ignores card condition and thin markets can skew medians.
- Swaps don't build trust score yet — planned as a later, explicit change.
- No email notifications; in-app bell only.
- Tracking numbers are displayed as entered, not validated against carriers.