# Series 1 Draw-Slot Curve

The Spaniel Syndicate Series 1 primary sale uses a deterministic
public price curve over **draw slots** 1..1,000,000,000 inclusive.
Every card sold in Series 1 — whether through a single pull, a
pack, a box, a crate, or a vault — consumes one or more contiguous
draw slots whose price comes from this curve.

No auctions. No reserves. No presale. No allocation. No off-curve
slots.

This document is the operator-visible contract for the curve. The
canonical config is [`config/curve.json`](../config/curve.json);
the canonical math is [`js/lib/curve.js`](../js/lib/curve.js);
tests are [`tests/curve.test.mjs`](../tests/curve.test.mjs).

---

## 1 — Phases

**Six** contiguous power-curve phases cover the full release.
Boundary prices match exactly between phases (no jumps). The final
69,420 draw slots are split between the `phase_5_final_69420` tail
and the `phase_6_final_4200` spectacle ramp ending at **$6,942,000,000**.

| Phase | Draw slots | USD start → end | Exponent |
|---|---|---|---:|
| `phase_1_mass_rip` (Mass Rip) | 1..690,000,000 | $0.069 → $0.69 | 1.05 |
| `phase_2_pack_season` (Pack Season) | 690,000,001..969,000,000 | $0.69 → $4.20 | 1.15 |
| `phase_3_hobby_heat` (Hobby Heat) | 969,000,001..996,900,000 | $4.20 → $69.00 | 1.30 |
| `phase_4_whale_rips` (Whale Rips) | 996,900,001..999,930,580 | $69.00 → $6,942.00 | 2.00 |
| `phase_5_final_69420` (Final 69,420) | 999,930,581..999,995,800 | $6,942.00 → $69,420,000.00 | 2.70 |
| `phase_6_final_4200` (Final 4,200) | 999,995,801..1,000,000,000 | $69,420,000.00 → $6,942,000,000.00 | 3.13 |

Total draw slots: 1,000,000,000 (= total Series 1 card supply). The
final 69,420 draw slots (phases 5 + 6) and the final 4,200 of those
sell through the same public curve — no auctions, no reserves, no
allocation. The final tail is spectacle, not a promise of value.

Per-card pricing near the final tail (sampled):

| Draw slot | Approx price |
|---|---:|
| 999,930,581 | $6,942 |
| 999,995,800 | $69,420,000 |
| 999,995,801 | $69,420,000 |
| 999,999,000 | ~$1.4B |
| 1,000,000,000 | $6,942,000,000 |

### Formula

For draw slot `s` in a phase `[A, B, exponent]` covering
`startSlot..endSlot`:

```
p = (s - startSlot) / (endSlot - startSlot)
price = A + (B - A) * p^exponent
```

`linear` and `exponential` curveTypes are also supported but unused
in Series 1.

---

## 2 — Config schema

```jsonc
{
  "version":          "2026-05-17",
  "seriesId":         "s1",
  "totalDrawSlots":   1000000000,
  "phases": [
    {
      "id":                 "phase_1_mass_rip",
      "label":              "Mass Rip",
      "publicDescription":  "Ultra-cheap mass collector phase.",
      "startDrawSlot":      1,
      "endDrawSlot":        690000000,
      "startPriceUsd":      0.069,
      "endPriceUsd":        0.69,
      "curveType":          "power",
      "exponent":           1.05
    },
    ...
  ]
}
```

Validated by `validateCurveConfig(curve)`; throws on:

- Empty phase list.
- `totalDrawSlots ≠ TOTAL_DRAW_SLOTS` (1,000,000,000).
- First phase doesn't start at 1, or last phase doesn't end at
  1,000,000,000.
- Gap or overlap between adjacent phases.
- Non-integer slot bounds.
- Out-of-range slot bounds.
- Non-positive prices, or `endPriceUsd < startPriceUsd`.
- Unknown `curveType` (only `linear` / `power` / `exponential`
  allowed).
- `power` without a positive exponent.

---

## 3 — Pricing flow

For each product purchase the Worker computes:

```
1. Determine startDrawSlot from the next-available pointer.
2. drawSlotsConsumed = product.drawSlots * quantity
   endDrawSlot       = startDrawSlot + drawSlotsConsumed - 1
3. stickerUsd        = Σ priceUsdForDrawSlot(s, curve)
   stickerLamports   = usdToLamports(stickerUsd, solUsdPrice)
4. bundleDiscount    = stickerLamports * product.bundleDiscountBps / 10000
   subtotalLamports  = stickerLamports - bundleDiscount
5. (if valid referral)
   referralDiscount  = subtotalLamports * 1000 / 10000
   buyerPays         = subtotalLamports - referralDiscount
6. (no referral)
   referralDiscount  = 0
   buyerPays         = subtotalLamports
7. rescueContribution = buyerPays * 690 / 10000     (always; 6.9% Rescue Pool)
   postRescue         = buyerPays - rescueContribution
8. (if valid referral)
   commission        = postRescue * affiliateRateBps / 10000
   treasury          = postRescue - commission
9. (no referral)
   commission        = 0
   treasury          = postRescue
```

The Rescue Pool cut is always carved out of the buyer-paid amount.
See `docs/RESCUE_POOL.md` for the full Rescue Pool contract.

See `js/lib/products.js::computeProductQuote` and tests in
`tests/products.test.mjs` for the canonical implementation.

The frontend mirrors the math for display only. The Worker signs
the quote; the on-chain program verifies the signature, the expiry,
and the treasury pubkey.

---

## 4 — Dry-run

```bash
node scripts/curve-dry-run.mjs --sol-usd=150
```

Output includes:

- Series 1 print-run summary.
- Per-phase summary + theoretical gross.
- Price at key draw slots.
- Product pricing (Single / Pack / Box / Crate / Vault) at sample
  start slots.
- Pack pricing with/without referral.
- Affiliate commission rate ladder.
- Pull odds by product size.

The script is deterministic from `config/curve.json` + `js/lib/*`.

---

## 5 — Cross-references

- [`config/curve.json`](../config/curve.json) — curve definition.
- [`js/lib/curve.js`](../js/lib/curve.js) — math + validation.
- [`tests/curve.test.mjs`](../tests/curve.test.mjs) — invariants.
- [`docs/SERIES_1_PRINT_RUN.md`](./SERIES_1_PRINT_RUN.md) — supply.
- [`docs/PACKS_BOXES_CRATES.md`](./PACKS_BOXES_CRATES.md) — products.
- [`docs/AFFILIATE_PROGRAM.md`](./AFFILIATE_PROGRAM.md) — referral math.
- [`docs/MANIFEST_COMMITMENT.md`](./MANIFEST_COMMITMENT.md) — virtual manifest.
- [`scripts/curve-dry-run.mjs`](../scripts/curve-dry-run.mjs) — verifier.
