Overview
The eCandle x Bitaxe demo demonstrates a complete machine-to-machine (M2M) energy payment system running on embedded hardware. Two microcontrollers — an ESP32-C3 (seller) and an ESP32-S3 (buyer) — negotiate energy prices, stream payments over Bluetooth Low Energy, and settle on-chain via Circle’s infrastructure.
This post covers the technical architecture, the engineering challenges we solved, and the design decisions behind each layer.
System Architecture
┌─────────────────┐ BLE ┌─────────────────┐
│ eCandle │◄───────────────►│ Bitaxe 601 │
│ ESP32-C3 │ Price broadcast │ ESP32-S3 │
│ Solar + Inv. │ EIP-3009 sigs │ BM1370 ASIC │
│ BoAT SDK │ Energy control │ BoAT SDK │
└────────┬─────────┘ └──────────────────┘
│ WiFi + MQTT
▼
┌─────────────────┐
│ Cloud API │ Device telemetry, OTA, geolocation
│ (FastAPI) │
└────────┬─────────┘
│
▼
┌─────────────────┐ x402/REST ┌──────────────────┐
│ Settle Worker │───────────────►│ Circle Gateway │
│ (Node.js) │ EIP-3009 proofs│ Arc Testnet │
│ MQTT subscriber│ Batch settle │ Zero gas │
└──────────────────┘ └──────────────────┘
Challenge 1: Cryptographic Signing on a Microcontroller
The Constraint
The ESP32-C3 is a RISC-V single-core processor running at 160MHz with 400KB of SRAM and 4MB of flash. It cannot run Node.js, Python, or any standard blockchain SDK. There is no operating system — the firmware runs on FreeRTOS with cooperative multitasking.
Yet we need it to produce valid EIP-3009 transferWithAuthorization signatures — the same cryptographic operation that MetaMask performs when you approve a USDC transfer.

The Solution: TLAY BoAT MWR SDK
BoAT (Blockchain of AI Things) MWR (Minimum Wallet Required) SDK is a C-language library designed specifically for this class of device. It implements:
- secp256k1 elliptic curve operations optimized for RISC-V (no hardware crypto accelerator on C3) - EIP-712 typed data hashing — the structured signing format required by EIP-3009 - Keccak-256 — Ethereum’s hash function, implemented in pure C - RLP encoding — for transaction serialization

The signing flow on-device:
1. Construct EIP-3009 authorization struct:
— from: device wallet address
— to: USDC contract (transferWithAuthorization)
— value: slice price in USDC atomic units
— validAfter: 0 (immediate)
— validBefore: current_time + 86400
— nonce: random 32 bytes- EIP-712 hash: — domainSeparator = hash(name, version, chainId, verifyingContract) — structHash = hash(TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, …) — digest = hash(0x1901 || domainSeparator || structHash)
- secp256k1 sign: — (r, s, v) = ecSign(digest, privateKey) — Canonical form: if s > secp256k1n/2, s = secp256k1n — s
- Output: 65-byte signature (r[32] || s[32] || v[1]) ```
Performance: A single EIP-3009 signature takes approximately120ms on the ESP32-C3 at 160MHz. This is well within our 10-second slice interval.
Key Storage: The private key is stored in the ESP32’s flash with flash encryption enabled. In production, it would be burned into eFuse (one-time programmable) for tamper resistance.
Why BoAT Matters
Without BoAT, the alternative is to relay unsigned data to a cloud server for signing — which introduces a single point of failure, requires constant internet connectivity, and means the device doesn’t truly own its wallet. BoAT makes the device a self-sovereign economic agent.
— -
Challenge 2: Sub-Cent Payment Economics
The Problem
A single energy slice is: - Duration: 10 seconds - Power: 15 watts - Energy: 0.042 Wh - Price: ~$0.000042 (at mid-range pricing)
On Ethereum mainnet, a USDC transfer costs ~$0.50 in gas. The gas fee is 12,000x the payment amount. Even on L2s, minimum gas costs make individual nanopayments uneconomical.

The Solution: Circle Nanopayments + x402
Circle’s nanopayment infrastructure separates authorization from settlement:
- Authorization (on-device, instant): The buyer signs an EIP-3009 proof. This is a cryptographic commitment to pay, but no on-chain transaction occurs yet. Cost: zero.
- Accumulation (off-chain): Signed proofs accumulate during a streaming session. Each proof is independently verifiable — the seller can confirm the signature is valid without touching the blockchain.
- Settlement (batched, async): When a session ends, proofs are submitted to Circle Gateway via the x402 protocol. Multiple proofs settle in a single transaction, amortizing any settlement cost across many payments.

The x402 payload structure:
{
“x402Version”: 2,
“resource”: {
“url”: “ble://ecandle/energy”,
“description”: “Energy slice payment”,
“mimeType”: “application/json”
},
“payload”: {
“signature”: “0x…”,
“authorization”: {
“from”: “0xBuyerAddress”,
“to”: “0xUSDCContract”,
“value”: “42”,
“validAfter”: “0”,
“validBefore”: “1713200000”,
“nonce”: “0x…”
}
}
}Settlement target: Arc Testnet (chainId: 5042002) — Circle’s zero-gas L2, purpose-built for this class of transaction.
Dynamic Pricing Algorithm

The demo uses a sinusoidal price curve to simulate solar energy availability:
// Price oscillates between min and max over a 60-second cycle
float phase = (float)(time_sec % 60) / 60.0f * 2.0f * M_PI;
float normalized = (sinf(phase) + 1.0f) / 2.0f; // 0.0 to 1.0
uint32_t price_uslice = MIN_PRICE + (uint32_t)(normalized * (MAX_PRICE — MIN_PRICE));The buyer’s purchase threshold is configurable. In the demo, the Bitaxe calculates profitability based on: - Current BTC price (estimated) - Hashrate (measured) - Power consumption (known: 15W) - Energy price (received via BLE)
— -
Challenge 3: BLE Energy Streaming Protocol
Why BLE, Not WiFi
| Factor | BLE | WiFi P2P | — — — — | — — - | — — — — — | Power consumption | ~10mA | ~120mA | Connection setup | <500ms | 2–5s | Works without AP | Yes | Requires negotiation | Range | 10–30m (sufficient) | 50–100m (unnecessary) | Offline capable | Yes | No (if using cloud) | Coexistence with WiFi STA | Possible on single radio | Conflicts with STA mode |
|---|

BLE enables the devices to transact even when the internet is down. The eCandle can sell energy and accumulate signed proofs, settling them later when connectivity returns. This is critical for off-grid deployments.
GATT Service Design
eCandle Energy Service (UUID: custom)
├── Price Characteristic (notify)
│ └── Broadcasts current price every 10s
│ └── Format: { price_uslice: uint32, timestamp: uint32 }
│
├── Control Characteristic (write)
│ └── Buyer sends: START_SESSION, STOP_SESSION
│ └── Format: { command: uint8, params: bytes }
│
├── Payment Characteristic (write)
│ └── Buyer sends EIP-3009 signed proof per slice
│ └── Format: { slice_id: uint16, sig: bytes[65], value: uint32, nonce: bytes[32] }
│
└── Status Characteristic (notify)
└── Seller sends session state updates
└── Format: { state: uint8, slices: uint16, total_paid: uint32 }State Machine
┌──────────┐
│ IDLE │ ◄──── Price broadcast active
└────┬─────┘ No active buyer
│ Buyer connects + START_SESSION
▼
┌──────────┐
│NEGOTIATE │ ◄──── Verify buyer wallet, check balance
└────┬─────┘
│ Accepted
▼
┌──────────┐
│STREAMING │ ◄──── Energy flowing, payments streaming
└────┬─────┘ One EIP-3009 proof per 10s slice
│ STOP / price threshold / disconnect
▼
┌──────────┐
│ SETTLING │ ◄──── Accumulate proofs, submit to Gateway
└────┬─────┘
│ Settlement confirmed
▼
┌──────────┐
│ COMPLETE │ ◄──── Session stats logged
└──────────┘Reconnection and Proof Persistence
BLE connections can drop — especially on single-radio hardware where WiFi and BLE share the same 2.4GHz radio. The protocol handles this:
- Proof persistence: All received EIP-3009 proofs are written to flash immediately. Even if the device crashes, no payments are lost. 2. Session recovery: If BLE disconnects mid-session, the buyer can reconnect and resume from the last acknowledged slice. 3. Idempotent settlement: Each proof has a unique nonce. Submitting the same proof twice to the Gateway is safe — it will be deduplicated.
— -

Challenge 4: BLE + WiFi Coexistence on Single Radio
The Problem
The ESP32-C3 has a single 2.4GHz radio shared between WiFi and BLE. Naive concurrent use causes: - BLE connection drops during WiFi scans - WiFi disconnects during BLE advertising - Memory exhaustion from running both stacks simultaneously

The Solution
After extensive testing, we arrived at a specific initialization order and configuration that enables stable coexistence:
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_ESP_COEX_SW_COEXIST_ENABLE=yCritical lesson: The initialization order matters. WiFi must be initialized and connected before BLE starts. Starting BLE first and then connecting WiFi causes stack corruption on the C3’s single-core RISC-V architecture.
// Correct order:
1. esp_wifi_init()
2. esp_wifi_start()
3. Wait for WIFI_EVENT_STA_CONNECTED
4. nimble_port_init() // BLE starts AFTER WiFi is stable
5. ble_svc_gap_init()
6. ble_gatts_start()— -
Challenge 5: Settlement Pipeline
Architecture
Device (ESP32)
│ MQTT: ecandle/{device_id}/settle
▼
Settle Worker (Node.js, PM2)
│ Parse EIP-3009 proofs
│ Construct x402 payload
│ Base64 encode
▼
HashAnchor Gateway
│ POST /v1/x402/settle
│ Authorization: Bearer {api_key}
▼
Circle Gateway
│ Verify signatures
│ Execute transferWithAuthorization
▼
Arc Testnet (chainId: 5042002)
│ USDC transfer recorded
│ Zero gas for submitter
▼
Payment Server (Next.js)
│ POST /api/settlements
│ Record settlement in SQLite
▼
Demo Dashboard
└ Real-time display: SETTLED entries
Settlement Message Format (MQTT)
{
“type”: “settle”,
“payTo”: “0xSellerAddress”,
“proofs”: [
{
“from”: “0xBuyerAddress”,
“to”: “0xUSDCContract”,
“value”: “42”,
“validAfter”: “0”,
“validBefore”: “1713200000”,
“nonce”: “0xabc123…”,
“sig”: “0xdef456…”
}
]
}Why This Pipeline
The device cannot call Circle Gateway directly — it’s an HTTPS REST API, and the ESP32-C3’s TLS stack struggles with large certificate chains while simultaneously running BLE and WiFi. Instead:
- Device publishes proofs to MQTT (lightweight, already connected for telemetry) 2. Settle Worker (always-on Node.js process) subscribes and handles HTTPS complexity 3. HashAnchor provides x402 protocol wrapping and API key management 4. Circle Gateway handles the actual on-chain settlement
This separation means the device’s firmware stays simple and the settlement logic can be updated server-side without OTA.
— -
Challenge 6: OTA Safety for DePIN Devices
The Risk
A DePIN device in the field — perhaps on a rooftop in Nairobi or a hillside in Southeast Asia — cannot be physically accessed if a bad firmware update bricks it. OTA must be bulletproof.

Rollback Protection
The ESP32’s bootloader supports app rollback with our configuration:
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
CONFIG_BOOTLOADER_FACTORY_RESET=y
CONFIG_BOOTLOADER_NUM_PIN_FACTORY_RESET=9
CONFIG_BOOTLOADER_HOLD_TIME_GPIO=5The rollback flow:
1. OTA firmware written to ota_0 or ota_1 partition
2. Bootloader marks new app as “pending verification”
3. New firmware boots, starts 60-second timer
4. If WiFi connects within 60s:
→ esp_ota_mark_app_valid_cancel_rollback()
→ Firmware confirmed, will persist across reboots
5. If WiFi fails to connect:
→ Device reboots after 63s
→ Bootloader sees unverified app, rolls back to factory
→ Device recovers automaticallyTested and verified: We flashed a firmware with a known BLE crash bug via OTA. The device crashed on boot, the bootloader detected the unverified state, and automatically rolled back to the factory partition. Total downtime: ~65 seconds.
Partition Layout
Address Size Label Type
0x000000 0x006000 nvs WiFi data
0x00F000 0x002000 otadata OTA selector
0x011000 0x001000 phy_init RF calibration
0x020000 0x190000 factory Factory app (1.56MB) ← USB flashed, rollback target
0x1B0000 0x120000 ota_0 OTA slot 0 (1.125MB)
0x2D0000 0x120000 ota_1 OTA slot 1 (1.125MB)
0x3F0000 0x001000 nvs_keys NVS encryption keys— -
Performance Summary
| Metric | Value | — — — — | — — — - | EIP-3009 signing latency | ~120ms per signature | BLE throughput (payment proofs) | ~2KB/s effective | Slice interval | 10 seconds | Payment per slice | ~$0.000042 | Session setup (BLE connect → first slice) | <2 seconds | WiFi + BLE concurrent uptime | Stable over 24h+ | OTA rollback time | ~65 seconds | Firmware size (no BLE) | ~1.06MB | Firmware size (with BLE) | ~1.07MB | Free heap during streaming | ~80KB | Settlement latency (device → chain) | ❤0 seconds |
|---|
— -

Reproducibility
The demo environment is fully reproducible:
- ESP32 firmware: Built with ESP-IDF v5.4, targeting ESP32-C3 - Payment server: Next.js on GCP VPS, SQLite storage - Settle worker: Node.js, subscribes to MQTT
ecandle/+/settle- Cloud API: FastAPI (Python), MQTT broker (Mosquitto) - Settlement: Circle Gateway via HashAnchor, Arc Testnet - Demo dashboard: ecandle.xid.network/demo)
— -
What’s Next

- Arc Mainnet settlement — moving from testnet to production USDC 2. Secure element integration — ATECC608A or ESP32’s eFuse for production key storage 3. Multi-buyer support — eCandle serving multiple Bitaxe miners simultaneously 4. Standardized BLE payment profile — proposing a BLE SIG-compatible payment characteristic 5. Offline settlement queue — devices accumulate proofs for days, settle in bulk when connectivity returns
— -
Built with Arkreen eCandle, TLAY BoAT MWR SDK, and Circle Nanopayments on Arc Testnet.