Powering Africa with affordable, pay-as-you-go solar energy on blockchain.
Stellar SolarGrid is a decentralized PAYG solar energy platform built on Soroban, within the Stellar ecosystem. Households and small businesses in underserved regions access solar electricity through flexible micro-payments — no large upfront costs required.
graph TD
subgraph IoT
Meter["IoT Smart Meter"]
end
subgraph Messaging
Broker["Mosquitto MQTT Broker"]
end
subgraph Backend Services
Bridge["IoT Bridge"]
Backend["Express Backend REST API"]
end
subgraph Blockchain
Contract["Soroban Smart Contract"]
end
subgraph Client
Frontend["React Frontend"]
end
subgraph External
Provider["Energy Provider"]
end
Meter -- "1. Sends usage payloads (MQTT)" --> Broker
Broker -- "2. Consumes usage payloads" --> Bridge
Bridge -- "3. Calls batch_update_usage" --> Contract
Contract -- "4. Emits meter:activated/deactivated events" --> Bridge
Bridge -- "5. Sends ON/OFF relay commands" --> Meter
Frontend -- "Calls contract directly" --> Contract
Frontend -- "Queries REST API" --> Backend
Backend -- "Fires webhook notifications" --> Provider
Users purchase energy access through flexible payment plans (daily, weekly, or usage-based stablecoin payments) via the React Frontend dashboard, which interacts directly with the Soroban smart contract. The contract verifies the payment, activates the user's meter, and tracks the remaining energy units or time validity.
Rather than updating every usage update individually, the IoT Bridge consumes MQTT payloads sent by active smart meters to the Mosquitto broker, aggregates them, and calls batch_update_usage on the Soroban smart contract in a single batch transaction. This saves gas and transaction fees on the Stellar network.
To prevent unauthorized usage reports or unauthorized meter controls, an Allowlist checks and verifies that only registered smart meters (registered via the admin CLI/dashboard) can be active on the system. Additionally, the IoT Bridge/Oracle address is allowlisted on the smart contract to restrict usage updates to trusted nodes.
For local development setup and contributing guidelines, please refer to the Contributing Guide.
- Smart Meter Integration — IoT meters with real-time usage monitoring and on/off control
- Flexible Payment Plans — Daily, weekly, or usage-based micro-payments in stablecoins
- Automated Access Control — Smart contracts enable/disable electricity based on payment status
- Energy Usage Tracking — Dashboards for users and providers
- Rust +
wasm32-unknown-unknowntarget - Stellar CLI
- Node.js >= 18
- Freighter Wallet (browser extension)
cd contracts
cargo build --target wasm32-unknown-unknown --release
stellar contract deploy --wasm target/wasm32-unknown-unknown/release/solar_grid.wasm --network testnetDeployment guidance:
- Prefer setting
adminandtoken_addressthrough the contract constructor at deploy time so initialization is atomic. - If you must call
initialize, do it in the same transaction flow as deployment. Leaving the contract uninitialized after deploy creates a front-running risk where another caller can initialize first.
cd frontend
npm install
npm run devcd backend
npm install
npm run devThe backend stores IoT usage events in a local SQLite database at backend/data/usage-events.sqlite by default. Set USAGE_EVENTS_DB_PATH to override the file location.
You can spin up the infrastructure (MQTT broker and the backend service) using Docker Compose:
- Copy the environment template at the repository root:
cp .env.example .env
- Populate the
.envfile with yourCONTRACT_ID,ADMIN_SECRET_KEY, andVITE_CONTRACT_ID. - Start the services:
docker compose up --build
The env-check service validates that all required environment variables are correctly populated before the backend starts up, preventing silent configuration errors.
The SolarGrid contract manages:
| Function | Description |
|---|---|
register_meter(meter_id, owner) |
Register a new smart meter |
make_payment(meter_id, amount, plan) |
Pay for energy access |
check_access(meter_id) |
Check if meter is currently active |
get_usage(meter_id) |
Retrieve usage data |
update_usage(meter_id, units) |
Called by IoT oracle to update consumption |
deactivate_meter(meter_id) |
Admin-only: immediately deactivate a meter |
GET /api/meters/:id/balance
Returns the live balance, usage, and active status for a single meter. Responses are cached for 5 seconds to reduce RPC load. The frontend UserDashboard polls this endpoint every 30 seconds.
Response
{
"meter_id": "METER1",
"balance": 5000000,
"units_used": 1200,
"active": true
}| Status | Description |
|---|---|
| 200 | Meter found, returns balance data |
| 404 | Meter not found |
The Meter struct carries a version: u32 field (currently 1). When the struct layout changes in a future release, existing persistent storage entries must be migrated before they can be read by the new code.
- Deploy the new contract WASM (the old entries remain in persistent storage).
- For each registered meter, call the admin-only
migrate_meter(meter_id)function.
It reads the entry as the previous schema (LegacyMeter) and writes it back as the currentMeterv1. - Once all entries are migrated, the
LegacyMetertype andmigrate_meter_v0helper can be removed in a subsequent release.
# Example: migrate a single meter via Stellar CLI
stellar contract invoke \
--id <CONTRACT_ID> \
--source <ADMIN_SECRET> \
--network testnet \
-- migrate_meter --meter_id METER1Note:
migrate_meteris idempotent per entry — calling it on an already-migrated meter will overwrite with the same data. Always test migrations on testnet before mainnet.
Deployed on Stellar Testnet. Switch to Mainnet for production.
- Never commit
.envfiles. Copy.env.exampleto.envand populate locally. ADMIN_SECRET_KEYis loaded once at backend startup into aKeypairobject; the raw secret string is not referenced anywhere after module initialisation.- All error handlers log only
err.message— raw error objects (which may contain XDR or serialised environment variables) are never logged. - Enable secret scanning in CI (e.g.
git-secrets, GitHub secret scanning) to prevent accidental key commits.
MIT