Verifiable renewable energy certificates (RECs) for Nigerian solar mini-grids.
AI-verified meter data → one on-chain certificate per MWh → sold to buyers → retired forever.
IEEE ClimateChain Global Hackathon · Track: Renewable Energy & Energy Trading Built by Lawal Ridwan, frontend & web3 developer, Nigeria
Nigeria has 100+ solar mini-grid operators. Together they power communities, markets and clinics that the national grid doesn't reach. They produce real clean energy, but they can't easily prove it:
| Pain | Why it matters |
|---|---|
| Paper or spreadsheet RECs get double-counted | The same MWh can be sold to two buyers, or claimed by a funder and a corporate at the same time. |
| Meter data can be faked | Inflated readings, generation logged at night, copied rows. Funders paying per kWh have no cheap way to check. |
| Verification is slow and costly | Manual audits don't work for a 50 kW site that makes about 1–2 MWh a month. Small operators get left out of carbon and REC markets. |
| Buyers can't trust claims | Corporates reporting Scope 2 emissions need proof that a certificate was retired once and can't be resold. |
The result: clean generation that is real but hard to sell, and funders who either over-pay for fake kWh or under-fund honest operators.
SunLedger has three parts:
- AI meter verification (
ai/anomaly.py). Every hourly reading is checked against a physical model of what the panels could produce given the irradiance sensor (capacity_kW × irradiance/1000 × performance ratio × temperature derate). An IsolationForest (scikit-learn) runs on the residuals as well. Each batch gets an anomalyScore (0–100) and a SHA-256 data hash. - On-chain certificates (
contracts/SunLedgerREC.sol, ERC-1155 + AccessControl).- 1 verified MWh = 1 token. Each site+period gets its own token id, with on-chain metadata:
siteId, periodStart, periodEnd, kWh, anomalyScore, dataHash. - A meter batch hash can only be minted once, and a site's periods can never overlap. This blocks double counting.
- Batches with anomalyScore above the threshold are rejected on-chain.
- Physical cap: a batch can never claim more than
nameplate kW × hours. - Sub-MWh remainders carry forward per site, so small sites still earn certificates over time.
retire(id, amount, beneficiary, reason)burns the certificates and writes a permanentRetirementrecord, so a claimed REC can't be resold.
- 1 verified MWh = 1 token. Each site+period gets its own token id, with on-chain metadata:
- Oracle (
ai/oracle.py). Runs the detector and outputs the exactsubmitBatchpayload as JSON. It can also broadcast the call with web3.py. In quarantine mode it drops the flagged hours and certifies only the clean energy. The operator still gets paid for real generation, and the fakes earn nothing.
flowchart LR
subgraph Site["☀️ Mini-grid site (e.g. 50 kW, Kano)"]
M[Smart meter<br/>hourly kWh]
P[Pyranometer<br/>irradiance + temp]
end
subgraph Oracle["🧠 SunLedger Oracle (off-chain)"]
C[Canonical batch<br/>SHA-256 dataHash]
PH[Physics model<br/>cap × irr/1000 × PR]
IF[IsolationForest<br/>on residuals]
S[anomalyScore 0–100<br/>+ per-hour flags]
end
subgraph Chain["⛓️ SunLedgerREC (ERC-1155) on Celo / Base"]
R[Site registry<br/>OPERATOR_ROLE]
V{submitBatch<br/>ORACLE_ROLE}
T[REC tokens<br/>1 token = 1 MWh]
RT[retire → burn +<br/>permanent Retirement]
end
B[🏭 Corporate buyer /<br/>funder / DFI]
M --> C
P --> PH
C --> PH --> IF --> S
S --> V
R --> V
V -- "hash unused · no overlap ·<br/>score ≤ threshold · ≤ nameplate" --> T
V -. "else revert" .-> X[❌ Rejected]
T -- transfer / sell --> B
B --> RT
- Climate impact: clean MWh from distributed solar becomes a tradeable, auditable asset. Operators get a second revenue stream on top of tariffs, which helps them expand and displace diesel generators.
- Energy trading: RECs are standard ERC-1155 tokens. They can be sold peer-to-peer, listed on any marketplace, or bundled for corporate buyers. Every token traces back to a site, a period and a hashed meter batch.
- Trust without auditors on every site: anyone can re-run the open-source detector against the raw data and check the dataHash, and the contract enforces the rules nobody should have to trust anyone on (no double minting, no over-capacity, no resale after retirement).
ai/sample_data.csv holds 168 hourly readings (5–11 Oct 2026, WAT) from a simulated 50 kW site in Kano,
generated by ai/generate_sample_data.py with a solar-position model, day-to-day haze and cloud,
temperature derating and meter noise. Six fake readings were injected
(the ground truth is in ai/injected_labels.json, which the detector never reads):
| Injected fake | Detected as |
|---|---|
| 18.4 kWh at 02:00 | 🔴 critical: generation at night |
| 11.75 kWh at 23:00 | 🔴 critical: generation at night |
| 96.3 kWh in one hour (50 kW site) | 🔴 critical: exceeds nameplate + ML outlier |
| 71.2 kWh in one hour | 🔴 critical: exceeds nameplate + ML outlier |
| Cloudy hour inflated 17.4 → 37.1 kWh | 🟠 suspicious: IsolationForest + robust z on residual |
| Afternoon reading +17.5 kWh | 🟠 suspicious: IsolationForest + robust z on residual |
- 6/6 fakes caught, 0 false positives on the 162 genuine hours.
- Tampered batch: anomalyScore 100 → the contract reverts with
AnomalyTooHigh. - Same week with the fakes reverted to genuine readings: anomalyScore 0.
- Quarantine mode: 1,519 kWh claimed → 1,248 kWh verified → 1 REC minted, 248 kWh carried forward.
End-to-end run on a local Hardhat chain (npm run demo):
SunLedgerREC at 0x5FbD…0aa3 | site #1 registered (50 kW, Kano)
Tampered batch (score 100, 1519 kWh) -> REJECTED: AnomalyTooHigh
Verified batch (score 0, 1248 kWh) -> minted 1 REC, carry 248 kWh
Replay of same meter batch -> REJECTED: DuplicateBatch
Retired 1 REC for "Example Manufacturing Ltd (Kano)" (Q4 2026 Scope 2 claim); buyer balance now 0
sunledger/
├── contracts/SunLedgerREC.sol ERC-1155 + AccessControl REC contract
├── test/SunLedgerREC.test.ts 21 Hardhat tests
├── scripts/deploy.ts env-based deploy (Celo Sepolia / Base Sepolia / local)
├── scripts/demo.ts end-to-end: oracle payload → reject/mint → transfer → retire
├── hardhat.config.ts
├── ai/
│ ├── anomaly.py physics model + IsolationForest → anomalyScore + dataHash
│ ├── oracle.py builds submitBatch payload (JSON); optional web3.py send
│ ├── generate_sample_data.py reproducible synthetic 50 kW Kano dataset (seeded)
│ ├── sample_data.csv 168 hourly rows with 6 injected fakes
│ ├── injected_labels.json ground truth for evaluation
│ ├── test_anomaly.py 7 Python tests
│ └── requirements.txt
├── .env.example no keys are committed
└── LICENSE MIT
Requirements: Node.js 18+ (tested on 22), Python 3.10+ (tested on 3.12).
# Smart contracts
npm install
npx hardhat compile
npx hardhat test # 21 passing
# AI detector + oracle
python3 -m venv .venv && source .venv/bin/activate
pip install -r ai/requirements.txt
python3 ai/generate_sample_data.py # optional: regenerate the dataset (seeded)
python3 ai/anomaly.py ai/sample_data.csv --capacity-kw 50
python3 ai/anomaly.py --json # machine-readable report
python3 -m unittest ai/test_anomaly.py -v # 7 passing
# Oracle payloads
python3 ai/oracle.py # strict: raw batch (will be rejected)
python3 ai/oracle.py --mode quarantine # fake hours excluded, clean energy certified
# Full end-to-end demo on a local chain
npm run democp .env.example .env # fill DEPLOYER_PRIVATE_KEY, OPERATOR_ADDRESS, ORACLE_ADDRESS
npm run deploy:celoSepolia # Celo Sepolia, chainId 11142220
npm run deploy:baseSepolia # Base Sepolia, chainId 84532Note on Celo Alfajores: Celo retired Alfajores on 30 Sep 2025 and replaced it with Celo Sepolia (Celo docs). The
alfajoresnetwork entry is still there for old tooling, but deploy toceloSepolia.
Then send a verified batch from the oracle wallet:
pip install web3
export SUNLEDGER_RPC_URL=https://forno.celo-sepolia.celo-testnet.org
export SUNLEDGER_CONTRACT=0x... ORACLE_PRIVATE_KEY=...
python3 ai/oracle.py --mode quarantine --send # preflight refuses to send a batch the contract would reject| Function | Role | Purpose |
|---|---|---|
registerSite(name, location, capacityKw, payout) |
OPERATOR | Register a mini-grid; certificates go to payout |
setSiteActive(siteId, bool) |
site operator / admin | Pause a site (e.g. meter replaced) |
submitBatch(siteId, periodStart, periodEnd, kWh, anomalyScore, dataHash) |
ORACLE | Verify and mint floor((kWh + carry)/1000) RECs |
retire(id, amount, beneficiary, reason) |
any holder | Burn RECs and store a permanent Retirement |
setAnomalyThreshold(uint8) |
ADMIN | Default 30 (0–100) |
getSite, getCertificate, getRetirement, certificateId, batchHashUsed, totalRetired |
view | Audit trail |
Checks in submitBatch: site exists and is active · hash never used · periodStart < periodEnd ≤ now ·
no overlap with the site's last period · kWh > 0 · score ≤ threshold · kWh ≤ capacity × hours.
A rejected batch does not burn its hash, so a corrected batch can still be submitted.
Anomaly score (in ai/anomaly.py):
min(100, 400 × unexplained-excess-energy share + 20 × critical hours + 8 × suspicious hours).
A critical hour is physically impossible (night generation, over nameplate, negative). A suspicious hour is one where the
IsolationForest marks it as an outlier and its residual is above ±25% / 3 kWh and its robust z-score is above 3.5.
The weights are tuned for this hackathon. Production weights would be calibrated on labelled field data.
- v0.1 (this hackathon): contract, tests, AI detector, oracle, local end-to-end demo, testnet deploy scripts.
- v0.2: operator dashboard (React + wagmi) with site map, monthly kWh, flagged hours and RECs held. Buyer portal with retirement certificate PDFs.
- v0.3: signed meter data at the edge (device keys on the meter or gateway), so the oracle verifies a signature as well as statistics. Satellite irradiance cross-check (e.g. NASA POWER) in case the site's pyranometer is tampered.
- v0.4: multi-oracle quorum (k-of-n oracles must agree on hash and score). Slashing or role revocation for bad oracles.
- v1.0: alignment with an established REC standard (e.g. I-REC) through a registered issuer, so tokens can mirror or bridge recognised certificates. Mainnet on Celo, which suits low fees and mobile-first wallets in Africa.
| Stakeholder | What they get | How we reach them |
|---|---|---|
| Mini-grid developers | A new revenue stream from RECs, and automated proof of performance for investors | Pilot with 2–3 operators in Northern Nigeria (Kano/Kaduna). Most sites already have smart meters and remote monitoring, so integration is one CSV/API export per site. |
| REA Nigeria and programme funders (Rural Electrification Agency; World Bank/AfDB-backed mini-grid programmes) | Results-based grants paid per verified kWh, with a public audit trail and less manual verification | Offer SunLedger as a verification layer for per-connection or per-kWh grant disbursement. Read-only dashboard for programme officers. |
| Corporate buyers (manufacturers, telcos, banks with ESG reporting) | Local RECs with provenance down to the site and hour, and retirement records they can show auditors | Bundle certificates from many small sites into buyer-sized lots. Retirement through a simple web checkout, with the on-chain record linked in ESG reports. |
| Climate finance / DFIs | Portfolio-level clean MWh data that can't be double-counted | Same data feed. Extend to carbon-credit methodologies for diesel displacement. |
Scaling path: 1 site → a pilot operator's portfolio → many operators across Nigeria → other ECOWAS markets.
On-chain cost per site per period is a single submitBatch transaction, so it grows linearly and stays cheap on L2s.
The detector is lightweight (it runs in seconds on a laptop), so one oracle can serve thousands of sites.
- The sample dataset is synthetic (seeded and reproducible), not field data from a real operator.
- The oracle is a single trusted role in v0.1. The roadmap adds signed meters and a multi-oracle quorum.
- The contract hasn't been audited. Don't use it with real value before an audit.
MIT © 2026 Lawal Ridwan. See LICENSE.