1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
|
# TollGate Core Extraction Roadmap
**Branch:** `feature/tollgate-core-v2`
**Start commit:** `851801f` (Phase 5 complete, hardware-verified)
**Goal:** Extract all portable TollGate business logic into `components/tollgate_core/` for reuse in NerdQAxePlus and other ESP32 firmware.
## Status (updated 2026-05-23)
| Phase | Description | Status | Commit |
|-------|-------------|--------|--------|
| 5 | Shim layer wiring | Done | `851801f` |
| 6a-6d | Consolidate & clean up | Done | `9330b01` |
| 6e-6f | Break circular deps | Done | `fb8558c` |
| 7 | Eliminate shims | Done | `5140a30` |
| 8a-8b | Extract beacon_price + market into component | Done | `0642900` |
| 8c-8f | Extract captive_portal, stratum_client, mint_health, tollgate_client | Done | `be4d490` |
| 9a-9b | NerdQAxePlus build with TOLLGATE=1 | Done | `371ae9ee` |
| 9c | BitAxe platform implementation | Future | — |
| 10a | Component metadata v1.1.0, README, CHANGELOG | Done | `a40cf60` |
| 10b | CI pipeline (GitHub Actions) | Future | — |
| 10c | Publish to IDF Component Registry | Future | — |
## Testing Protocol
After **every** step:
1. `make test-unit` -- all 21+ unit tests must pass
2. `idf.py build` -- ESP-IDF build must succeed
3. After multi-step phases: flash Board A + `pytest tests/test_smoke.py --board=a`
---
## Phase 6: Consolidate & Clean Up
### 6a. Delete standalone `tollgate_core/`
Dead code -- nothing in the build references it. The component version is what's compiled.
- [ ] `git rm -r tollgate_core/`
- [ ] Verify nothing references it: `grep -r "tollgate_core/" main/ components/ tests/`
- [ ] Test: `make test-unit` + `idf.py build`
### 6b. Eliminate `dns_server.c` duplication
`main/dns_server.c` (316 lines) duplicates `components/tollgate_core/src/tollgate_core_dns.c`.
- [ ] Rewrite `main/dns_server.c` as thin shim: `dns_server_*()` calls `tollgate_core_dns_*()`
- [ ] Update `main/dns_server.h` to keep original function signatures
- [ ] Move `dns_server_set_client_authenticated()` notification to component's internal DNS
- [ ] Add unit test for DNS shim if not covered by existing `test_firewall_sandbox`
- [ ] Test: `make test-unit` + `idf.py build` + flash + pytest smoke
### 6c. Eliminate `stratum_proxy.c` duplication
`main/stratum_proxy.c` (160 lines) duplicates `components/tollgate_core/src/tollgate_core_stratum_proxy.c`.
- [ ] Rewrite `main/stratum_proxy.c` as thin shim: `stratum_proxy_*()` -> `tollgate_core_stratum_proxy_*()`
- [ ] Update `main/stratum_proxy.h` to keep original type names for backward compat
- [ ] Test: `make test-unit` + `idf.py build`
### 6d. Move sandbox logic into component firewall
`main/firewall.c` has `is_sandbox_allowed()` (allows TCP to ports 80/2121/mining_port for unauth clients) -- missing from component.
- [ ] Add `tollgate_core_fw_set_sandbox_ports()` to component's firewall API
- [ ] Add `tollgate_core_fw_is_sandbox_allowed()` to component's internal firewall
- [ ] Update component's `tollgate_core_ip4_canforward_filter` to check sandbox rules
- [ ] Update `main/firewall.c` shim to call `tollgate_core_fw_set_sandbox_ports()` in init
- [ ] Add unit test for sandbox logic with known port combinations
- [ ] Test: `make test-unit` + `idf.py build` + flash + `pytest tests/test_dns_firewall.py --board=a`
### 6e. Break `mint_health` <-> `tollgate_api` circular dependency
`mint_health.c` includes `tollgate_api.h` for `tls_worker_set_queue()`. `tollgate_api.c` includes `mint_health.h` for `mint_health_get_all()`.
- [ ] Extract `QueueHandle_t tls_worker_queue` into a shared module (e.g., `tls_worker.h/c`)
- [ ] `mint_health.c` includes `tls_worker.h` instead of `tollgate_api.h`
- [ ] `tollgate_api.c` includes `tls_worker.h` to get the queue
- [ ] Test: `make test-unit` + `idf.py build`
### 6f. Break `config.h` -> `lightning_payout.h` reverse dependency
`config.h` includes `lightning_payout.h` for `payout_config_t` type.
- [ ] Move `payout_config_t` typedef to `config.h` (or a shared `tollgate_types.h`)
- [ ] Remove `#include "lightning_payout.h"` from `config.h`
- [ ] Add `#include "config.h"` to `lightning_payout.c` if needed
- [ ] Test: `make test-unit` + `idf.py build` + **commit + push**
---
## Phase 7: Eliminate Shim Files
Remove thin wrappers. Consumers use component headers directly.
### 7a. Remove `session.c` / `session.h` shim
- [ ] Update `tollgate_api.c`: `#include "tollgate_core_session.h"` instead of `#include "session.h"`
- [ ] Update `captive_portal.c`: same
- [ ] Update any other consumers of `session.h`
- [ ] Delete `main/session.c` and `main/session.h`
- [ ] Remove from `main/CMakeLists.txt` SRCS
- [ ] Update unit test Makefile if needed
- [ ] Test: `make test-unit` + `idf.py build`
### 7b. Remove `cashu.c` / `cashu.h` shim
- [ ] Update `tollgate_api.c`: `#include "tollgate_core_cashu.h"` instead of `#include "cashu.h"`
- [ ] Move multi-mint logic (iterating `accepted_mints[]`) into component's `tollgate_core_cashu_is_mint_accepted()`
- [ ] Delete `main/cashu.c` and `main/cashu.h`
- [ ] Remove from `main/CMakeLists.txt` SRCS
- [ ] Test: `make test-unit` + `idf.py build`
### 7c. Remove `mining_payment.c` / `mining_payment.h` shim
- [ ] Update all consumers: `#include "tollgate_core_mining.h"` instead of `#include "mining_payment.h"`
- [ ] Delete `main/mining_payment.c` and `main/mining_payment.h`
- [ ] Remove from `main/CMakeLists.txt` SRCS
- [ ] Test: `make test-unit` + `idf.py build`
### 7d. Remove `firewall.c` / `firewall.h` shim
After 6d, firewall shim only has lwIP hook registration + DNS notification.
- [ ] Move lwIP hook registration into `tollgate_main.c` or a new `main/esp_hooks.c`
- [ ] Update consumers to use `tollgate_core_fw_*()` directly
- [ ] Delete `main/firewall.c` and `main/firewall.h`
- [ ] Remove from `main/CMakeLists.txt` SRCS
- [ ] Test: `make test-unit` + `idf.py build` + flash + pytest smoke + DNS/firewall tests
- [ ] **Commit + push**
---
## Phase 8: Extract Layer 1 into Component
### 8a. Extract `beacon_price.c` -> `tollgate_core_beacon.c`
Dependencies: `config`, `identity`, `esp_wifi`, `mbedtls/sha256`
- [ ] Create `components/tollgate_core/src/tollgate_core_beacon.c/h`
- [ ] Abstract WiFi vendor IE API via `tollgate_platform_t` callbacks: `set_vendor_ie()`, `scan_start()`
- [ ] Move mint URL + npub hashing, geohash embedding, IE construction to component
- [ ] Rewrite `main/beacon_price.c` as thin ESP-specific glue calling component
- [ ] Add unit test with known IE vectors
- [ ] Test: `make test-unit` + `idf.py build`
### 8b. Extract `market.c` -> `tollgate_core_market.c`
Dependencies: `beacon_price`, `config`, `identity`, `esp_wifi`
- [ ] Create `components/tollgate_core/src/tollgate_core_market.c/h`
- [ ] Abstract WiFi scan results via platform callback: `on_scan_result()`
- [ ] Move market entry table, price comparison, cheapest selection to component
- [ ] Rewrite `main/market.c` as thin glue
- [ ] Add unit test for market table operations
- [ ] Test: `make test-unit` + `idf.py build`
### 8c. Extract `captive_portal.c` -> `tollgate_core_portal.c`
Dependencies: `firewall`, `session`, `config`, `mining_payment`, `stratum_proxy`, `esp_http_server`
- [ ] Create `components/tollgate_core/src/tollgate_core_portal.c/h`
- [ ] Extract template rendering logic (HTML generation, `__AP_IP__`/`__PRICE__` substitution)
- [ ] Extract captive detection URI handling (generate_204, hotspot-detect, success.txt, etc.)
- [ ] Extract payment processing flow (POST token -> decode -> validate -> grant)
- [ ] Abstract HTTP server via platform callbacks: `httpd_start()`, `register_handler()`, `send_response()`
- [ ] Keep ESP `httpd` glue in `main/captive_portal.c` (thin handler registration)
- [ ] Add unit test for template substitution + captive URI detection
- [ ] Test: `make test-unit` + `idf.py build` + flash + pytest portal tests
### 8d. Extract `stratum_client.c` -> `tollgate_core_stratum_client.c`
Dependencies: `stratum_proxy`, `mining_payment`, `config`, `esp_transport`
- [ ] Create `components/tollgate_core/src/tollgate_core_stratum_client.c/h`
- [ ] Abstract TCP transport via platform callbacks
- [ ] Move Stratum V1 protocol logic (subscribe, authorize, handle mining.notify, submit share)
- [ ] Rewrite `main/stratum_client.c` as thin glue
- [ ] Add unit test for Stratum message parsing
- [ ] Test: `make test-unit` + `idf.py build`
### 8e. Extract `mint_health.c` -> `tollgate_core_mint_health.c`
Dependencies: `tls_worker` (after 6e), `nucula_wallet`, `esp_http_client`
- [ ] Create `components/tollgate_core/src/tollgate_core_mint_health.c/h`
- [ ] Abstract HTTP client via platform callback: `http_get()`, `tls_worker_queue`
- [ ] Move mint probing logic, health state tracking, reachable/unreachable marking
- [ ] Rewrite `main/mint_health.c` as thin glue
- [ ] Add unit test for health state machine
- [ ] Test: `make test-unit` + `idf.py build`
### 8f. Extract `tollgate_client.c` -> `tollgate_core_client.c`
Dependencies: `config`, `market`, `nucula_wallet`, `esp_http_client`
- [ ] Create `components/tollgate_core/src/tollgate_core_client.c/h`
- [ ] Abstract HTTP + wallet via platform callbacks
- [ ] Move upstream TollGate discovery, auto-pay, usage tracking, auto-renew logic
- [ ] Rewrite `main/tollgate_client.c` as thin glue
- [ ] Add unit test for client state machine (already exists: `test_tollgate_client.c`)
- [ ] Test: `make test-unit` + `idf.py build` + flash + full pytest suite
- [ ] **Commit + push**
---
## Phase 9: NerdQAxePlus Integration
### 9a. Restore miner-integration worktree
- [ ] `git worktree add /home/c03rad0r/esp32-miner-integration feature/miner-integration`
- [ ] Or create fresh from `remotes/orangesync/feature/miner-integration`
- [ ] Verify NerdQAxePlus fork at `/home/c03rad0r/esp-miner-nerdqaxeplus/` is intact
### 9b. Copy finalized `tollgate_core` component
- [ ] Sync `components/tollgate_core/` from esp32-tollgate -> NerdQAxePlus `components/tollgate_core/`
- [ ] Update NerdQAxePlus `CMakeLists.txt` to depend on `tollgate_core`
- [ ] Verify `BOARD=NERDAXE TOLLGATE=1 idf.py build` succeeds
### 9c. Implement `tollgate_platform_t` for BitAxe/BM1397
- [ ] Create `components/tollgate_baxe/` with BitAxe-specific platform implementation
- [ ] Implement callbacks: `get_price_sats()`, `get_mint_url()`, `spend_proofs()`, stratum config
- [ ] Wire BM1397 ASIC -> stratum proxy -> tollgate_core mining pipeline
- [ ] Wire eCash payment -> session -> internet access on BitAxe AP
### 9d. Integrate into NerdQAxePlus UI
- [ ] Add payment status to OLED/LCD display
- [ ] Add WiFi AP setup with SSID derived from identity
- [ ] Add Cashu token input via web portal
- [ ] Test: Flash NerdAxe Ultra + mining test + payment test + internet verification
- [ ] **Commit + push**
---
## Phase 10: Publish
### 10a. Component metadata
- [ ] Update `idf_component.yml` with proper version, description, dependencies
- [ ] Add `README.md` to `components/tollgate_core/` with API docs
- [ ] Add `CHANGELOG.md` to component
### 10b. CI pipeline
- [ ] GitHub Actions or self-hosted CI: build on push, run unit tests
- [ ] Hardware-in-the-loop testing on push to develop (Board A)
- [ ] Integration test matrix: Board A + Board B + Board C
### 10c. Publish to IDF Component Registry
- [ ] `compote component upload` to ESP-IDF Component Registry
- [ ] Verify `idf.py add-dependency` works from a clean project
- [ ] Document usage in top-level README
---
## Dependency Graph (Extraction Order)
```
Layer 0: dns_server, lnurl_pay, asic_miner (no main/ deps)
Layer 1: config, identity, session, cashu, mining (foundation)
Layer 2: firewall, beacon_price, lightning_payout (depends on Layer 1)
Layer 3: market, stratum_proxy, stratum_client (depends on Layer 2)
Layer 4: captive_portal, tollgate_client, mint_health (depends on Layer 3)
Layer 5: tollgate_api (depends on everything)
```
## Current Test Coverage
| Type | Count | Command |
|------|-------|---------|
| Host unit tests | 21 | `make test-unit` |
| Integration tests | 17 | `TOLLGATE_IP=x make test-integration` |
| E2E tests | 3 suites | `make test-e2e` |
| Pytest (hardware) | 12 files | `pytest tests/ --board=a` |
|