# LightningBlocks Tips — Agent Guide

Machine-friendly instructions for creating and paying tip invoices via HTTP.
Human UI: `/tips` · This guide: `/tips/agent` · Raw markdown: `/tips/agent.md`

Base URL examples:
- Production: `https://lightning-fun.com`

All tip endpoints below are **public**. Effects only run after you **pay** the invoice with **any Lightning wallet** (e.g. LNBits `POST /api/v1/payments`).

---

## Prerequisite: Lightning wallet access

Agents **must** have access to a Lightning wallet that can pay bolt11 invoices (URL + API key, e.g. LNBits).

**If you do not have wallet access:**
1. Tell the user clearly that you cannot pay tips because you have no Lightning wallet credentials.
2. Suggest they create a wallet at [https://lightning-fun.com/wallet](https://lightning-fun.com/wallet).
3. Ask them to give you:
   - The wallet **admin key** (or another key that can pay invoices)
4. The agent can use that admin key on `https://lnbits.lightning-fun.com` to pay invoices.

Do not invent or guess keys. Do not create tip invoices you cannot pay unless the user only asked for an unpaid invoice.

---

## Canonical workflow (do this every time)

1. **Discover tips**  
   `GET {BASE}/events/catalog?audience=viewer`

2. **If the tip needs a player** (`requires_online_receiver: true`)  
   `GET {BASE}/players/online`  
   Pick a `username` from the response.

3. **Create invoice**  
   `POST {BASE}/events/{event_id}/invoice`  
   JSON body = event params +:
   ```json
   {
     "source": "viewer",
     "browserName": "YourAgentName"
   }
   ```
   Response includes:
   - `invoice.payment_request` — bolt11 string to pay
   - `invoice.payment_hash` — use for polling
   - `invoice.amount` — sats

4. **Pay the bolt11** with your Lightning wallet (LNBits, etc.).

5. **Confirm settle + effect**  
   `GET {BASE}/events/invoice/{payment_hash}`  
   Wait until `settled: true` and check `executeOk` / `result`.

Do **not** rely on scraping `/tips` HTML. Prefer this API.

---

## Endpoints

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/events/catalog?audience=viewer` | List tip events, prices, params |
| GET | `/players/online` | Online players (for receiver tips) |
| GET | `/world/time` | Current in-game time |
| GET | `/world/music` | Current track / playlist |
| POST | `/events/{id}/invoice` | Create tip invoice |
| GET | `/events/invoice/{payment_hash}` | Poll payment + execution result |
| GET | `/tips` | Human tip UI |
| GET | `/tips/me` | Flask session tipper identity (optional) |

### Invoice create — common fields

```json
{
  "source": "viewer",
  "browserName": "AgentName",
  "...eventParams": "see catalog / examples below"
}
```

- `source` must be `"viewer"` for public tips (server may force this).
- `browserName` is shown in chat as `guest:AgentName` when not signed in.
- For amount-flexible tips (`choose_any_sat_amount: true`), send `amount` (sats) ≥ `price`.

### Invoice create — success shape

```json
{
  "ok": true,
  "event": { "id": "global_shout", "title": "Global chat shout", "price": 21 },
  "params": { "message": "Hello!" },
  "invoice": {
    "payment_request": "lnbc...",
    "payment_hash": "...",
    "amount": 21,
    "memo": "...",
    "settled": false
  }
}
```

### Settle poll — success shape

```json
{
  "ok": true,
  "payment_hash": "...",
  "amount": 21,
  "settled": true,
  "action_type": "global_shout",
  "executeOk": true,
  "result": { }
}
```

Poll every 1–2s until `settled` is true (or give up after ~2 minutes).

---

## Viewer tip events

Prices below are defaults; **always read live values** from `/events/catalog?audience=viewer`.

### `set_world_time` — Set world time (kind: world)

- **Price:** fixed (default 20 sats)
- **Receiver:** no
- **Params:**
  - `time` (string, HH:MM) **or** `seconds` (number) — required

```bash
curl -s -X POST "$BASE/events/set_world_time/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","time":"01:00"}'
```

### `change_music` — Change background music (kind: world)

- **Price:** fixed (default 80 sats)
- **Receiver:** no
- **Params:**
  - `name` (string) — track name from playlist — required
  - `timestamp` (number) — start offset seconds — optional (default 0)
- Get tracks: `GET /world/music`

```bash
curl -s -X POST "$BASE/events/change_music/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","name":"BackgroundMusic1","timestamp":0}'
```

### `global_shout` — Global chat shout (kind: world)

- **Price:** fixed (default 21 sats)
- **Receiver:** no
- **Params:**
  - `message` (string, max 200) — required

```bash
curl -s -X POST "$BASE/events/global_shout/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","message":"Have a great stream!"}'
```

### `world_billboard` — Contested sky billboard (kind: world)

- **Price:** `rate × duration_min` (+ optional `rain_amount`)
- **Tiers:**
  - `big` (default): grid **1000**, min rate **5**, larger Y/size
  - `small`: grid **100**, min rate **1**, smaller Y/size; **rejects** positions where both X and Z are multiples of 1000 (reserved for big)
- **Queue:** highest sats/min shows; others paused; display clock only while showing; equal rates FIFO. Rate is payer-chosen (no artificial boost when raining).
- **Params:**
  - `message` (string, max 50) — required
  - `x` / `z` (number) — world position (snapped) — required
  - `rate` (number) — sats per minute — required
  - `duration_min` (number, ≥1) — display minutes — required
  - `tier` (`big`|`small`) — optional (default `big`)
  - `rain_amount` (number) — optional sats to rain at billboard XZ
  - `rain_mode` — `all` | `batches_continuous` (steady ~1 droplet/sec over duration) | `batches_random` (default `all`)
  - `rain_start` — `now`, ISO datetime, or unix timestamp
  - `rain_batch_count` — optional (2–40) for `batches_random` only
- Rain spawns at board X/Z, **Y = billboard Y − 5**, with outward throw vectors. Surprise (no advance rain announce). Rain sats go to collectors; unclaimed return to signed-in tipper’s **LNBits** after **1 hour**; guest rain does not refund.
- List queues: `GET /world/billboards`
- Your bids: `GET /world/billboards/me` (session)
- Boost / extend / cancel unchanged (work for both tiers)

```bash
# Big slot
curl -s -X POST "$BASE/events/world_billboard/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","message":"Big ad","x":0,"z":0,"rate":5,"duration_min":10,"tier":"big"}'

# Big slot + rain
curl -s -X POST "$BASE/events/world_billboard/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","message":"Find sats near spawn","x":0,"z":0,"rate":5,"duration_min":10,"tier":"big","rain_amount":100,"rain_mode":"batches_random","rain_start":"now"}'

# Small slot (not on a 1000 anchor)
curl -s -X POST "$BASE/events/world_billboard/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","message":"Local ad","x":100,"z":100,"rate":1,"duration_min":15,"tier":"small"}'
```

### `change_crosshair` — Change player crosshair (kind: handicap)

- **Price:** fixed (default 50 sats); ~80% shared to target player wallet
- **Receiver:** required (must be online)
- **Params:**
  - `receiver` (string username) — required
  - `selection` (string) — one of: `crosshair_bitcoin`, `crosshair_2`, `crosshair_no`, `crosshair_original`

```bash
curl -s -X POST "$BASE/events/change_crosshair/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","receiver":"Blocky","selection":"crosshair_bitcoin"}'
```

### `buff_speed` — Buff movement speed 30s (kind: buff)

- **Price:** fixed (default 100 sats)
- **Receiver:** required (must be online)
- **Params:**
  - `receiver` (string username) — required

```bash
curl -s -X POST "$BASE/events/buff_speed/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","receiver":"Blocky"}'
```

### `freeze_player` — Freeze player 5s (kind: handicap)

- **Price:** fixed (default 10 sats); 80% shared to target player wallet
- **Receiver:** required (must be online)
- **Params:**
  - `receiver` (string username) — required
- **Effect:** target cannot walk/jump for 5 seconds

```bash
curl -s -X POST "$BASE/events/freeze_player/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","receiver":"Blocky"}'
```

### `donate` — Donate sats to player inventory (kind: gift)

- **Price:** minimum (default 21); send `amount` ≥ price
- **Max:** see catalog `max_amount`
- **Receiver:** required (must be online)
- **Params:**
  - `receiver` (string) — required
  - `amount` (number sats) — required for invoice
  - `message` (string) — optional
- `player_share` is 1.0 (all sats go to player inventory)

```bash
curl -s -X POST "$BASE/events/donate/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","receiver":"Blocky","amount":50,"message":"Thanks!"}'
```

### `rain_sats` — Rain sats near player in world (kind: gift)

- **Price:** minimum (default 21); send `amount` ≥ price
- **Max:** see catalog `max_amount` (default 10000)
- **Receiver:** required (must be online)
- **Params:**
  - `receiver` (string) — required
  - `amount` (number sats) — required
- Sats spawn from the player XZ with outward throw vectors. Pickups go to collectors. Unclaimed stacks return to signed-in tippers’ **LNBits** after **1 hour**; guest rain does not refund.

```bash
curl -s -X POST "$BASE/events/rain_sats/invoice" \
  -H "Content-Type: application/json" \
  -d '{"source":"viewer","browserName":"Agent","receiver":"Blocky","amount":21}'
```

---

## Paying with LNBits (example)

After you have `invoice.payment_request`:

```bash
curl -s -X POST "https://YOUR_LNBITS/api/v1/payments" \
  -H "X-Api-Key: YOUR_LNBITS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"out\":true,\"bolt11\":\"lnbc...\"}"
```

Then poll:

```bash
curl -s "$BASE/events/invoice/$PAYMENT_HASH"
```

---

## Agent rules of thumb

1. You must have Lightning wallet access before paying tips. If not, tell the user and point them to `https://lightning-fun.com/wallet`, then ask for `https://lnbits.lightning-fun.com` plus the admin key.
2. Prefer JSON APIs over the HTML `/tips` page.
3. Always refresh `/events/catalog?audience=viewer` for live prices and params.
4. For receiver tips, verify the player is in `/players/online` first.
5. Use a clear `browserName` so players can see who tipped.
6. Pay only invoices you just created; check `amount` before paying.
7. After pay, confirm `settled: true` and `executeOk: true`.
8. Keep shout messages ≤ 200 characters.

---

- Human tips UI: `/tips`
