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.
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:
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
biginten watts-hora: no kWh, no decimales flotantes - El periodo cubierto es
fromTimestamp + readingCount × intervalMinutes × 60segundos - 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)
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:
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
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)
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:
const { uid } = await sdk.attestations.attestZeroPeriod({
projectId: 1,
interval: Interval.Daily,
method: 'manual-zero-report',
metadataURI: 'ipfs://QmMaintenanceLog',
});Directamente con el contrato
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
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
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.