engineering//3 min read
Software for carbon projects: digital MRV, registries and double counting
Every carbon credit rests on measurement, reporting and verification. How to build the data side: monitoring pipelines, evidence that survives an audit, and a ledger that makes double issuance and double retirement impossible.

In short
- Measurement, reporting and verification (MRV) is a data pipeline; digital MRV makes it continuous and auditable.
- Keep raw evidence immutable and every calculation reproducible from it.
- A registry is a ledger of serialised units; issuance, transfer and retirement must be atomic and checked against each other.
- Double counting is prevented by unique serial numbers, single ownership, and integration with the registries where credits really live.
A carbon credit is only as trustworthy as the data behind it. Before a registry issues credits, a project must measure what happened (trees grown, fuel saved, methane captured), report it under an approved methodology, and have it verified by an independent body. That cycle is MRV, and much of it is still done with spreadsheets and site visits. Digital MRV (dMRV) moves it into software: continuous data, automated calculations and evidence a verifier can inspect remotely.
The pipeline
- Collect: sensors and meters, field surveys, satellite and drone imagery, invoices and logs.
- Validate: range checks, gap detection, cross-checks between sources.
- Calculate: apply the methodology to get reductions or removals for a monitoring period.
- Report: generate the monitoring report with every figure traceable to its inputs.
- Verify: give the verifier read access to evidence, calculations and change history.
Evidence must not change
Store raw data as it arrived, append-only, with a content hash. Calculations read from it and write their results separately, with the methodology version and parameters used. Re-running a calculation on the same evidence must give the same number.
import { createHash } from "node:crypto";
/** Store raw evidence once, addressed by its own hash, so it cannot change silently. */
export async function storeEvidence(file: Buffer, meta: { source: string; capturedAt: string }) {
const sha256 = createHash("sha256").update(file).digest("hex");
await db.insert(evidence).values({ sha256, ...meta, size: file.byteLength })
.onConflictDoNothing(); // the same file uploaded twice is the same evidence
await objectStore.put(`evidence/${sha256}`, file);
return sha256;
}Remote sensing for nature projects
For forestry and land projects, satellite data estimates canopy cover, biomass change and disturbance across whole project areas. It complements, but does not replace, field plots: models need ground truth, and results carry uncertainty that the methodology must account for. Record which imagery, model and version produced each estimate.
The registry as a ledger
A registry tracks units, each with a unique serial number, through a simple lifecycle. Model it as a ledger where every state change is a transaction, and where the database enforces the rules rather than trusting the application.
State change | Rule |
|---|---|
Issuance | Only for a verified monitoring period, never more than the verified quantity |
Transfer | Only by the current holder, only for units in the active state |
Retirement | Permanent; records the beneficiary and the purpose |
Cancellation | For corrections or buffer use, with a recorded reason |
create table credit_blocks (
id bigserial primary key,
project_id bigint not null references projects(id),
vintage int not null,
serial_start bigint not null,
serial_end bigint not null,
holder_id bigint not null references accounts(id),
status text not null check (status in ('active', 'retired', 'cancelled')),
retired_for text,
check (serial_end >= serial_start),
-- No two blocks may cover the same serial numbers.
exclude using gist (int8range(serial_start, serial_end, '[]') with &&)
);The exclusion constraint (it needs the btree_gist extension) makes overlapping serial ranges impossible, which rules out double issuance at the database level. Splitting a block for a partial transfer happens inside a single transaction that closes the old block and opens the new ones.
Double counting in practice
- Double issuance: prevented by serial uniqueness and by linking issuance to verified quantities.
- Double use: prevented by single ownership and permanent retirement.
- Double claiming: a buyer and a host country both counting the same tonne; handled through Article 6 authorisations and corresponding adjustments, which the system must record.
Integrate, do not duplicate
Most credits live in established registries run by standards bodies or governments. A company platform should treat those as the source of truth: mirror holdings through their APIs or exports, reconcile regularly, and never issue its own units for tonnes already issued elsewhere. Be cautious with tokenising credits on a public blockchain: unless the original registry records the move, you have created a second claim on the same tonne.
Integrity in carbon markets is mostly a data problem: unique units, immutable evidence, and one place where each tonne is counted.
Build checklist
- To do: Raw evidence stored append-only, addressed by hash
- To do: Calculations reproducible, with methodology versions recorded
- To do: Validation and gap detection on every data source
- To do: Verifier access to evidence, calculations and history
- To do: Serial ranges enforced unique by the database
- To do: Issuance, transfer and retirement as atomic transactions
- To do: Regular reconciliation with the external registries

