# Skill: launch a token (project side) — SPENDS FUNDS + COMMITS TOKENOMICS

Use when the user is a **project** wanting to create their own token + sale in one transaction.
This is high-stakes: it deploys a fixed-supply token and locks in economics. **Confirm every
parameter with the user before sending.** The default path is `createSaleWithNewToken` (the
bring-your-own-token path is disabled unless the platform enabled `byoTokenEnabled`).

## Inputs to gather from the user (with sane prompts)

- Token: `name`, `symbol`, `decimals` (usually 18), `totalSupply` (human units).
- Quote asset: native HYPE (`0x0`) or an ERC20 address; its decimals.
- **Pricing mode**: `FIXED` (default) or `ORACLE_USD`.
  - FIXED: `tokensPerQuoteUnit` = sale-token base units per 1 quote unit; caps are in **quote** decimals.
  - ORACLE_USD: a Chainlink-compatible `priceFeed` (quote→USD), `tokenPriceUsd` (USD/token in the feed's
    decimals, usually 8), `maxOracleDelay` (seconds, `0 < d <= 7 days`). Caps are in **USD** (feed
    decimals). `tokensPerQuoteUnit` is ignored. **Single-phase only.**
- Caps & window: `softCap`, `hardCap`, `minBuy`, `maxBuy` (in the cap unit — quote for FIXED, USD for
  ORACLE_USD), `startsAt`, `endsAt` (unix).
- Vesting for buyers: `tgeBps` (0–10000), `cliffDuration`, `vestingDuration` (seconds).
- Liquidity (always locked): `lpBps` (% of net raise to LP), `lpTokensAmount` (sale tokens for LP),
  `lpLockDuration` (your floor; on-chain minimum is enforced — you can go longer, never shorter).
- Anti-snipe (optional): `antiSnipeBlocks`, `maxWalletDuringGuard`; if either > 0 you MUST set
  `tradingAutoOpenAt` to a non-zero backstop (e.g. `endsAt + 7 days`) or the call reverts.
- `burnUnsold` (bool): burn unsold remainder at a successful settle instead of returning it.
- **Phases (optional, FIXED only)**: a private→public schedule. Each phase
  `{startsAt, endsAt, tokensPerQuoteUnit, phaseCap, maxBuy, merkleRoot}`. Caps are **cumulative** and
  **strictly increasing**; the **last `phaseCap` must equal `hardCap`**; windows ordered & non-overlapping
  (a gap pauses buys). Empty = single-phase. ORACLE_USD requires empty phases.
- Optional `token0`: mine a CREATE2 `salt` so the token address sorts below the quote (V3 token0).

## Build the two structs (exact field order)

`SaleConfig` (24 fields) — the factory overwrites `saleToken` with the freshly-deployed token:

```
config = [
  0x0,                 # saleToken (ignored; factory fills it)
  quoteToken,          # 0x0 for native
  tokensPerQuoteUnit,  # FIXED only; 0 for ORACLE_USD (set >0 in FIXED — reuse phase[0].rate if multi-phase)
  softCap, hardCap, minBuy, maxBuy,   # cap unit: quote (FIXED) or USD in feed decimals (ORACLE_USD)
  startsAt, endsAt,    # uint64 unix  (multi-phase: must enclose all phases)
  merkleRoot,          # 0x0 = open sale (ignored when phases is non-empty — each phase carries its own)
  tgeBps, cliffDuration, vestingDuration,
  lpAdapter,           # the platform's approved V3 adapter (read from config/addresses.json)
  lpBps, lpTokensAmount, lpLockDuration,
  lpData,              # abi-encoded V3Data, see below
  burnUnsold,          # bool
  pricingMode,         # uint8: 0 = FIXED, 1 = ORACLE_USD
  priceFeed,           # ORACLE_USD: quote→USD feed; 0x0 for FIXED
  tokenPriceUsd,       # ORACLE_USD: USD per token in feed decimals; 0 for FIXED
  maxOracleDelay,      # ORACLE_USD: max price staleness seconds (0 < d <= 7 days); 0 for FIXED
  phases,              # Phase[] : [] = single-phase; else tuples (startsAt,endsAt,tokensPerQuoteUnit,phaseCap,maxBuy,merkleRoot)
]
```

`lpData` = ABI-encode of the V3 tuple — **8 static fields, all required** (the canonical encoding
is `frontend/app.js`; keep this in lockstep with it):
`(uint24 fee, int24 tickLower, int24 tickUpper, uint160 sqrtPriceX96, uint16 slippageBps,
uint64 deadlineBuffer, uint16 firstBuyBps, uint16 priceGuardBps)`.
Standard values: `fee=10000` (1%), `tickLower=-887200`, `tickUpper=887200`, `sqrtPriceX96=0`
(derive launch price on-chain from the LP-token:quote ratio), `slippageBps=500`,
`deadlineBuffer=900`, `firstBuyBps=0` (or e.g. 1000 = 10% protocol anti-snipe buy),
`priceGuardBps=500` (±band on the seeded pool price vs intended; 0 = off).
⚠️ Encoding only 7 fields makes `abi.decode(r.data,(V3Data))` revert on-chain — settlement will fail.

`TokenParams` (14 fields):

```
tp = [
  name, symbol, decimals, totalSupply,
  teamTgeBps, teamStart, teamCliff, teamVesting, teamBeneficiary,  # team allocation = supply - (sale + LP); 0/0/0/0x0 = instant to creator
  salt, sortBelow,         # token0 ordering: 0x0/0x0 = plain deploy; else mine salt so token < sortBelow
  antiSnipeBlocks, maxWalletDuringGuard, tradingAutoOpenAt,
]
```

## Supply must cover the sale + LP

```
required = Sale(predicted).tokensRequired()   # = sale allocation + LP tokens (handles every mode)
require totalSupply >= required               # else "Supply < required"; the remainder is the team allocation
```
Off-chain, the sale allocation depends on the mode:
- FIXED single-phase: `sale_alloc = hardCap * tokensPerQuoteUnit / quoteDecimalScale`
- FIXED multi-phase:  `sale_alloc = Σ (phaseCapᵢ − phaseCapᵢ₋₁) * rateᵢ / quoteDecimalScale`
- ORACLE_USD:         `sale_alloc = hardCap(USD) * 10**saleTokenDecimals / tokenPriceUsd`  (oracle-independent)

Then `required = sale_alloc + lpTokensAmount`. Prefer the on-chain `tokensRequired()` as the source of truth.

## token0 salt mining (optional)

If the user wants the token to be `token0` (cleaner V3 pool math), mine `salt` so the CREATE2
address `< sortBelow` (the quote; use WHYPE for native). Init code =
`LaunchToken.creationCode ++ abi.encode(name, symbol, decimals, totalSupply, factory)`. Loop
salts computing `CREATE2(factory, salt, keccak256(initCode))` until the address sorts below the
target. The on-chain `require(token < sortBelow)` is the safety net.

## Execute

```
fee = factory.creationFee()
(sale, token, vesting) = factory.createSaleWithNewToken.staticCall(config, tp, {value: fee})
                                   # simulate first: must not revert; CAPTURES all 3 addresses —
                                   # vesting is ONLY a return value, it is NOT in any event
send:     tx {to: factory, value: fee, data: calldata}
receipt -> cross-check (sale, token) against SaleCreated(sale, creator, saleToken) /
           TokenLaunched(token, sale, creator, supply). If you missed the staticCall, recover
           vesting from the receipt's token Transfer log (factory -> vesting wallet for the
           team remainder; absent when the remainder pays the beneficiary instantly).
```

## Post-checks & report

- Confirm `getSaleData()` reflects every intended parameter.
- Confirm the token `totalSupply()` and that the sale holds the sale+LP allocation
  (`deposited == true`).
- Confirm the team remainder landed (instant to beneficiary, or into a `TokenVesting` wallet).
- Report all addresses (sale, token, vesting), the launch window, and a buyer-facing summary the
  user can share. Then optionally hand off to `skills/monitor.md` to watch the sale.

> The project CANNOT later cancel/pause the sale, touch the LP, or change terms. Settlement is
> permissionless. Make sure the user understands this before launching.
