<feed xmlns='http://www.w3.org/2005/Atom'>
<title>npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/PLAN.md, branch master</title>
<subtitle>[no description]</subtitle>
<id>https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/atom?h=master</id>
<link rel='self' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/'/>
<updated>2026-05-18T21:02:41+00:00</updated>
<entry>
<title>feat: local Nostr relay with relay selection, sync, and integration tests</title>
<updated>2026-05-18T21:02:41+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-18T21:01:19+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=81f2dc52dc42d01c89dff45a5407ec40b8863052'/>
<id>urn:sha1:81f2dc52dc42d01c89dff45a5407ec40b8863052</id>
<content type='text'>
Local Nostr relay (NIP-01) on port 4869 with LittleFS 4MB storage.
All events published locally first, then synced to public relays via REQ-diff.
Relay selection via NIP-11 HTTP probing with NIP-77 scoring and auto-failover.

Components:
- wisp_relay: 16-file local relay (ws_server, storage_engine, sub_manager,
  broadcaster, relay_validator, router, handlers, rate_limiter, nip11,
  deletion, flash_monitor, relay_types)
- esp_littlefs: LittleFS VFS integration (git submodule)
- negentropy: for future NIP-77 binary sync (git submodule)

New source files:
- local_relay.c/h: thin wrapper for relay init/start/publish
- relay_selector.c/h: NIP-11 probe + scoring + auto-failover
- sync_manager.c/h: REQ-diff sync (primary 30min, fallback 6h)

Bug fixes:
- config.c: use-after-free (cJSON_Delete before seed_relays/sync parsing)
- local_relay: moved init to app_main for boot-time start (not gated on STA IP)

Flash layout: 4MB LittleFS partition at 0x500000 for relay_store

Test results (Board B, live hardware):
- Smoke: ping + HTTP 4869 + NIP-11: PASS
- NIP-11 info document: 10/11 PASS
- WS pub/sub (connect, REQ/EOSE, EVENT/OK, CLOSE, concurrent): 6/6 PASS
- Unit tests (relay_validator + relay_selector): 13/13 PASS

Hardware test make targets in physical-router-test-automation/:
- make relay-build, relay-flash-b, relay-test-smoke/nip11/pubsub/sync/full
</content>
</entry>
<entry>
<title>feat: ContextVM (MCP over Nostr) server with full integration</title>
<updated>2026-05-18T19:40:06+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-18T19:40:06+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=42902a36bc52e009a1e8d3c371741e30a9cb4c33'/>
<id>urn:sha1:42902a36bc52e009a1e8d3c371741e30a9cb4c33</id>
<content type='text'>
Complete CVM implementation: persistent WebSocket relay listener,
kind 25910 event subscription, MCP protocol handlers, CEP-6 announcements,
10 MCP tools, per-board hardware locks, WiFi EU regulatory fix.

Architecture:
- cvm_server.c: WS relay listener, kind 25910 subscription, MCP dispatch
- mcp_handler.c/h: 10 MCP tools (get_config, set_config, get_balance,
  wallet_send, get_sessions, get_usage, set_payout, set_metric,
  set_price, wallet_melt)
- Responses published via existing WS connection (not new TLS)
- Auth check: only owner npub accepted
- CEP-6: kinds 11316 (server), 11317 (tools), 10002 (relay list)
- WS ping/pong keepalive every 30s, 60s TLS read timeout

Critical fixes:
- WiFi country code DE (ESP-IDF defaults to CN, breaks EU APs)
- Subscription #p filter must be array not string
- Use-after-free: tags_str freed before nostr_event_to_json
- MCP responses via existing WS (ESP32 can't open multiple TLS)
- EVENT msg buffer underflow, WS frame masking, TLS write loop

Per-board hardware locks:
- Lock files in physical-router-test-automation/locks/
- lock-a/b/c, unlock-a/b/c targets in 3 Makefiles
- All hardware-touching targets require board lock

Verified on Board B via relay.primal.net:
- 282 unit tests passing (61 CVM + 60 MCP + 161 existing)
- MCP initialize roundtrip: PASS
- tools/list: PASS
- tools/call get_config: PASS
- tools/call get_balance: PASS
- tools/call set_price: PASS (write operation)
- CEP-6 announcements (11316, 11317, 10002): all accepted by relay
- WiFi STA connection (EnterSSID-2.4GHz): PASS with country code DE
- Board A WiFi confirmed hardware issue (not firmware)
</content>
</entry>
<entry>
<title>feat: per-client NAT filtering via LWIP_HOOK_IP4_CANFORWARD</title>
<updated>2026-05-17T11:09:31+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-17T11:09:31+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=0c2c67b463d6a90aaa0bb69bf3c91dba1d9ec3ec'/>
<id>urn:sha1:0c2c67b463d6a90aaa0bb69bf3c91dba1d9ec3ec</id>
<content type='text'>
- Add lwip_tollgate_hooks.h defining LWIP_HOOK_IP4_CANFORWARD macro
- Inject hook into lwIP build via CMakeLists.txt ESP_IDF_LWIP_HOOK_FILENAME
- Filter forwarded packets by source IP against firewall allowed list
- Only filter packets from AP subnet (10.192.45.0/24), allow all others
- Fix byte order bug: use network byte order for firewall_is_client_allowed
- NAT always enabled, removed global NAT toggle functions
- Remove spent-secret tracking from session.c (mint is authority)
- Remove unused get_ap_netif() function
- Reduce API server stack from 32KB to 16KB (fixes ESP_ERR_HTTPD_TASK)
- Add esp_random.h stub for unit tests
- All 186 unit tests passing
- Verified on hardware: block-&gt;pay-&gt;allow-&gt;revoke-&gt;block E2E works
</content>
</entry>
<entry>
<title>Phase 4: TollGate client detection + auto-payment</title>
<updated>2026-05-16T22:51:39+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-16T22:51:39+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=78dd599277b8e8b2ddc39a4ae710ec91d737272e'/>
<id>urn:sha1:78dd599277b8e8b2ddc39a4ae710ec91d737272e</id>
<content type='text'>
- New tollgate_client.c/h: detect upstream TollGate (kind=10021),
  auto-pay via nucula wallet, session monitoring with 20% renewal
- State machine: IDLE→DETECTING→NEEDS_PAY→PAYING→PAID→RENEWING
- Blocking: upstream payment before local services start
- Synchronous wallet init (was async task)
- Client config: enabled, steps_to_buy, renewal_threshold_pct
- Updated PLAN.md with Phases 4-7 (client, payout, bytes, CVM)
- Updated CHECKLIST.md with all new phase items
- 30 new unit tests (all passing), 116 total
</content>
</entry>
<entry>
<title>Testing infrastructure: AGENTS.md rules + unit test framework + geohash tests (11/11 pass)</title>
<updated>2026-05-16T20:01:49+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-16T20:01:49+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=347d29658959c7e4b368a15134c183f4ce7a25bc'/>
<id>urn:sha1:347d29658959c7e4b368a15134c183f4ce7a25bc</id>
<content type='text'>
- Add AGENTS.md: full project context + mandatory testing rules for AI sessions
- Add tests/unit/ with host-compiled C unit test infrastructure
- Clean stubs approach: ESP-IDF type stubs in tests/unit/stubs/, no source modifications
- Fix geohash.c bit extraction bug (3-byte span) found by unit tests
- test_geohash: 11/11 passing with reference vectors (Munich, NYC, origin, boundaries)
</content>
</entry>
<entry>
<title>Phase 3: Nostr identity derivation + wifistr service discovery</title>
<updated>2026-05-16T18:25:05+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-16T18:25:05+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=4c47ae188b288e7d24bd9566ab3e6a6805d9484f'/>
<id>urn:sha1:4c47ae188b288e7d24bd9566ab3e6a6805d9484f</id>
<content type='text'>
- Add identity.c/h: HMAC-SHA512 derivation from nsec → npub, STA/AP MAC, SSID, AP IP
- Add nostr_event.c/h: NIP-01 event serialization + Schnorr signing (BIP-340)
- Add geohash.c/h: lat/lon to geohash encoding
- Add wifistr.c/h: kind 38787 event builder + WebSocket publish to Nostr relays
- Update config.c/h: nsec-based identity, Nostr relay/geo config, remove static SSID/IP
- Replace custom mbedTLS wallet with nucula library (libsecp256k1)
- Remove wallet.c/h, wallet_persist.c/h (replaced by nucula_lib component)
- Verified on Board A: derived SSID, captive portal, payment, wallet, wifistr publish
</content>
</entry>
<entry>
<title>Phase 3: on-device Cashu wallet with mbedTLS secp256k1 + SPIFFS persistence + PSRAM</title>
<updated>2026-05-16T10:02:55+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-16T10:02:55+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=133e40c82afb4d7659758b1fa57925ac57af4621'/>
<id>urn:sha1:133e40c82afb4d7659758b1fa57925ac57af4621</id>
<content type='text'>
- wallet.c/h: secp256k1 ECP primitives (hash_to_curve, scalar_mul, point_add)
- wallet_persist.c/h: SPIFFS persistence with threshold-based write protection
- Fee accounting for swap (input_fee_ppk from /v1/keysets)
- Keyset fetch via /v1/keysets (586 bytes vs 21KB for /v1/keys)
- Wallet API: GET /wallet, POST /wallet/swap, POST /wallet/send
- Payment proofs auto-stored to wallet + persisted on SPIFFS
- PSRAM enabled for large allocations (ESP32-S3 has 8MB)
- Wallet init deferred to dedicated task (avoids sys_evt stack overflow)
- Cashu proof ID buffer size fixed (66 hex chars, not 16)
- HTTP client: added fetch_headers() call for proper response handling
- persist_threshold_sats config parameter (default: 1 sat)
</content>
</entry>
<entry>
<title>Unique SSID/IP per board + captive detection fix + mint list in portal</title>
<updated>2026-05-16T06:26:43+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-16T06:26:43+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=38aa9ec3801f5895e09866fe92cb8e44fb987cee'/>
<id>urn:sha1:38aa9ec3801f5895e09866fe92cb8e44fb987cee</id>
<content type='text'>
- Derive unique SSID (TollGate-{MAC4}{MAC5}) and AP IP (10.{b5}.{subnet}.1)
  from factory MAC — boards no longer conflict
- Board A: TollGate-377C @ 10.55.85.1, Board B: TollGate-5050 @ 10.80.10.1
- Captive portal detection URIs return 200 with portal HTML (matching
  esp32-mesh working approach) instead of 302 redirect
- Dynamic AP IP in portal HTML via __AP_IP__ template substitution
- Supported mints section in portal page (shows mint URL, tap to copy)
- Fixed mint URL to testnut.cashu.space (was stale in SPIFFS)
- DoT reject server on port 853 for DNS-over-TLS fallback
- DNS hijack: NXDOMAIN for all non-A queries, no forwarding for unauthed
- Playwright tests updated for 200 response on detection URIs
- Phase 2 test suite: 20/21 pass (test 22 expiry ping route issue)
- Tests 25-27 deferred to Phase 3 (Board B as second client)
</content>
</entry>
<entry>
<title>Phase 2 Playwright tests: 10/10 passing (portal, captive detection, API)</title>
<updated>2026-05-15T23:21:57+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-15T23:21:57+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=ee4e13680f522253f94e8ebdea5df80332afc495'/>
<id>urn:sha1:ee4e13680f522253f94e8ebdea5df80332afc495</id>
<content type='text'>
- Updated from Phase 1 tests to Phase 2 (302 redirects, Cashu token input)
- Test captive detection URIs return 302 (using request API)
- Test invalid token via request API (no CORS issues)
- Tests: portal branding, price, token input, pay button, detection redirects
- Tests: whoami, usage, API advertisement, invalid token
</content>
</entry>
<entry>
<title>Captive portal detection fix + Phase 2 tests 16-18,20 passing (17/17)</title>
<updated>2026-05-15T23:16:32+00:00</updated>
<author>
<name>Your Name</name>
<email>you@example.com</email>
</author>
<published>2026-05-15T23:16:32+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub12m5exm2uk3xa674cc5r0hlyvccs5xxn7qv83ezuteefv5972nquq4j4szl/esp32-tollgate/commit/?id=50b5975ac8793d6d820c35b5999f8a909f64e71b'/>
<id>urn:sha1:50b5975ac8793d6d820c35b5999f8a909f64e71b</id>
<content type='text'>
- Add DoT reject server on port 853 (TCP RST forces DNS-over-TLS fallback)
- DNS hijack returns NXDOMAIN for all non-A query types (no forwarding for unauthed)
- Shorter TTL on hijack responses (10s) for faster captive detection
- Explicit 302 redirect handlers for /generate_204, /hotspot-detect.html, etc.
- HTTP and DNS request logging for debugging captive detection
- Per-MAC tracking in firewall (find_by_mac, get_mac_for_ip with ARP fallback)
- Session MAC tracking (session_find_by_mac)
- Phase 2 test 18: add route through TollGate before ping test
- All 17 Phase 2 tests pass (15-21 + whoami + portal form)
</content>
</entry>
</feed>
