# Skill: discover & vet sales (read-only)

Use when the user wants to find a launch, list active sales, or check whether a sale is
legitimate and matches their intent. **No funds are spent here.**

## List all sales

```
n = factory.allSalesLength()
for i in [0, n):
  saleAddr = factory.allSales(i)
  v = Sale(saleAddr).getSaleData()
```

`getSaleData()` returns one struct — decode by the field order in
`reference/launchpad-spec.md` (or `SALE_VIEW_KEYS`). Key fields:

| field | meaning |
|---|---|
| `saleToken`, `quoteToken` | token being sold / asset paid in (`0x0` = native HYPE) |
| `pricingMode` | 0 = FIXED (caps in quote) · 1 = ORACLE_USD (caps in USD) |
| `priceFeed`, `tokenPriceUsd` | ORACLE_USD: quote→USD feed + USD/token (feed decimals); 0 in FIXED |
| `tokensPerQuoteUnit`, `quoteDecimalScale` | FIXED price math (see formula below) |
| `softCap`, `hardCap`, `minBuy`, `maxBuy` | in the **cap unit**: quote decimals (FIXED) or USD feed decimals (ORACLE_USD) |
| `startsAt`, `endsAt` | unix seconds |
| `merkleRoot` | non-zero ⇒ whitelist required (single-phase; multi-phase uses per-phase roots) |
| `tgeBps`, `cliffDuration`, `vestingDuration` | buyer vesting |
| `lpAdapter`, `lpBps` | LP routing (non-zero ⇒ liquidity will be seeded & locked) |
| `state` | 0 Pending · 1 Active · 2 Ended · 3 Finalized · 4 Cancelled (multi-phase: a gap reads as Pending) |
| `totalRaised`, `totalTokensSold`, `saleAllocation` | progress (`totalRaised` is QUOTE) |
| `totalRaisedUsd` | ORACLE_USD: cumulative USD raised (the cap-progress unit in oracle mode) |
| `finalizedAt`, `claimsOpenAt`, `deposited`, `cancelled`, `lpCreated`, `escaped`, `closedEarly` | lifecycle flags |
| `projectOwner` | the launcher |

Also call **`getPhases()`** — a non-empty array means a **multi-phase** sale (resolve the active phase by
`startsAt<=now<=endsAt`; each phase has its own price/cap/maxBuy/whitelist). Empty = single-phase.

## Compute human-readable price & progress

**FIXED mode:**
```
price_tokens_per_quote = tokensPerQuoteUnit / quoteDecimalScale          # tokens per 1 quote unit
tokens_for(amount)     = amount * tokensPerQuoteUnit / quoteDecimalScale # integer (floor)
progress_pct           = totalRaised / hardCap * 100
```
(Multi-phase: use the **active phase's** `tokensPerQuoteUnit` and `phaseCap`.)

**ORACLE_USD mode:** price is `tokenPriceUsd` (USD/token, feed decimals); progress is `totalRaisedUsd / hardCap * 100`.
A buyer paying `amount` quote receives `usd(amount) * 10**saleTokenDecimals / tokenPriceUsd` tokens, where
`usd(amount) = amount * feed.latestRoundData().answer / quoteDecimalScale`.

Fetch `decimals()`/`symbol()` on `saleToken` and (if not native) `quoteToken` to render amounts.

## Vet a sale (report a verdict, don't just dump fields)

- **Provenance:** confirm the address is in `factory.allSales` / emitted by `SaleCreated`.
- **Liquidity:** `lpAdapter != 0x0 && lpBps > 0` ⇒ raise will seed locked LP (lower rug risk).
- **Supply sanity:** `saleAllocation` and `tokensPerQuoteUnit` imply the max tokens sellable;
  compare with the token's `totalSupply()` to gauge float / dilution.
- **Vesting:** translate `tgeBps`/`cliff`/`vesting` to plain English ("20% at launch, then
  linear over 30 days after a 7-day cliff").
- **Caps & timing:** is the window open/closed now? Is the soft cap reachable? In ORACLE_USD mode the
  caps are USD — quote progress in `totalRaised` against a USD cap is meaningless; use `totalRaisedUsd`.
- **Pricing mode:** if `pricingMode == 1` (ORACLE_USD), sanity-check `priceFeed` returns a fresh,
  positive answer (`latestRoundData`) — a dead/stale feed will block all buys. State the price in USD.
- **Phases:** if `getPhases()` is non-empty, summarize the schedule (each round's window, price, cap,
  whitelist) and which round is live now.
- **Whitelist:** if the applicable root `!= 0` (single-phase `merkleRoot`, or the active phase's root),
  the user needs to be on **that round's** list — flag it.

## Output to the user

A short verdict + the few numbers that matter: price, your max buy, time window, vesting,
whether LP is locked, and any red flags (no LP, whitelist-gated, already ended). Offer the
next action ("want me to buy at open?").
