Submitting Energy Records
Energy records (called Attestations in the protocol) are the core data unit of EnergyAS — permanent, verifiable readings that anyone can audit.
For a complete working example including setup steps, see the End-to-End Integration tutorial.
Prepare Your Data
Each record contains interval readings in watt-hours (Wh). For example, 24 hourly readings from a solar farm:
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,
];
const fromTimestamp = Math.floor(new Date('2025-01-15T00:00:00Z').getTime() / 1000);Key rules:
- Readings are
bigintvalues in watt-hours — not kWh, not floats - The period covered is
fromTimestamp + readingCount × intervalMinutes × 60seconds - The same period cannot be submitted twice for the same project
- Each submission must start exactly where the previous one ended (no gaps)
Submit a Single Record
Using the SDK (recommended)
import { EnergySDK, Interval } from 'energy-attestation-sdk';
const sdk = await EnergySDK.fromPrivateKey({
network: 'amoy',
privateKey: process.env.DEVICE_PRIVATE_KEY,
});
const { uid, txHash } = await sdk.attestations.attest({
projectId: 1,
readings,
readingIntervalMinutes: Interval.Hourly, // or just 60
fromTimestamp,
method: 'smart-meter-api',
metadataURI: 'ipfs://QmExportHash', // optional
});
console.log('Record UID:', uid);Using the contract directly
The SDK handles ABI encoding internally. If you're not using the SDK, encode the data manually:
import { AbiCoder, ZeroAddress, ZeroHash } from 'ethers';
const data = AbiCoder.defaultAbiCoder().encode(
['uint64', 'uint32', 'uint32', 'uint256[]', 'uint64', 'string', 'string'],
[projectId, readingCount, intervalMinutes, readings, fromTimestamp, 'iot', metadataURI]
);
const tx = await eas.attest({
schema: schemaUID,
data: {
recipient: ZeroAddress,
expirationTime: 0,
revocable: true,
refUID: ZeroHash,
data,
value: 0n,
}
});
const receipt = await tx.wait();Batch Submissions
Submit multiple records in a single transaction for gas efficiency — useful when catching up on multiple periods.
Using the SDK
const { uids, txHash } = await sdk.attestations.attestBatch([
{
projectId: 1,
readings: [500_000n, 510_000n, 495_000n],
readingIntervalMinutes: Interval.Hourly,
fromTimestamp: period1Start,
method: 'smart-meter-api',
},
{
projectId: 1,
readings: [488_000n, 502_000n, 497_000n],
readingIntervalMinutes: Interval.Hourly,
fromTimestamp: period2Start,
method: 'smart-meter-api',
},
]);
console.log('Submitted UIDs:', uids);Using the contract directly (EAS multiAttest)
const items = reports.map(report => ({
recipient: ZeroAddress,
expirationTime: 0n,
revocable: true,
refUID: ZeroHash,
data: encodeReport(report),
value: 0n,
}));
await eas.multiAttest([{ schema: schemaUID, data: items }]);Zero-Generation Periods
If a site had no generation during a period (planned maintenance, outage, etc.), you still need to submit a record to keep the attestation chain intact. Future submissions will be blocked if there's a gap.
Using the SDK (easiest)
attestZeroPeriod automatically picks up from the project's last attested timestamp:
const { uid } = await sdk.attestations.attestZeroPeriod({
projectId: 1,
interval: Interval.Daily,
method: 'manual-zero-report',
metadataURI: 'ipfs://QmMaintenanceLog',
});Using the contract directly
const lastTs = await registry.getProjectLastTimestamp(projectId);
const data = AbiCoder.defaultAbiCoder().encode(
['uint64', 'uint32', 'uint32', 'uint256[]', 'uint64', 'string', 'string'],
[projectId, 1, 1440, [0n], Number(lastTs), 'manual-zero-report', '']
);
await eas.attest({ schema: schemaUID, data: { ...defaults, data } });Corrections
To correct an existing record, submit a replacement referencing the original by UID. The contract validates that the replacement covers the same project and time period, then updates the running totals by the delta. Direct revocations are blocked — corrections must always go through this mechanism to preserve the audit trail.
Using the SDK
const { uid: correctedUid } = await sdk.attestations.overwriteAttestation({
refUID: originalUid, // UID of the record being corrected
projectId: 1,
readings: correctedReadings,
readingIntervalMinutes: Interval.Hourly,
fromTimestamp,
method: 'smart-meter-api-corrected',
metadataURI: 'ipfs://QmCorrectionAuditReport',
});Using the contract directly
const tx = await eas.attest({
schema: schemaUID,
data: {
recipient: ZeroAddress,
expirationTime: 0n,
revocable: true,
refUID: originalAttestationUID, // non-zero = replacement
data: correctedData,
value: 0n,
}
});After correction, the original record is marked replaced: true in the subgraph and excluded from totals. The replacement links back to the original via replaces, and the original links forward via replacedBy — forming a full correction chain.