CoresRent
Open dashboard

The ledger

The record format

The exact shape of a meter record: the EIP-712 domain and types, the rules for each field, how records chain together, and how an hour of records becomes one root on the Ledger.

This is the exact shape of a meter record: how it is encoded, what it signs, how records chain together, and how an hour of records at one site becomes a single root on Ethereum. It is the reference the verifier is built from.

Files and encoding

One record is one JSON file for one machine and one UTC hour. Files are UTF-8 with no byte order mark. Money is in USDC base units (6 decimals) written as decimal strings, so no reader ever rounds through a floating point number. Time is UTC. Energy is whole watt-hours. See Reading a record for a complete example.

The typed data

Records are signed as EIP-712 typed data with the custodian's secp256k1 key. The domain binds a signature to mainnet and to the Ledger contract, so it cannot be replayed on another chain or for another deployment.

TypeScriptEIP-712 domain and types
const domain = {
  name: "CoresRent Meter",
  version: "1",
  chainId: 1,
  verifyingContract: "0x47ac525AA85Efe47f42BB213Ca9e38B5f5C4DD83", // Ledger
} as const;

const types = {
  MeterRecord: [
    { name: "machineId", type: "string" },
    { name: "site", type: "string" },
    { name: "hourStart", type: "uint64" },
    { name: "samples", type: "uint16" },
    { name: "gpuSeconds", type: "uint32" },
    { name: "energyWh", type: "uint32" },
    { name: "rateUsdcPerGpuHour", type: "uint64" },
    { name: "grossUsdc", type: "uint64" },
    { name: "tenantRef", type: "bytes32" },
    { name: "prevHash", type: "bytes32" },
  ],
} as const;

In the JSON file, hourStart is written as an ISO 8601 string and signed as Unix seconds. version, custodian and signature sit beside the signed fields and are not part of the typed data.

Field rules

FieldTypeRule
machineIdstringPattern CR-SITE-RACK-SLOT, e.g. CR-IAD4-07-04. Must exist in the Registry.
sitestringA site code from the Registry, e.g. IAD-4.
hourStartuint64Unix seconds, a multiple of 3,600.
samplesuint160 to 60.
gpuSecondsuint320 to samples × 60 × 8. At most 28,800.
energyWhuint32Whole Wh read at the PDU outlet.
rateUsdcPerGpuHouruint64USDC base units. 0 if no lease was active.
grossUsdcuint64Exactly gpuSeconds × rate ÷ 3,600, rounded down.
tenantRefbytes32keccak256 of the lease ID and a per-lease salt. Zero if idle.
prevHashbytes32EIP-712 digest of the previous hour's record. Zero for a machine's first hour.

One record carries one rate. If a sled served two leases at different prices in the same hour, rateUsdcPerGpuHour is the GPU-second weighted average and grossUsdc is the exact sum of both leases.

The hash chain

Every record carries the digest of the same machine's record for the hour before. A machine's history is a chain that runs from its first hour in service to now. A machine that is offline still gets a record for every hour, with samples at 0, so the chain never has a gap by design. A gap, an edit or a reordering is detectable by anyone holding two neighbouring records.

The hourly root

  1. Leaves are record digests. Each leaf is the EIP-712 digest of one record: the same hash the custodian signed.
  2. Leaves are sorted. All digests for one site and one hour are sorted in ascending order.
  3. Pairs are hashed in order. Each parent is keccak256 of its two children, smaller first. An odd node at the end of a level is carried up unchanged.
  4. The root is posted. The site's custodian key calls postRoot on the Ledger within 10 minutes of the hour, with the record count and the bundle's IPFS content ID. A root cannot be replaced once posted.
SolidityLedger (excerpt)
function postRoot(bytes32 site, uint64 hourStart, bytes32 root, uint32 count, string calldata cid) external;
function rootOf(bytes32 site, uint64 hourStart)
  external view returns (bytes32 root, uint32 count, string memory cid, uint64 postedAt);

event RootPosted(bytes32 indexed site, uint64 indexed hourStart, bytes32 root, uint32 count, string cid);

The published bundle

Each site-hour is published to IPFS as one folder. The content ID is the one stored with the root, so the bundle you fetch is provably the one that was anchored.

ShellIPFS bundle layout
IAD-4/2026-09-27T14/
  manifest.json              # site, hourStart, count, root, machine IDs in leaf order
  CR-IAD4-07-03.json
  CR-IAD4-07-03.proof.json   # { "leaf": "0x…", "siblings": ["0x…", "0x…"] }
  CR-IAD4-07-04.json
  CR-IAD4-07-04.proof.json
  CR-IAD4-07-05.json
  CR-IAD4-07-05.proof.json
  CR-IAD4-07-06.json
  CR-IAD4-07-06.proof.json   # one pair per sled at the site: 4 at IAD-4

Versioning

version goes up when a field is added. The EIP-712 domain version changes only if what is signed changes. Old records stay valid forever under the version they were signed with, and the verifier knows every version.

Last updated 5 October 2026.