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:

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
  1. EIP-712 hash: — domainSeparator = hash(name, version, chainId, verifyingContract) — structHash = hash(TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, …) — digest = hash(0x1901 || domainSeparator || structHash)
  1. secp256k1 sign: — (r, s, v) = ecSign(digest, privateKey) — Canonical form: if s > secp256k1n/2, s = secp256k1n — s
  1. 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:

  1. 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.
  1. 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.
  1. 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

FactorBLEWiFi P2P— — — —— — -— — — — —Power consumption~10mA~120mAConnection setup<500ms2–5sWorks without APYesRequires negotiationRange10–30m (sufficient)50–100m (unnecessary)Offline capableYesNo (if using cloud)Coexistence with WiFi STAPossible on single radioConflicts 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:

  1. 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=y

Critical 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:

  1. 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=5

The 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 automatically

Tested 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

MetricValue— — — —— — — -EIP-3009 signing latency~120ms per signatureBLE throughput (payment proofs)~2KB/s effectiveSlice interval10 secondsPayment per slice~$0.000042Session setup (BLE connect → first slice)<2 secondsWiFi + BLE concurrent uptimeStable over 24h+OTA rollback time~65 secondsFirmware size (no BLE)~1.06MBFirmware size (with BLE)~1.07MBFree heap during streaming~80KBSettlement latency (device → chain)❤0 seconds

— -

Reproducibility

The demo environment is fully reproducible:

— -

What’s Next

  1. 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.