upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/AGENTS.md
diff options
context:
space:
mode:
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md195
1 files changed, 195 insertions, 0 deletions
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..f5d4f7e
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,195 @@
1# AGENTS.md — Instructions for AI Coding Agents
2
3## Project Overview
4
5TollGate ESP32 firmware: captive portal WiFi hotspot with Cashu e-cash payments, on-device wallet, Nostr identity derivation, and wifistr service discovery. Runs on two ESP32-S3 boards.
6
7## Technology Stack
8
9- **Framework:** ESP-IDF v5.4.1 (C/C++)
10- **Target:** ESP32-S3, 16MB flash, 8MB PSRAM (OCT mode)
11- **Wallet:** nucula library (libsecp256k1) via git submodule
12- **Identity:** Nostr nsec → HMAC-SHA512 → deterministic MAC/SSID/IP
13- **Service discovery:** wifistr (Nostr kind 38787) via WebSocket
14- **Testing:** Host C unit tests (gcc), Node.js integration tests (live board), Playwright E2E
15
16## Board Configuration
17
18| Board | Port | Factory MAC | Notes |
19|-------|------|-------------|-------|
20| A | `/dev/ttyACM0` | `94:a9:90:2e:37:7c` | Primary test target |
21| B | `/dev/ttyACM1` | `fc:01:2c:c5:50:50` | Secondary |
22
23Identity (SSID, IP, MAC) is derived from `nsec` in config.json. Each board gets a unique nsec.
24
25## Boot Sequence
26
27```
28nvs_flash_init()
29 → tollgate_config_init() // loads config.json with nsec from SPIFFS
30 → identity_init(nsec) // derives npub, STA/AP MAC, SSID, IP via HMAC-SHA512
31 → tollgate_config_derive_unique() // copies derived values into config struct
32 → esp_netif_init() + esp_event_loop_create_default()
33 → wifi_init_sta() + wifi_create_ap_netif() // AP netif with derived IP
34 → esp_wifi_init()
35 → esp_wifi_set_mac(STA/AP) // sets derived MACs
36 → esp_wifi_set_mode(APSTA)
37 → wifi_configure_ap() // uses derived SSID
38 → esp_wifi_start()
39 → [on STA got IP] start_services():
40 firewall_init, session_init, wallet_init, dns_server, captive_portal, api, wifistr_publish
41```
42
43## Key Files
44
45### Source (main/)
46- `tollgate_main.c` — entry point, WiFi AP+STA, event loop, service lifecycle
47- `config.c/h` — SPIFFS config.json parsing, nsec/nostr/wifi/mint settings
48- `identity.c/h` — HMAC-SHA512 derivation from nsec, npub/MAC/SSID/IP
49- `nostr_event.c/h` — NIP-01 event serialization + BIP-340 Schnorr signing
50- `geohash.c/h` — lat/lon to geohash encoding
51- `wifistr.c/h` — kind 38787 event builder + WebSocket relay publish
52- `captive_portal.c/h` — HTTP :80 portal, captive detection, grant/reset
53- `dns_server.c/h` — DNS hijack/forward per-client, DoT reject
54- `firewall.c/h` — NAPT on/off per-client, MAC resolution
55- `session.c/h` — time-based sessions, spent-secret tracking
56- `cashu.c/h` — Cashu token decode, checkstate, allotment calc
57- `tollgate_api.c/h` — HTTP :2121, payment endpoints, wallet endpoints
58
59### Components
60- `nucula_lib/` — C++ bridge to nucula::Wallet (C API in nucula_wallet.h)
61- `secp256k1/` — symlink to nucula_src/components/secp256k1/
62
63### Config Format (config.json on SPIFFS)
64```json
65{
66 "nsec": "<64-char hex>",
67 "wifi_networks": [{"ssid":"...", "password":"..."}],
68 "ap_password": "",
69 "mint_url": "https://testnut.cashu.space",
70 "price_per_step": 21,
71 "step_size_ms": 60000,
72 "nostr_geohash": "u281w0dfz",
73 "nostr_relays": ["wss://relay.damus.io", "wss://nos.lol"],
74 "nostr_publish_interval_s": 21600
75}
76```
77
78## Testing Rules — MANDATORY
79
80### Rule 1: Every new C source file MUST have unit tests
81- Place test in `tests/unit/test_<module>.c`
82- Test pure-logic functions with known input/output vectors
83- Compile with host gcc via `make -C tests/unit`
84- Source files remain untouched — stubs in `tests/unit/stubs/` provide ESP-IDF types
85- **Run `make test-unit` after any code change. Must pass before commit.**
86
87### Rule 2: Every new HTTP endpoint MUST have integration tests
88- Place in `tests/integration/phase<N>.mjs`
89- Test against live board using curl + `TOLLGATE_IP` env var
90- Never hardcode IP addresses — always use `process.env.TOLLGATE_IP`
91
92### Rule 3: Every new browser-visible feature MUST have Playwright E2E tests
93- Place in `tests/e2e/<feature>.spec.mjs`
94- Test the full user-visible flow in a browser
95
96### Rule 4: All tests must pass before commit
97- `make test-unit` — host unit tests (no hardware needed)
98- `make test-integration` — against live Board A (needs hardware)
99- `make test-e2e` — Playwright browser tests (needs hardware)
100
101### Rule 5: Test naming conventions
102| Test type | Location | Naming | Run command |
103|-----------|----------|--------|-------------|
104| Host unit | `tests/unit/` | `test_<module>.c` | `make test-unit` |
105| Integration | `tests/integration/` | `phase<N>.mjs` or `<feature>.mjs` | `make test-integration` |
106| E2E | `tests/e2e/` | `<feature>.spec.mjs` | `make test-e2e` |
107
108### Rule 6: Coverage requirements by code type
109| Code type | Required test type | Examples |
110|-----------|-------------------|----------|
111| Pure math/logic | Unit test | geohash, allotment calc, derivation |
112| Crypto operations | Unit test with known vectors | HMAC derivation, Schnorr signing, SHA-256 |
113| Token parsing | Unit test with known tokens | Cashu token decode |
114| State management | Unit test with mocks | Session lifecycle, firewall client list |
115| HTTP endpoints | Integration test | GET /wallet, POST /, POST /wallet/send |
116| HTML pages | Playwright E2E | Portal rendering, payment flow |
117| Network behavior | Integration test | DNS hijack, NAT, connectivity |
118
119## How to Run Tests
120
121```bash
122# Host unit tests (no hardware needed)
123make test-unit
124
125# Integration tests (needs Board A connected and flashed)
126export TOLLGATE_IP=10.192.45.1
127export TOLLGATE_SSID=TollGate-C0E9CA
128make test-integration
129
130# E2E tests (needs Board A + browser)
131make test-e2e
132
133# All tests
134make test-all
135
136# Quick smoke (30s, needs hardware)
137make smoke
138```
139
140## Build & Flash
141
142```bash
143source ~/esp/esp-idf/export.sh
144make flash # build + flash to Board A
145make flash-a # same
146make flash-b # flash to Board B
147```
148
149## Test Infrastructure
150
151### Host Unit Tests (`tests/unit/`)
152- Compile with system gcc, link against `libmbedcrypto` + `libcjson` + secp256k1
153- ESP-IDF types provided by stubs in `tests/unit/stubs/`
154- Each test file is a standalone binary that returns 0 on success, 1 on failure
155- Uses a minimal assert macro: `ASSERT(cond, msg)`
156- Golden test vectors: known nsec → expected npub/MAC/SSID/IP
157
158### Integration Tests (`tests/integration/`)
159- Node.js scripts that run curl/ping/nmcli against a live ESP32 board
160- Require `TOLLGATE_IP` env var (default: auto-detect or error)
161- Token generation via nutshell CLI: `cashu -h https://testnut.cashu.space send --legacy 21`
162
163### E2E Tests (`tests/e2e/`)
164- Playwright browser tests
165- Config in `tests/e2e/playwright.config.mjs`
166- Test the captive portal UI and payment flow
167
168## Environment Variables
169
170| Variable | Default | Purpose |
171|----------|---------|---------|
172| `TOLLGATE_IP` | (none, must set) | Board A's AP IP (e.g., `10.192.45.1`) |
173| `TOLLGATE_SSID` | `TollGate-C0E9CA` | Board A's AP SSID |
174| `TEST_TOKEN` | (none) | Cashu token for payment tests |
175| `SUDO_PW` | `c03rad0r123` | sudo password for route management |
176
177## External Dependencies
178
179- **Test mint:** `testnut.cashu.space` — auto-pays lightning invoices
180- **Nostr relays:** `relay.damus.io`, `nos.lol` — for wifistr events
181- **Nutshell CLI:** `cashu` command for token generation
182- **ESP-IDF:** `source ~/esp/esp-idf/export.sh` before `idf.py` commands
183- **System libs for unit tests:** `libmbedtls-dev`, `libcjson-dev`
184
185## Reminders
186
187- **Commit + push every time a test passes that previously didn't pass.** Green tests = checkpoint. Don't batch multiple test fixes into one commit.
188- Commit + push after each working change
189- Board A is at `/dev/ttyACM0`, Board B at `/dev/ttyACM1`
190- `sudo` password: `c03rad0r123`
191- SPIFFS is at offset `0x410000`, size `0xF0000` — erase with `esptool.py erase_region 0x410000 0xF0000` if config is stale
192- NVS stores wallet proofs — erasing NVS clears wallet balance
193- The `nostr_event.c` `created_at` field uses `gettimeofday()` — mock this in unit tests
194- Wifistr event signing uses `secp256k1_schnorrsig_sign32()` — verify with `_verify()` in tests
195- Portal HTML has server-side template substitution (`__AP_IP__`, `__PRICE__`, `__MINT_URL__`) — no JS fetch