# SMASHR firmware

ESP32-S3 sketch for the SMASHR button. The device holds no wallet key: it signs each press with a
device secret issued at pairing, and the server executes the Jupiter swap from the owner's capped button wallet.

## Build

Arduino IDE 2 or arduino-cli, with the **esp32 by Espressif** core 3.x.

```sh
arduino-cli core install esp32:esp32
arduino-cli lib install "Adafruit NeoPixel" "ArduinoJson"
arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32S3 firmware/sendit
arduino-cli upload  --fqbn esp32:esp32:XIAO_ESP32S3 -p /dev/cu.usbmodem* firmware/sendit
```

Pick a partition scheme with two OTA app slots (the XIAO default does). Change `API_HOST` in `config.h`
if you self-host the API.

## Files

| File | What it does |
| --- | --- |
| `sendit.ino` | Boot flow, debounced button, press dispatch, heartbeat, factory reset |
| `config.h` | Pins, timings, API host, brightness cap |
| `led.cpp` | Ring choreography on its own FreeRTOS task (core 0), same states as the web |
| `api.cpp` | HTTPS client, HMAC-SHA256 signing, clock offset from server time |
| `portal.cpp` | Captive portal: AP `SMASHR-xxxx`, Wi-Fi form, pairing |
| `store.cpp` | NVS storage for Wi-Fi and the device identity |
| `ota.cpp` | HTTPS pull updates from the heartbeat pointer, optional LAN OTA |
| `certs.h` | Root CAs: GTS Root R1 (vercel.app) and ISRG Root X1 |

## Using it

1. In the dashboard, open a button and press **Pair a device**. You get a 6 character code, valid 10 minutes.
2. Hold the button while plugging in USB-C. After 3 seconds the ring pulses amber: setup mode.
3. Join `SMASHR-xxxx` from your phone. The setup page opens by itself (or browse to 192.168.4.1).
4. Pick your 2.4 GHz network, type the password and the code. Green sweep means paired.

Hold the button for 10 seconds at any time to forget Wi-Fi and pairing (ring flashes red and white first; let go to cancel).

## LED states

| State | Ring |
| --- | --- |
| Idle, ready | Red, slow breathe |
| Sending | White comet |
| `ok` | Green sweep, fades after 1.5 s |
| `cooldown` | Amber ring that drains over the wait |
| `insufficient` | Red double blink |
| `error` | Red solid |
| Setup mode | Amber, every other pixel, pulsing |
| Connecting | Blue comet |
| Offline | Very dim red, slow pulse |

## Protocol

```
POST /api/device/press
x-sendit-device: SIXXXXXXXX
x-sendit-ts:     1791796716          (unix seconds, +-120 s)
x-sendit-nonce:  32 hex chars        (single use)
x-sendit-sig:    hex(HMAC_SHA256(secret, ts + "." + nonce + "." + body))
body:            {"event":"press","seq":12,"rssi":-58,"fw":"1.0.0"}

200 {"code":"ok","sig":"5h...","tokens":3241400,"symbol":"Bonk","led":"green_sweep"}
200 {"code":"cooldown","retry_in":23,"reason":"cooldown","led":"amber_drain"}
200 {"code":"insufficient","reason":"low_sol","led":"red_double_blink"}
200 {"code":"error","reason":"no_route","led":"red_solid"}
```

`/api/device/ping` takes the same headers every 60 s and returns the button config and an optional
`ota: {version, url}` pointer.
