Querying Energy Data
All EnergyAS data is public and queryable by anyone — no access controls, no registration required. This guide covers the three ways to query data, from simplest to most low-level.
| Approach | Best for |
|---|---|
| SDK (EnergyQuery) | Applications and scripts — typed methods, no GraphQL required |
| Subgraph (GraphQL) | Custom queries, dashboards, analytics |
| Direct contract calls | On-chain integrations, auditing specific values |
Using the SDK
The EnergyQuery class from the SDK is the easiest way to fetch data. It handles authentication, request construction, and response parsing for you. See the full SDK Reference for setup and all available methods.
import { EnergyQuery } from 'energy-attestation-sdk';
const query = new EnergyQuery({
network: 'polygon',
apiKey: 'YOUR_THEGRAPH_API_KEY',
});Common queries
// Global protocol stats
const protocol = await query.getProtocol();
console.log(`${protocol.totalProjects} projects, ${protocol.totalGeneratedWh} Wh generated`);
// List organizations sorted by generation
const orgs = await query.getWatchers({
orderBy: 'totalGeneratedWh',
orderDirection: 'desc',
first: 10,
});
// Get an organization with all its projects
const org = await query.getWatcher('1');
console.log(org.projects);
// Get energy records for a project (most recent first)
const records = await query.getAttestations({
projectId: '5',
replaced: false,
orderBy: 'fromTimestamp',
orderDirection: 'desc',
});
// Daily snapshots for a chart
const snapshots = await query.getDailySnapshots({
projectId: '5',
dateFrom: '2025-01-01',
dateTo: '2025-01-31',
});For the full method reference — including filtering, pagination, and all return types — see SDK Reference: Querying Data.
Using the Subgraph Directly
If you need custom GraphQL queries beyond what the SDK exposes, you can query the subgraph directly. The subgraph indexes all EnergyRegistry events into a queryable GraphQL API.
Endpoint
const SUBGRAPH_URL = 'https://gateway.thegraph.com/api/subgraphs/id/<SUBGRAPH_ID>';
async function querySubgraph(query, variables = {}) {
const response = await fetch(SUBGRAPH_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({ query, variables }),
});
const { data } = await response.json();
return data;
}Subgraph IDs by network:
| Network | Subgraph ID |
|---|---|
| Polygon Amoy (testnet) | 33b8nJcqxLyH96eSyyHC9vsdvHifUXejLzMEtqLeySBC |
| Polygon | D8AgWoxUr3aDgWQCEy2hVeU8hnsrs5N3vmPSeGMphgEi |
| Celo | 9BM6kQzg7jtcbnphW7UXCGMmvJ2QtDkxnU8fqDYinUx1 |
Indexed entities
| Entity | ID format | Description |
|---|---|---|
| Protocol | "protocol" | Singleton with global counters |
| Resolver | resolver address | Authorization status of resolver contracts |
| EnergyType | uint8 as string (e.g. "1") | Energy source types with total generated |
| Watcher | watcherId as string (e.g. "1") | Monitoring organizations |
| WatcherOwnershipTransfer | txHash-logIndex | Immutable audit trail for ownership changes |
| Project | projectId as string (e.g. "1") | Individual energy sites |
| EnergyAttestation | EAS uid (bytes32 hex) | Individual records with readings array |
| ProjectAttester | projectId-address | Per-project submitter whitelist entries |
| WatcherAttester | watcherId-address | Organization-wide submitter whitelist entries |
| DailyEnergySnapshot | projectId-YYYY-MM-DD | Daily aggregates per project for charts |
Example queries
Protocol stats
{
protocol(id: "protocol") {
totalAttestations
totalGeneratedWh
totalConsumedWh
totalWatchers
totalProjects
}
}List organizations
{
watchers(
first: 10
orderBy: totalGeneratedWh
orderDirection: desc
) {
id
name
owner
totalGeneratedWh
totalConsumedWh
projectCount
}
}Organization detail with projects
{
watcher(id: "1") {
id
name
owner
totalGeneratedWh
projects {
id
name
energyType { id name }
totalGeneratedWh
attestationCount
}
watcherAttesters {
attester
active
}
}
}Project records (non-replaced only)
{
energyAttestations(
first: 50
orderBy: fromTimestamp
orderDirection: desc
where: { project: "1", replaced: false }
) {
id
energyWh
readings
fromTimestamp
toTimestamp
attester
metadataURI
txHash
}
}Replacement chain — follow corrections
When a record is corrected, the original is marked replaced: true and links to the new version:
{
energyAttestation(id: "0xoriginaluid...") {
id
replaced
replacedBy {
id
energyWh
readings
}
}
}Daily energy snapshots (time series)
{
dailyEnergySnapshots(
first: 30
orderBy: timestamp
orderDirection: desc
where: { project: "1" }
) {
date
generatedWh
consumedWh
attestationCount
}
}Energy distribution by type
{
energyTypes(
orderBy: totalGeneratedWh
orderDirection: desc
where: { registered: true }
) {
id
name
totalGeneratedWh
}
}Pagination
# Page 1
{ watchers(first: 10, skip: 0) { id name } }
# Page 2
{ watchers(first: 10, skip: 10) { id name } }The Graph caps first at 1000. Use skip to page through larger result sets.
Tips
- Entity IDs are strings. Query
watcher(id: "1"), notwatcher(id: 1). - Energy type is a relation. Query
energyType { id name }, not a scalar. - Consumers have
nullenergyType. Filter generators withwhere: { energyType_not: null }. - Timestamps are BigInt strings. The Graph handles ordering correctly when you sort them as strings.
- Use
replaced: falseto exclude corrected records and show only the current version.
Direct Contract Calls
For on-chain integrations or when you need to read values directly from the contract, you can call the EnergyRegistry functions via ethers, viem, or any web3 library.
const watcher = await registry.getWatcher(watcherId);
console.log(watcher.name, watcher.owner);
const projects = await registry.getWatcherProjects(watcherId);Energy totals
const generated = await registry.getTotalGeneratedEnergy(projectId);
const consumed = await registry.getTotalConsumedEnergy(projectId);
console.log(`Generated: ${generated} Wh, Consumed: ${consumed} Wh`);
// Watcher-level totals
const totalGen = await registry.getTotalGeneratedEnergyByWatcher(watcherId);
const totalCon = await registry.getTotalConsumedEnergyByWatcher(watcherId);Check if a period has been attested
import { ZeroHash } from 'ethers';
const uid = await registry.getAttestedPeriodUID(projectId, fromTimestamp, toTimestamp);
const isAttested = uid !== ZeroHash;Follow the replacement chain
// Get the timestamp after the last attested period
const lastTs = await registry.getProjectLastTimestamp(projectId);
// Walk the replacement chain for an attestation
let currentUid = originalUid;
let replacementUid = await registry.getReplacementUID(currentUid);
while (replacementUid !== ZeroHash) {
currentUid = replacementUid;
replacementUid = await registry.getReplacementUID(currentUid);
}
// currentUid is now the latest version of the recordCheck submitter authorization
const isAuthorized =
await registry.isProjectAttester(projectId, wallet) ||
await registry.isWatcherAttester(watcherId, wallet);Event indexing
If you're building your own indexer rather than using the subgraph, the key events are:
EnergyAttested— every new record, with full readings arrayEnergyReplaced— every correction, with old and new energy valuesProjectRegistered/ProjectDeregistered— project lifecycleWatcherRegistered— new organization onboardingWatcherAttesterAdded/WatcherAttesterRemoved— authorization changes
See the Events reference for full parameter signatures.