Documentation

EnergySDK — Writing Data

EnergySDK handles all on-chain write operations: registering organizations and projects, managing authorized submitters, and submitting energy records. It requires a signing wallet and uses ethers v6 under the hood.

Setup

There are two ways to create an SDK instance, depending on your environment.

From a private key (Node.js / IoT / backend)

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

const sdk = await EnergySDK.fromPrivateKey({
  network: 'polygon',
  privateKey: process.env.PRIVATE_KEY,
  // rpcUrl: 'https://...',  // optional — uses the network default if omitted
});

console.log('Signing as:', sdk.address);

From a browser wallet (MetaMask / WalletConnect)

JavaScript
import { BrowserProvider } from 'ethers';
import { EnergySDK } from 'energy-attestation-sdk';

const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

const sdk = await EnergySDK.fromSigner({
  network: 'polygon',
  signer,
});

Both factory methods validate the connected chain ID against the specified network and throw a ConfigurationError if they don't match.

Organizations (Watchers)

Register a new organization

JavaScript
const { watcherId, txHash } = await sdk.watchers.createWatcher('Greenfield Energy');

console.log('Organization ID:', watcherId.toString());  // e.g. 7n
console.log('Transaction:', txHash);

The wallet that calls this becomes the organization owner. The owner can authorize submitters, create projects, and transfer ownership.

Transfer organization ownership

JavaScript
await sdk.watchers.transferWatcherOwnership(watcherId, '0xNewOwnerAddress');

Projects

Register a project

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

const { projectId, txHash } = await sdk.projects.createProject(
  watcherId,           // organization ID
  'Solar Farm Alpha',  // display name
  EnergyType.SOLAR_PV,  // energy type (or pass the numeric value directly)
);

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

EnergyType enum values:

EnumValueDescription
EnergyType.CONSUMER0Consuming site (data center, EV fleet, etc.)
EnergyType.SOLAR_PV1Solar photovoltaic
EnergyType.WIND_ONSHORE2Onshore wind
EnergyType.WIND_OFFSHORE3Offshore wind
EnergyType.HYDRO4Hydroelectric
EnergyType.BIOMASS5Biomass
EnergyType.GEOTHERMAL6Geothermal
EnergyType.OCEAN_TIDAL7Ocean / tidal
EnergyType.NUCLEAR8Nuclear
EnergyType.NATURAL_GAS9Natural gas
EnergyType.COAL10Coal
EnergyType.OIL11Oil
EnergyType.STORAGE_DISCHARGE12Battery storage discharge
EnergyType.HYDROGEN_FUEL_CELL13Hydrogen fuel cell
Note

Energy type is permanent. You cannot change a project's type after registration. This prevents misclassification of generation vs consumption data.

Attach metadata to a project

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

See Project Metadata for the expected JSON format.

Deregister a project

Deregistering blocks new submissions but preserves all historical records permanently:

JavaScript
await sdk.projects.deregisterProject(projectId);

Transfer a project to another organization

JavaScript
await sdk.projects.transferProject(projectId, targetWatcherId);

Authorized Submitters (Attesters)

You must authorize at least one wallet before any energy records can be submitted for a project.

Authorize a single submitter for a specific project

JavaScript
await sdk.attesters.addAttester(projectId, '0xDeviceWalletAddress');

Authorize multiple submitters at once

JavaScript
await sdk.attesters.addAttesters(projectId, [
  '0xDevice1Address',
  '0xDevice2Address',
]);

Authorize a submitter across all projects in an organization

Useful for backup operators or manual auditors:

JavaScript
await sdk.attesters.addWatcherAttester(watcherId, '0xOperatorAddress');

Remove authorization

JavaScript
await sdk.attesters.removeAttester(projectId, '0xDeviceWalletAddress');
await sdk.attesters.removeWatcherAttester(watcherId, '0xOperatorAddress');

Submitting Energy Records

Energy records (attestations) are the core data unit in EnergyAS. Each record covers a time period and contains one reading per interval.

Submit a single record

JavaScript
const { uid, txHash } = await sdk.attestations.attest({
  projectId: 1,
  readings: [500_000n, 510_000n, 495_000n],  // Wh per interval, as BigInt
  readingIntervalMinutes: 60,                  // interval length in minutes
  fromTimestamp: 1735689600,                   // Unix timestamp (start of first interval)
  method: 'smart-meter-api',                   // collection method label
  metadataURI: 'ipfs://QmMetadataCID',         // optional — attach evidence
});

console.log('Record UID:', uid);
console.log('Transaction:', txHash);

The readings array length and readingIntervalMinutes together define the period covered. Three 60-minute readings starting at fromTimestamp cover a 3-hour window. The contract prevents submitting a period that overlaps with any existing record for the same project.

All fields:

FieldTypeRequiredDescription
projectIdnumber | bigintYesID of the project
readingsbigint[]YesWh per interval. Use 0n for zero-generation periods
readingIntervalMinutesnumberYesMinutes per reading (e.g. 15, 30, 60)
fromTimestampnumberYesUnix timestamp of the start of the first interval
methodstringYesFree-text label for the collection method
metadataURIstringNoURI pointing to supporting documentation

Submit multiple records in one transaction

Batch submissions are more gas-efficient when you have multiple periods to report:

JavaScript
const { uids, txHash } = await sdk.attestations.attestBatch([
  {
    projectId: 1,
    readings: [500_000n, 510_000n],
    readingIntervalMinutes: 60,
    fromTimestamp: 1735689600,
    method: 'smart-meter-api',
  },
  {
    projectId: 1,
    readings: [490_000n, 505_000n],
    readingIntervalMinutes: 60,
    fromTimestamp: 1735696800,
    method: 'smart-meter-api',
  },
]);

console.log('Submitted UIDs:', uids);

Correct an existing record

If a record contained an error, submit a replacement that references the original via refUID. The original is marked as replaced and excluded from totals; the new record takes effect instead:

JavaScript
const { uid } = await sdk.attestations.overwriteAttestation({
  refUID: '0xOriginalRecordUID',  // the record being corrected
  projectId: 1,
  readings: [512_000n, 508_000n, 499_000n],  // corrected values
  readingIntervalMinutes: 60,
  fromTimestamp: 1735689600,
  method: 'smart-meter-api-corrected',
  metadataURI: 'ipfs://QmCorrectionAuditReport',
});
Note

Note: Direct revocation is blocked by the contract. Corrections must always go through overwriteAttestation, which preserves a full audit trail.

Report a zero-generation period

If a site was offline or had no generation for a period, use attestZeroPeriod. It automatically picks up from the last attested timestamp:

JavaScript
const { uid } = await sdk.attestations.attestZeroPeriod({
  projectId: 1,
  interval: 60,          // Interval enum value or minutes
  method: 'manual-zero-report',
  metadataURI: 'ipfs://QmMaintenanceLog',  // optional
});

This is equivalent to calling attest with readings: [0n] starting from getProjectLastTimestamp().

Gas Estimation

Every write method has a corresponding estimate*Gas method for pre-flight checks:

JavaScript
const gas = await sdk.attestations.estimateAttestGas(params);
console.log('Estimated gas:', gas.toString());

const gas2 = await sdk.watchers.estimateCreateWatcherGas('My Org');
const gas3 = await sdk.projects.estimateCreateProjectGas(watcherId, 'Site A', EnergyType.SOLAR_PV);