EnergyQuery — Reading Data
EnergyQuery is a typed client for the EnergyAS subgraph. It handles authentication, GraphQL request construction, and response parsing for you. No wallet or private key required.
Setup
import { EnergyQuery } from 'energy-attestation-sdk';
const query = new EnergyQuery({
network: 'polygon', // 'amoy' | 'polygon' | 'celo'
apiKey: 'YOUR_API_KEY', // The Graph API key — get one at thegraph.com/studio
});Config options:
| Option | Type | Description |
|---|---|---|
network | string | Network to query. Must have a deployed subgraph. |
apiKey | string | The Graph API key (used in the Authorization header). |
subgraphUrl | string | Override the subgraph URL directly. Use this for local development or custom deployments instead of apiKey. |
You need either apiKey (for the standard subgraph endpoints) or subgraphUrl (for a custom or self-hosted endpoint). Providing both uses subgraphUrl and ignores apiKey.
Protocol Stats
Get global totals across all networks:
const protocol = await query.getProtocol();
console.log(protocol.totalWatchers); // number of registered organizations
console.log(protocol.totalProjects); // number of energy sites
console.log(protocol.totalAttestations); // total records submitted
console.log(protocol.totalGeneratedWh); // cumulative generated energy (Wh)
console.log(protocol.totalConsumedWh); // cumulative consumed energy (Wh)Energy Types
const types = await query.getEnergyTypes();
for (const t of types) {
console.log(t.id, t.name, t.totalGeneratedWh);
}
// e.g. "1" "Solar PV" "42000000"Organizations (Watchers)
List all organizations
const watchers = await query.getWatchers({
first: 20,
skip: 0,
orderBy: 'totalGeneratedWh',
orderDirection: 'desc',
registered: true, // optional: filter active orgs only
});
for (const w of watchers) {
console.log(w.id, w.name, w.owner, w.projectCount);
}Get a single organization with full detail
const watcher = await query.getWatcher('1'); // pass watcherId as string
if (watcher) {
console.log(watcher.name);
console.log(watcher.projects); // list of projects
console.log(watcher.watcherAttesters); // authorized submitters
console.log(watcher.ownershipHistory); // ownership transfer log
}Filter by owner address
const myOrgs = await query.getWatchers({
owner: '0xYourWalletAddress',
});Ownership history audit trail
const history = await query.getWatcherOwnershipHistory('1');
for (const transfer of history) {
console.log(transfer.previousOwner, '→', transfer.newOwner, 'at block', transfer.blockNumber);
}Projects
List projects
const projects = await query.getProjects({
first: 50,
orderBy: 'totalGeneratedWh',
orderDirection: 'desc',
watcherId: '1', // optional: filter by organization
energyTypeId: '1', // optional: '1' = Solar PV, '0' = Consumer, etc.
registered: true,
});Get a single project
const project = await query.getProject('5');
if (project) {
console.log(project.name);
console.log(project.energyType?.name); // null for consumers
console.log(project.totalGeneratedWh);
console.log(project.attestationCount);
console.log(project.metadataURI); // link to metadata (IPFS or HTTPS)
console.log(project.lastToTimestamp); // end of the last attested period
}Consumers vs generators: Consumer projects have energyType: null. To filter generators only, use energyTypeId with a non-zero type ID. To get consumers only, omit energyTypeId and filter client-side.
Energy Records (Attestations)
List records for a project
const records = await query.getAttestations({
projectId: '5',
replaced: false, // exclude corrected records
orderBy: 'fromTimestamp',
orderDirection: 'desc',
first: 50,
});
for (const r of records) {
console.log(r.fromTimestamp, r.toTimestamp, r.energyWh);
console.log(r.readings); // array of per-interval Wh values (as strings)
console.log(r.attester); // address that submitted the record
console.log(r.txHash); // transaction hash
}Get a single record by UID
const record = await query.getAttestation('0xabcd...');
if (record) {
console.log(record.replaced); // true if this record was corrected
console.log(record.replacedBy?.id); // UID of the correction
console.log(record.replaces?.id); // UID of the original this corrected
}Filter by date range
const startOfMonth = Math.floor(new Date('2025-01-01').getTime() / 1000).toString();
const endOfMonth = Math.floor(new Date('2025-02-01').getTime() / 1000).toString();
const records = await query.getAttestations({
projectId: '5',
fromTimestamp_gte: startOfMonth,
fromTimestamp_lte: endOfMonth,
replaced: false,
});Daily Snapshots (Time Series)
The subgraph pre-aggregates energy data into daily buckets — the fastest way to build charts:
const snapshots = await query.getDailySnapshots({
projectId: '5',
dateFrom: '2025-01-01', // YYYY-MM-DD (inclusive)
dateTo: '2025-01-31', // YYYY-MM-DD (inclusive)
orderDirection: 'asc',
first: 365,
});
for (const day of snapshots) {
console.log(day.date, day.generatedWh, day.consumedWh, day.attestationCount);
}Authorized Submitters
Project-level submitters
const submitters = await query.getProjectAttesters('5', { active: true });
for (const s of submitters) {
console.log(s.attester, s.active, s.addedAt);
}Organization-wide submitters
const submitters = await query.getWatcherAttesters('1', { active: true });Pagination
All list methods accept first and skip. The subgraph caps first at 1000:
// Page through all projects
let skip = 0;
let all = [];
while (true) {
const page = await query.getProjects({ first: 1000, skip });
all = all.concat(page);
if (page.length < 1000) break;
skip += 1000;
}