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.
This guide targets Polygon Amoy (testnet). Everything works the same way on Polygon or Celo mainnet — just change the network value.
Prerequisites
- Node.js 18+
- A wallet with Amoy testnet MATIC (get some from the Polygon Amoy faucet)
- A The Graph API key (free at thegraph.com/studio → API Keys)
1. Install the SDK
npm install energy-attestation-sdk2. Set Up Environment Variables
Create a .env file (never commit this):
PRIVATE_KEY=0x...your_operator_wallet_private_key
DEVICE_PRIVATE_KEY=0x...your_iot_device_private_key
THEGRAPH_API_KEY=your_api_key_hereUse 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
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
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: 12Optionally, attach metadata to the project (location, certifications, etc.):
await sdk.projects.setProjectMetadataURI(
projectId,
'ipfs://QmYourMetadataCID',
);See Project Metadata for the expected JSON format.
5. Authorize a Device to Submit Readings
// 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:
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:
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:
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:
// 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 automatically9. 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:
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 timestampFull 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
- SDK Reference: Querying Data — all
EnergyQuerymethods - SDK Reference: Writing Data — all
EnergySDKmethods - Project Metadata — metadata format and IPFS anchoring
- Attestation Schema — full schema and encoding details