Documentación

Enviar registros de energía

Los registros de energía (llamados atestaciones en el protocolo) son la unidad de datos central de EnergyAS: lecturas permanentes y verificables que cualquiera puede auditar.

Nota

Para un ejemplo completo y funcional que incluye los pasos de configuración, consulta el tutorial de Integración de extremo a extremo.

Prepara tus datos

Cada registro contiene lecturas por intervalo en watts-hora (Wh). Por ejemplo, 24 lecturas horarias de una granja solar:

JavaScript
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);

Reglas clave:

  • Las lecturas son valores bigint en watts-hora: no kWh, no decimales flotantes
  • El periodo cubierto es fromTimestamp + readingCount × intervalMinutes × 60 segundos
  • No se puede enviar dos veces el mismo periodo para el mismo proyecto
  • Cada envío debe comenzar exactamente donde terminó el anterior (sin huecos)

Envía un solo registro

Con el SDK (recomendado)

JavaScript
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);

Directamente con el contrato

El SDK se encarga internamente de la codificación ABI. Si no usas el SDK, codifica los datos manualmente:

JavaScript
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();

Envíos por lotes

Envía varios registros en una sola transacción para ahorrar gas; es útil cuando te pones al día con varios periodos.

Con el SDK

JavaScript
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);

Directamente con el contrato (EAS multiAttest)

JavaScript
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 }]);

Periodos sin generación

Si un sitio no generó energía durante un periodo (mantenimiento planificado, falla, etc.), igualmente debes enviar un registro para mantener intacta la cadena de atestaciones. Los envíos futuros se bloquearán si hay un hueco.

Con el SDK (lo más fácil)

attestZeroPeriod retoma automáticamente desde el último timestamp atestado del proyecto:

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

Directamente con el contrato

JavaScript
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 } });

Correcciones

Para corregir un registro existente, envía un reemplazo que haga referencia al original por su UID. El contrato valida que el reemplazo cubra el mismo proyecto y el mismo periodo, y luego actualiza los totales acumulados según la diferencia. Las revocaciones directas están bloqueadas: las correcciones siempre deben pasar por este mecanismo para preservar la pista de auditoría.

Con el SDK

JavaScript
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',
});

Directamente con el contrato

JavaScript
const tx = await eas.attest({
  schema: schemaUID,
  data: {
    recipient: ZeroAddress,
    expirationTime: 0n,
    revocable: true,
    refUID: originalAttestationUID,  // non-zero = replacement
    data: correctedData,
    value: 0n,
  }
});

Tras la corrección, el registro original se marca como replaced: true en el subgraph y se excluye de los totales. El reemplazo apunta al original mediante replaces, y el original apunta al reemplazo mediante replacedBy, formando una cadena de correcciones completa.