# Official Marketplace

The Spaniel Syndicate official marketplace lets owners list, delist,
change prices, buy, and make/accept standing offers on Spaniel
cards and sealed products at `spanielsyndicate.com/market`. Every
trade pays a flat **2% platform fee**. Of that 2%, **6.9% goes to
the community-directed Rescue Pool** (see `docs/RESCUE_POOL.md`);
the remaining 93.1% is split 50/50 with the referrer if a valid
Barklink attribution exists on the buyer side (or the bidder side,
on standing-offer acceptance), otherwise routed entirely to the
treasury.

Seller still pays only the 2% platform fee — the Rescue Pool cut is
carved out of the platform fee, not stacked on top.

This is a **platform fee**, not a collection royalty. Metaplex Core
royalties on Series 1 assets remain at 0%.

Math: [`js/lib/marketplace-math.js`](../js/lib/marketplace-math.js).
Tests: [`tests/marketplace-math.test.mjs`](../tests/marketplace-math.test.mjs).

---

## 1 — Tradable assets

| Asset | Listable | Buyable | Offer-matchable |
|---|---|---|---|
| Revealed Series 1 card NFT | yes | yes | yes |
| Sealed pack | yes | yes | no (predicates target cards) |
| Sealed box | yes | yes | no |
| Sealed crate | yes | yes | no |
| Sealed vault | yes | yes | no |
| Mythic 1/1 | yes | yes | yes |

Listings are scoped by `assetType ∈ {card, sealed_product}`. The
marketplace UI filters by type.

---

## 2 — Fee math (BigInt lamports)

```
salePrice           = listing price (or offer price on accept)
marketFee           = floor(salePrice * 200 / 10000)        # 2.00%
sellerReceives      = salePrice - marketFee

# Rescue Pool carved from the 2% platform fee (always).
rescueContribution  = floor(marketFee * 690 / 10000)        # 6.9% of fee
postRescueFee       = marketFee - rescueContribution

# With valid referral attribution:
referralFee         = floor(postRescueFee * 5000 / 10000)   # 50% of post-rescue
# Packline upstream sponsor overrides — carved from postRescueFee
# when L2..L5 ancestors are present (4% / 3% / 2% / 1% respectively).
# Missing ancestors at a level → that level's lamports stay with
# treasury. Packline pays only when a valid direct referrer exists.
packlineL2          = floor(postRescueFee * 400 / 10000)
packlineL3          = floor(postRescueFee * 300 / 10000)
packlineL4          = floor(postRescueFee * 200 / 10000)
packlineL5          = floor(postRescueFee * 100 / 10000)
packlineTotal       = Σ packlineL{2..5} for present ancestors
treasuryFee         = postRescueFee - referralFee - packlineTotal

# Without referral:
referralFee         = 0
packlineTotal       = 0
treasuryFee         = postRescueFee
```

See [`docs/PACKLINE.md`](./PACKLINE.md) for the upstream chain rules
(no self-sponsor, no cycles, no marketplace transferability).

The holder-tier affiliate rate does NOT affect the marketplace
split — the referrer share is always 50% of the post-rescue
marketplace fee.

Canonical:
[`js/lib/marketplace-math.js::computeMarketplaceFee`](../js/lib/marketplace-math.js).

---

## 3 — Listing flow

```
1. Seller visits marketplace, picks an owned card/sealed product.
2. Seller signs a list order (price + asset + nonce + expiry).
3. Worker stores the listing in market_listings (status=ACTIVE).
4. Buyer (or anyone) browses market.html, filters, finds the listing.
5. Buyer clicks Buy → POST /api/market/buy/quote.
6. Worker returns a signed buy quote with the fee split.
7. Buyer signs single transaction.
8. Series 1 Program verifies signature + expiry + treasury + listing state.
9. Series 1 Program atomically:
   - transfers asset → buyer
   - transfers (salePrice - marketFee) → seller
   - transfers referralFee → referrer (if any)
   - transfers treasuryFee → treasury
10. Worker records trade in market_trades + decrements listing.
```

The marketplace UI shows the buyer-pays amount, the seller-receives
amount, the marketplace fee, and (when applicable) the referrer
share — all formatted in SOL. The values are read from the signed
quote, not computed client-side.

---

## 4 — Standing offers

See [`docs/STANDING_OFFERS.md`](./STANDING_OFFERS.md) for the full
predicate schema and the accept flow.

Standing offers can match by:

- `exactCardKey`, `dogId`, `variant`, `rarityGroup`, `serialNumber`,
  `stampType`, `plateColor`, `isMythic`, `minVariantRank`,
  `maxVariantRank`.

Offers may target cards that have NOT been minted. On accept, the
Series 1 Program verifies the card matches the predicate and atomically
settles. Offer escrow holds the bidder's SOL; on cancel/expiry the
SOL returns to the bidder.

Accept settles through the same 2% fee + 50/50 referral split as a
normal marketplace buy. The referral attribution belongs to the
offer maker (the bidder).

---

## 5 — Listing & buy endpoints

| Endpoint | Purpose |
|---|---|
| `GET /api/market/config` | Marketplace fee bps + referrer share bps + market paused flag |
| `GET /api/market/listings` | Listings (filterable) |
| `GET /api/market/listings/:id` | Listing detail |
| `POST /api/market/list/quote` | Signed list order |
| `POST /api/market/list` | Record on-chain listing creation |
| `POST /api/market/delist/quote` | Signed delist order |
| `POST /api/market/delist` | Record on-chain delisting |
| `POST /api/market/change-price/quote` | Signed change-price order |
| `POST /api/market/change-price` | Record on-chain price change |
| `POST /api/market/buy/quote` | Signed buy quote with fee split |
| `POST /api/market/buy/record` | Record on-chain settle |
| `GET /api/market/activity` | Recent trades feed |
| `GET /api/market/stats` | Volume, listing counts, etc. |

For offers:

| Endpoint | Purpose |
|---|---|
| `GET /api/offers` | Standing offers (filterable by predicate fields) |
| `GET /api/offers/:id` | Offer detail |
| `POST /api/offers/create/quote` | Signed offer creation |
| `POST /api/offers/create` | Record on-chain offer escrow |
| `POST /api/offers/cancel/quote` | Signed cancel |
| `POST /api/offers/cancel` | Record on-chain cancel |
| `POST /api/offers/accept/quote` | Signed accept |
| `POST /api/offers/accept` | Record on-chain accept |
| `GET /api/dogs/:dogId/offers` | Standing offers covering this dog |
| `GET /api/cards/:cardKey/offers` | Standing offers covering this exact card |

See [`docs/WORKER_API_CONTRACT.md`](./WORKER_API_CONTRACT.md) for
full request/response shapes.

---

## 6 — Filters

The marketplace UI supports filtering listings + offers by:

- Dog ID
- Dog name (search)
- Variant slug
- Rarity group
- Serial / print count
- Stamp type / plate color
- Mythic flag
- Asset type (revealed card vs sealed product)
- Sealed product type (pack/box/crate/vault)
- Price range
- Recently listed / recently filled / recently revealed
- Offers available on the card

---

## 7 — UI rules

- Always show the **buyer pays** + **seller receives** explicitly.
  Never hide the 2% fee or quote a "net" price without the breakdown.
- Show the **referrer share** if there is a valid referral
  attribution; otherwise omit (don't show "0.00 SOL referrer
  share").
- Sealed-product listings show:
  - Product type + draw slot range.
  - Estimated pull odds for the product size.
  - Bundle discount that applied at the original sale.
- Standing-offer rows show the predicate description ("any mythic",
  "dog #215 gold-tier", etc.) generated via
  `js/lib/offers.js::describePredicate`.
- "Accept offer" CTAs render only when the connected wallet owns a
  card matching the predicate AND the offer state is `ACTIVE`.

---

## 8 — Cross-references

- `js/lib/marketplace-math.js` — fee math.
- `js/lib/offers.js` — predicate matching.
- `tests/marketplace-math.test.mjs` — invariants.
- `tests/offers.test.mjs` — predicate tests.
- `docs/AFFILIATE_PROGRAM.md` — referral attribution.
- `docs/STANDING_OFFERS.md` — offer schema + accept.
- `docs/PACKS_BOXES_CRATES.md` — sealed-product lifecycle.
- `docs/ONCHAIN_MINT_REQUIREMENTS.md` — Series 1 marketplace handlers.
