# Skill: claim vested tokens / refund a failed sale

Use after a sale has settled. The right action depends on the sale's outcome.

## Decide: claim or refund?

```
v = Sale(sale).getSaleData()

if v.escaped == true:
    # platform pulled the raise (emergency). Funds are seized and NOT refundable.
    # There is nothing to claim or refund. Report this clearly and stop.

elif v.cancelled == true:           # state == 4 Cancelled (and not escaped)
    -> REFUND path (sale failed; get the quote back in full)

elif v.claimsOpenAt != 0:           # claims are open (success path)
    -> CLAIM path (receive vested tokens over time)

else:
    # not settled yet (Pending/Active/Ended/Finalized-pre-LP). Nothing to do.
    # If state==3 Finalized but claimsOpenAt==0 and lpAdapter!=0 and !lpCreated:
    #   liquidity hasn't been seeded. Anyone may call createLiquidity() to open claims,
    #   or finalizeAndCreateLiquidity earlier. If >7 days since finalizedAt with no LP,
    #   failPostFinalize() flips it to refundable (see below).
```

## CLAIM path (vested)

Claims unlock on a TGE + cliff + linear schedule anchored at `claimsOpenAt`:

```
claimable_now = Sale(sale).claimable(you)   # the contract computes the unlocked-minus-claimed amount
require claimable_now > 0                    # else "Nothing to claim"
calldata = sale.claim()
simulate -> send -> wait
```

- `claim()` pays from the segregated `ClaimVault` (anti-snipe limits never block claims).
- Vesting math (for telling the user how much unlocks when):
  - `total = purchased[you]`
  - `tge = total * tgeBps / 10000` unlocked at `claimsOpenAt`
  - during the cliff (`elapsed < cliffDuration`): only `tge` is unlocked
  - after cliff, linear over `vestingDuration`: `unlocked = tge + (total - tge) * (elapsed - cliff) / vestingDuration`
  - at/after `cliff + vestingDuration`: fully unlocked = `total`
- **To fully realize a position you may need to claim multiple times** as more vests. If the
  user said "claim everything as it unlocks", schedule re-checks (see `skills/monitor.md`) and
  claim whenever `claimable(you) > 0`, until `claimed[you] == purchased[you]`.

Post-check: `claimed[you]` increased; the user's wallet balance of `saleToken` increased by
`claimable_now`. Report tx, amount claimed, amount still locked, and the next unlock time.

## REFUND path (failed/cancelled sale)

```
amount = contributed[you]
require amount > 0
calldata = sale.refund()
simulate -> send -> wait
```

- Refund returns the **full** quote contribution (native or ERC20). After refund,
  `contributed[you]` and `purchased[you]` are zeroed, so you cannot also claim — this is by design.
- Post-check: wallet quote balance increased by `amount`; `contributed[you] == 0`. Report tx + amount.

## Escaped sales (no recourse)

If `v.escaped == true`, the platform executed `emergencyEscape`: the raise was withdrawn and
the sale voided. Buyers are **not** refunded (documented, governed power). Do not attempt
`refund()` — it reverts "Not cancelled". Report the seizure to the user honestly.
