# Skill: monitor & act on triggers (the "do it for me while I'm busy" loop)

This is the core value for a delegating user: watch a sale and act automatically when a
condition is met, without the user babysitting it. Use a polling loop or event subscription.

## Pattern

```
loop every <interval> (e.g. 15–30s near a known boundary, longer when idle):
  v = Sale(sale).getSaleData()
  evaluate triggers (below)
  on a matched trigger -> run the matching skill (participate / claim-refund)
  re-read state after acting; stop or continue per the user's instruction
exit when the goal is achieved or the user's deadline passes
```

Prefer event-driven where possible: watch `SaleCreated` (factory), and per-sale `Bought`,
`Finalized`, `LiquidityCreated`, `Cancelled`, `Claimed`, `Refunded`. Fall back to polling
`getSaleData()` for the authoritative state.

## Common triggers → actions

| User intent | Trigger condition | Action |
|---|---|---|
| "Buy at open" | `state` transitions `Pending(0) -> Active(1)` and `deposited` | `participate.md` with sized amount |
| "Buy the dip to my max" | `Active` and `contributed[you] < maxBuy` and budget left | top up toward `maxBuy` / `hardCap` |
| "Get me in before it sells out" | `Active` and cap room small (`hardCap - totalRaised`, or USD: `hardCap - totalRaisedUsd`) | buy promptly within budget |
| "Buy in the public round" (multi-phase) | active phase (via `getPhases()`) is the target phase; phase `merkleRoot` open or you're listed | `participate.md` against the active phase's terms |
| "Claim when it unlocks" | `claimsOpenAt != 0` and `claimable(you) > 0` | `claim()`; repeat as more vests |
| "Claim everything over time" | recurring: `claimable(you) > 0` until `claimed==purchased` | claim on each unlock |
| "Refund if it fails" | `cancelled == true` and `contributed[you] > 0` | `refund()` |
| "Alert me on settlement" | `state -> Finalized(3)` or `Cancelled(4)` | notify; then claim/refund per outcome |
| "Open claims if stuck" | `Finalized`, `lpAdapter!=0`, `!lpCreated` | (permissionless) `createLiquidity()` to open claims |
| "Rescue my funds if LP never seeds" | `Finalized`, `!lpCreated`, `now > finalizedAt + 7d` | `failPostFinalize()` then `refund()` |

## Timing hints

- `startsAt` / `endsAt` are known in advance — schedule tight polling around them, idle otherwise.
- **Multi-phase:** phase windows (`getPhases()`) are known in advance too; a gap between phases reads
  as `Pending(0)`, and each phase boundary changes price/cap/whitelist — poll tightly around boundaries.
- A hard-cap sellout flips `Active -> Ended` early (when the cap is hit — `totalRaised == hardCap` in
  FIXED, `totalRaisedUsd == hardCap` in ORACLE_USD); don't assume the sale runs to `endsAt`.
- Vesting unlocks are continuous after `claimsOpenAt + cliff`; you don't need to poll every block
  — compute the next meaningful unlock time and wake then.

## Safety in a loop

- Re-validate budget and per-wallet caps on **every** iteration before spending.
- Make each spend idempotent (re-read `contributed[you]` / `claimed[you]`) so a missed receipt
  never causes a double buy/claim.
- Respect a stop condition and a deadline; report a summary when the loop ends (what happened,
  tx hashes, final position).
