Documentation

End-to-End Integration

This tutorial walks through the complete EnergyAS workflow using the SDK — from installing the package to querying back the data you submitted. By the end, you'll have a working script that registers an organization, creates a project, authorizes a device wallet, submits a day of energy readings, and reads them back.

Note

This guide targets Polygon Amoy (testnet). Everything works the same way on Polygon or Celo mainnet — just change the network value.

Prerequisites

1. Install the SDK

Terminal
npm install energy-attestation-sdk

2. Set Up Environment Variables

Create a .env file (never commit this):

Environment
PRIVATE_KEY=0x...your_operator_wallet_private_key
DEVICE_PRIVATE_KEY=0x...your_iot_device_private_key
THEGRAPH_API_KEY=your_api_key_here
Note

Use separate wallets for the operator (who registers the organization and manages projects) and the device (which submits readings). This limits blast radius if a device key is ever compromised.

3. Register an Organization

JavaScript
import 'dotenv/config';
import { EnergySDK } from 'energy-attestation-sdk';

const sdk = await EnergySDK.fromPrivateKey({
  network: 'amoy',
  privateKey: process.env.PRIVATE_KEY,
});

console.log('Operator address:', sdk.address);

// Register a new organization
const { watcherId, txHash } = await sdk.watchers.createWatcher('Greenfield Energy');

console.log('Organization ID:', watcherId.toString());
console.log('Transaction:', txHash);
// → Organization ID: 7
// → Transaction: 0xabc...

Save the watcherId — you'll need it in the next steps.

4. Create an Energy Project

JavaScript
import { EnergyType } from 'energy-attestation-sdk';

const { projectId } = await sdk.projects.createProject(
  watcherId,
  'Solar Farm Alpha',
  EnergyType.SOLAR_PV,
);

console.log('Project ID:', projectId.toString());
// → Project ID: 12

Optionally, attach metadata to the project (location, certifications, etc.):

JavaScript
await sdk.projects.setProjectMetadataURI(
  projectId,
  'ipfs://QmYourMetadataCID',
);

See Project Metadata for the expected JSON format.

5. Authorize a Device to Submit Readings

JavaScript
// The device wallet address (from DEVICE_PRIVATE_KEY)
const deviceAddress = '0xYourDeviceWalletAddress';

await sdk.attesters.addAttester(projectId, deviceAddress);

console.log('Device authorized for project', projectId.toString());

The device wallet can now submit readings for this project. If the device key is ever rotated or compromised, remove it and add the new one without affecting the organization:

JavaScript
await sdk.attesters.removeAttester(projectId, oldDeviceAddress);
await sdk.attesters.addAttester(projectId, newDeviceAddress);

6. Submit Energy Readings

Now switch to the device wallet and submit a day of hourly readings. In a real system, this would run on an IoT device or automated backend:

JavaScript
import { EnergySDK, Interval } from 'energy-attestation-sdk';

// Device wallet — submits readings but cannot manage the organization
const deviceSdk = await EnergySDK.fromPrivateKey({
  network: 'amoy',
  privateKey: process.env.DEVICE_PRIVATE_KEY,
});

// 24 hourly readings for a solar farm (Wh)
// Night hours are zero, peak is around midday
const readings = [
  0n, 0n, 0n, 0n, 0n, 0n,
  15_000n, 45_000n, 120_000n, 280_000n, 410_000n, 520_000n,
  580_000n, 590_000n, 560_000n, 480_000n, 350_000n, 200_000n,
  80_000n, 20_000n, 0n, 0n, 0n, 0n,
];

// Start of the reporting period (beginning of the day in UTC)
const fromTimestamp = Math.floor(new Date('2025-01-15T00:00:00Z').getTime() / 1000);

const { uid, txHash } = await deviceSdk.attestations.attest({
  projectId: Number(projectId),
  readings,
  readingIntervalMinutes: Interval.Hourly,
  fromTimestamp,
  method: 'smart-meter-api',
  metadataURI: 'ipfs://QmDailyExportHash',  // optional evidence
});

console.log('Record submitted. UID:', uid);
console.log('Transaction:', txHash);
// → Record submitted. UID: 0x1234...

7. Query the Data Back

Once the transaction is confirmed, the subgraph indexes it within a few minutes. Read back using EnergyQuery:

JavaScript
import { EnergyQuery } from 'energy-attestation-sdk';

const query = new EnergyQuery({
  network: 'amoy',
  apiKey: process.env.THEGRAPH_API_KEY,
});

// Check organization totals
const org = await query.getWatcher(watcherId.toString());
console.log('Organization:', org.name);
console.log('Total generated:', org.totalGeneratedWh, 'Wh');
// → Organization: Greenfield Energy
// → Total generated: 4250000 Wh

// List the project's records
const records = await query.getAttestations({
  projectId: projectId.toString(),
  replaced: false,
  orderBy: 'fromTimestamp',
  orderDirection: 'desc',
});

console.log(`${records.length} record(s) found`);
for (const r of records) {
  const from = new Date(Number(r.fromTimestamp) * 1000).toISOString();
  const to   = new Date(Number(r.toTimestamp) * 1000).toISOString();
  console.log(`  ${from} → ${to}: ${r.energyWh} Wh`);
}

// Daily snapshots (for charting)
const snapshots = await query.getDailySnapshots({
  projectId: projectId.toString(),
  dateFrom: '2025-01-15',
  dateTo: '2025-01-15',
});

for (const snap of snapshots) {
  console.log(`${snap.date}: generated=${snap.generatedWh} Wh`);
}

8. Correct a Record

If you later discover that some readings were wrong, submit a correction referencing the original by UID:

JavaScript
// Corrected readings
const correctedReadings = [
  0n, 0n, 0n, 0n, 0n, 0n,
  14_000n, 43_000n, 118_000n, 275_000n, 405_000n, 515_000n,
  575_000n, 585_000n, 555_000n, 475_000n, 345_000n, 195_000n,
  78_000n, 18_000n, 0n, 0n, 0n, 0n,
];

const { uid: correctedUid } = await deviceSdk.attestations.overwriteAttestation({
  refUID: uid,  // the UID of the record being corrected
  projectId: Number(projectId),
  readings: correctedReadings,
  readingIntervalMinutes: Interval.Hourly,
  fromTimestamp,
  method: 'smart-meter-api-corrected',
  metadataURI: 'ipfs://QmCorrectionAuditReport',
});

console.log('Correction submitted:', correctedUid);
// The original record is now marked replaced: true
// Accumulators are updated by the delta automatically

9. Report a Zero-Generation Period

If the site was offline for a period, you must still submit a record to keep the attestation chain intact — otherwise future submissions will be blocked:

JavaScript
const { uid: zeroUid } = await deviceSdk.attestations.attestZeroPeriod({
  projectId: Number(projectId),
  interval: Interval.Daily,
  method: 'manual-zero-report',
  metadataURI: 'ipfs://QmMaintenanceLog',
});

console.log('Zero period submitted:', zeroUid);
// Automatically continues from the last attested timestamp

Full Script

The steps above can be combined into a single script. A reference implementation is included in the SDK repository under examples/end-to-end.ts.

What's Next