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)
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)
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
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
await sdk.watchers.transferWatcherOwnership(watcherId, '0xNewOwnerAddress');Projects
Register a project
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:
| Enum | Value | Description |
|---|---|---|
EnergyType.CONSUMER | 0 | Consuming site (data center, EV fleet, etc.) |
EnergyType.SOLAR_PV | 1 | Solar photovoltaic |
EnergyType.WIND_ONSHORE | 2 | Onshore wind |
EnergyType.WIND_OFFSHORE | 3 | Offshore wind |
EnergyType.HYDRO | 4 | Hydroelectric |
EnergyType.BIOMASS | 5 | Biomass |
EnergyType.GEOTHERMAL | 6 | Geothermal |
EnergyType.OCEAN_TIDAL | 7 | Ocean / tidal |
EnergyType.NUCLEAR | 8 | Nuclear |
EnergyType.NATURAL_GAS | 9 | Natural gas |
EnergyType.COAL | 10 | Coal |
EnergyType.OIL | 11 | Oil |
EnergyType.STORAGE_DISCHARGE | 12 | Battery storage discharge |
EnergyType.HYDROGEN_FUEL_CELL | 13 | Hydrogen fuel cell |
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
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:
await sdk.projects.deregisterProject(projectId);Transfer a project to another organization
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
await sdk.attesters.addAttester(projectId, '0xDeviceWalletAddress');Authorize multiple submitters at once
await sdk.attesters.addAttesters(projectId, [
'0xDevice1Address',
'0xDevice2Address',
]);Authorize a submitter across all projects in an organization
Useful for backup operators or manual auditors:
await sdk.attesters.addWatcherAttester(watcherId, '0xOperatorAddress');Remove authorization
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
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:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | number | bigint | Yes | ID of the project |
readings | bigint[] | Yes | Wh per interval. Use 0n for zero-generation periods |
readingIntervalMinutes | number | Yes | Minutes per reading (e.g. 15, 30, 60) |
fromTimestamp | number | Yes | Unix timestamp of the start of the first interval |
method | string | Yes | Free-text label for the collection method |
metadataURI | string | No | URI pointing to supporting documentation |
Submit multiple records in one transaction
Batch submissions are more gas-efficient when you have multiple periods to report:
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:
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: 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:
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:
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);