The complete public API surface of @chainblocks/core. Every exported symbol is documented here with its signature, behavior, parameters, return value, errors it can throw, and a runnable example.
For the bigger picture, see:
STORY.md— why the library existsMODEL.md— the portable data-model spec (Block format, canonicalization, verification algorithm)REQUIREMENTS.md— REQ-CB-* the API satisfiesdocs/FEATURES.md— FT-CB-* features mapped to REQs
The API is stable across patch and minor versions; breaking changes require a major version bump (per REQ-CB-NF-033).
- Opening and closing a ledger
- The
Ledgerclass - Standalone verification
- Stores
- Hashing primitives
- Block helpers
- Types
- Errors
- Constants
Open an existing ledger or create a new one. Returns a Ledger instance ready for append, read, verify, etc.
function openLedger(options: OpenLedgerOptions): Promise<Ledger>Parameters
| Field | Type | Description |
|---|---|---|
store |
Store |
The storage backend. Use FileStore for production, MemoryStore for tests. |
name |
string |
Ledger name. Bound into the genesis block and into every block's hash input. |
writer |
string? |
Optional identity of the writing entity (e.g. "app", "system", an agent id). Bound into the genesis block. |
Returns — Promise<Ledger>
If the store is empty, a genesis block is created and persisted. If the store already contains a genesis block, its name and writer are validated against the supplied options.
Throws
LockHeldError— another writer is already holding the lockfile (REQ-CB-005)LedgerMismatchError— the on-disk genesis doesn't match{name, writer}(REQ-CB-002)MalformedBlockError— the genesis block is structurally invalid
Example
import { openLedger, FileStore } from '@chainblocks/core';
const ledger = await openLedger({
store: new FileStore({ path: './audit.ledger' }),
name: 'app-audit',
writer: 'app',
});
console.log('Opened at head:', (await ledger.head()).seq);
await ledger.close();Requirements: REQ-CB-001, REQ-CB-002, REQ-CB-005 · Feature: FT-CB-01
The runtime handle for an open ledger. Obtained from openLedger. Methods are listed below.
Atomically append a new block to the end of the chain.
append(input: BlockInput): Promise<Block>Parameters
| Field | Type | Description |
|---|---|---|
input.kind |
string |
Caller-defined event type (e.g. "app.event.recorded"). chainblocks does not interpret this. |
input.payload |
unknown |
JSON-serializable payload. Stored as supplied. |
Returns — Promise<Block> — the sealed block, with computed seq, ts, prev, and hash.
Behavior
seqis monotonically incremented (REQ-CB-011)tsis generated server-side at append time in ISO 8601 UTC ms (REQ-CB-012); callers cannot supply itprevis set to the previous block'shash(or"GENESIS"for the first block after genesis)hashis computed viacanonicalHashover the canonical JSON of all other fields- The operation is atomic: either the block is durable after return, or an error is thrown and the chain is unchanged (REQ-CB-019)
- Concurrent
appendcalls on the same ledger are serialized internally (REQ-CB-020) - An
'append'event is emitted after the block is persisted (REQ-CB-070)
Throws
StoreClosedError— the ledger has been closedTornWriteError,MalformedBlockError, or other I/O-related errors propagated from the underlying store
Example
const block = await ledger.append({
kind: 'app.event.recorded',
payload: { eventId: 'e-a8c4', actor: 'alice', action: 'login' },
});
console.log(`seq=${block.seq} ts=${block.ts} hash=${block.hash}`);Requirements: REQ-CB-010 through REQ-CB-020, REQ-CB-070 · Feature: FT-CB-02
Iterate over blocks in seq order, optionally filtered.
read(options?: ReadOptions): AsyncIterable<Block>Parameters
| Field | Type | Description |
|---|---|---|
options.fromSeq |
number? |
Start at this seq (inclusive). Clamped to [0, head.seq]. |
options.toSeq |
number? |
End at this seq (inclusive). Clamped to [0, head.seq]. |
options.kind |
string? |
Filter by kind. Supports exact match ("app.event.recorded") or trailing wildcard ("app.event.*"). |
Returns — AsyncIterable<Block> — blocks in seq order from genesis to head (or within the requested range).
Behavior
- Snapshot-consistent: every yielded block has a valid hash chain back to genesis as of the moment of yield (REQ-CB-034)
- Safe to call concurrently with
append - Filters compose:
{fromSeq, toSeq, kind}applies all three
Example
for await (const block of ledger.read({ kind: 'app.event.*', fromSeq: 100 })) {
console.log(block.seq, block.kind, block.payload);
}Requirements: REQ-CB-030 through REQ-CB-034 · Features: FT-CB-03, FT-CB-04
Return a snapshot of the current head of the chain.
head(): Promise<LedgerHead>Returns — Promise<LedgerHead> — { seq, hash, ts } of the highest-seq block.
For a freshly initialized ledger (genesis only), returns the genesis block's head.
Example
const head = await ledger.head();
console.log(`Chain has ${head.seq + 1} blocks; tip hash = ${head.hash}`);Requirements: REQ-CB-035 · Feature: FT-CB-05
Fetch the block at a specific sequence number.
getBySeq(seq: number): Promise<Block | null>Parameters
| Field | Type | Description |
|---|---|---|
seq |
number |
The sequence number to look up. |
Returns — Promise<Block | null> — the block, or null if seq is out of range.
Note: performance characteristics depend on the store. FileStore is O(N); a SQLite-backed store would be O(log N).
Example
const block = await ledger.getBySeq(1342);
if (block) {
console.log('Found:', block.kind, block.payload);
}Requirements: REQ-CB-036 · Feature: FT-CB-06
Walk the chain and verify every block's hash. The integrity guarantee at the heart of chainblocks.
verify(options?: VerifyOptions): Promise<VerifyResult>Parameters
| Field | Type | Description |
|---|---|---|
options.fromSeq |
number? |
Start verifying from this seq forward, using the block at fromSeq - 1 as the trust anchor. Useful for incremental verification of large ledgers. |
Returns — Promise<VerifyResult>
type VerifyResult =
| { ok: true; head: VerifiedHead }
| { ok: false; tamperedAt: number; reason: VerifyFailureReason }VerifyFailureReason is one of: "hash_mismatch", "prev_mismatch", "missing_block", "out_of_order", "torn_write", "malformed_block".
Behavior
- O(N) time in the number of blocks (REQ-CB-043)
- Stops at the first inconsistency and reports its exact seq and reason (REQ-CB-041)
- Works on read-only handles (REQ-CB-044)
Example
const result = await ledger.verify();
if (result.ok) {
console.log(`Valid: ${result.head.seq + 1} blocks, head ${result.head.hash}`);
} else {
console.error(`Tampered at seq=${result.tamperedAt}: ${result.reason}`);
}Requirements: REQ-CB-040 through REQ-CB-044 · Features: FT-CB-07, FT-CB-08
Release resources (file handles, lockfile, in-memory caches).
close(): Promise<void>Idempotent. Subsequent calls on the same Ledger instance throw StoreClosedError.
Example
await ledger.close();Requirements: REQ-CB-003 · Feature: FT-CB-01
Subscribe to ledger events. Currently exposes one event: 'append', emitted after every successful append.
on<E extends keyof LedgerEventMap>(
event: E,
listener: (payload: LedgerEventMap[E]) => void,
): thisBehavior
- Synchronous emission after the block is persisted (REQ-CB-070)
- A listener that throws does NOT corrupt the chain state, but its error is propagated to the
appendcaller wrapped inAppendListenerError(REQ-CB-071)
Example
ledger.on('append', (block) => {
console.log(`appended seq=${block.seq} kind=${block.kind}`);
});Requirements: REQ-CB-070, REQ-CB-071 · Feature: FT-CB-13
Convenience helper that opens a store, verifies, and closes — all in one call. Used by the chainblocks-verify CLI.
function verifyLedger(options: VerifyLedgerOptions): Promise<VerifyResult>Parameters
| Field | Type | Description |
|---|---|---|
store |
Store |
The store to verify. |
name |
string |
Expected ledger name (validated against the genesis block). |
writer |
string? |
Expected writer (validated against the genesis block, if supplied). |
Returns — Promise<VerifyResult> — same shape as Ledger.verify.
Example
import { verifyLedger, FileStore } from '@chainblocks/core';
const result = await verifyLedger({
store: new FileStore({ path: './audit.ledger', readonly: true }),
name: 'app-audit',
});
console.log(result.ok ? 'valid' : `tampered at ${result.tamperedAt}`);Requirements: REQ-CB-044, REQ-CB-NF-002 · Feature: FT-CB-07
Lower-level: verify a sequence of blocks given a Store and ledger metadata. Used by Ledger.verify internally and exposed for advanced consumers who want to drive verification themselves.
function verifyChain(
store: Store,
meta: LedgerMeta,
options?: VerifyOptions,
): Promise<VerifyResult>Parameters
| Field | Type | Description |
|---|---|---|
store |
Store |
The store to read from. |
meta |
LedgerMeta |
{ name, writer } — must match the genesis block. |
options.fromSeq |
number? |
Incremental-verification anchor. |
Returns — Promise<VerifyResult>.
Example
import { verifyChain, FileStore } from '@chainblocks/core';
const store = new FileStore({ path: './audit.ledger', readonly: true });
await store.init();
const result = await verifyChain(store, { name: 'app-audit', writer: 'app' });
await store.close();Requirements: REQ-CB-040, REQ-CB-NF-002 · Feature: FT-CB-07
The pluggable storage contract. Any object implementing this interface can be a chainblocks backend.
interface Store {
init(genesisIfMissing: Block): Promise<{ head: Block }>;
append(block: Block): Promise<void>;
readAll(): AsyncIterable<Block>;
readFromSeq(seq: number): AsyncIterable<Block>;
getHead(): Promise<Block | null>;
close(): Promise<void>;
}Implementations MUST satisfy:
appendis atomic: success ⇒ durable; failure ⇒ unchanged (REQ-CB-051)readAllandreadFromSeqyield blocks in seq order (REQ-CB-052)getHeadreturns the highest-seq block, ornullif empty (REQ-CB-053)closeis idempotent
Requirements: REQ-CB-050 through REQ-CB-053 · Feature: FT-CB-09
In-memory store backed by a Map<number, Block>. Lost on close.
class MemoryStore implements Store {
constructor()
}Intended for tests, ephemeral ledgers, and as a reference implementation of the Store contract.
Example
import { openLedger, MemoryStore } from '@chainblocks/core';
const ledger = await openLedger({
store: new MemoryStore(),
name: 'ephemeral-test',
});Requirements: REQ-CB-054 · Feature: FT-CB-11
JSONL file with O_EXCL lockfile. The production default.
class FileStore implements Store {
constructor(options: FileStoreOptions)
}Parameters
| Field | Type | Description |
|---|---|---|
options.path |
string |
Absolute or relative path to the .ledger file. The lockfile is placed at <path>.lock. |
options.readonly |
boolean? |
Open read-only (no lockfile acquired). Used by verifier flows. |
Behavior
- Each block is one JSONL line, written in canonical JSON form, followed by
\n(REQ-CB-055) fsyncis called after every append, before return (REQ-CB-057)- The lockfile contains
{pid, createdAt}and is acquired viaO_EXCL; concurrent opens fail withLockHeldError(REQ-CB-056) - Stale locks (PID no longer alive AND lockfile older than the configurable threshold, default 60s) are reclaimed automatically (REQ-CB-NF-012)
Example
import { openLedger, FileStore } from '@chainblocks/core';
const ledger = await openLedger({
store: new FileStore({ path: '/var/audit/service.ledger' }),
name: 'service-audit',
writer: 'service',
});Requirements: REQ-CB-054 through REQ-CB-057, REQ-CB-NF-012 · Feature: FT-CB-10
These are exposed so cross-language verifiers can match exactly. Most consumers do not need to call them directly.
Compute a "sha256:" + hex(SHA256(...)) over a canonical-JSON serialization of the input.
function canonicalHash(value: unknown): stringUses RFC 8785 JSON Canonicalization Scheme (JCS) for the serialization step.
Example
import { canonicalHash } from '@chainblocks/core';
const h = canonicalHash({ seq: 0, kind: 'ledger.genesis', payload: {} });
console.log(h); // "sha256:abc..."Requirements: REQ-CB-016, REQ-CB-NF-020 · Feature: FT-CB-15
Serialize a value to its RFC 8785 canonical JSON string.
function canonicalJson(value: unknown): stringExample
import { canonicalJson } from '@chainblocks/core';
console.log(canonicalJson({ b: 1, a: 2 })); // '{"a":2,"b":1}'Requirements: REQ-CB-016 · Feature: FT-CB-15
These are exposed for consumers writing custom Store implementations or migration tooling. Normal consumers never call them; Ledger.append handles all sealing internally.
Compute the hash for a non-genesis block.
function sealBlock(
input: BlockInput,
prev: PrevContext,
meta: LedgerMeta,
): BlockCompute the hash for the genesis block.
function sealGenesisBlock(
payload: GenesisPayload,
meta: LedgerMeta,
ts: string,
): BlockRecompute the hash of an existing block. Used by verifyChain to detect tampering.
function recomputeHash(block: Block, meta: LedgerMeta): stringRequirements: REQ-CB-016, REQ-CB-017 · Feature: FT-CB-02
The following types are exported for use in consumer code:
| Type | Description |
|---|---|
Block |
A sealed block: {seq, ts, prev, kind, payload, hash} |
BlockInput |
The caller-supplied portion: {kind, payload} |
LedgerMeta |
{name, writer?} — the ledger-level metadata bound into every hash |
LedgerHead |
{seq, hash, ts} — a head snapshot |
OpenLedgerOptions |
{store, name, writer?} — arguments to openLedger |
ReadOptions |
{fromSeq?, toSeq?, kind?} — filters for Ledger.read |
VerifyOptions |
{fromSeq?} — options for verify |
VerifyResult |
The discriminated {ok: true, ...} | {ok: false, ...} verify result |
VerifyFailureReason |
"hash_mismatch" | "prev_mismatch" | "missing_block" | "out_of_order" | "torn_write" | "malformed_block" |
VerifiedHead |
{seq, hash} — the verified-head shape inside VerifyResult.ok = true |
LedgerEventMap |
{ 'append': Block } — typed event-listener map |
GenesisPayload |
{name, writer: string | null, createdAt} — the genesis-block payload |
PrevContext |
{seq, hash} — the predecessor info sealBlock needs |
Store |
The pluggable storage interface |
FileStoreOptions |
{path, readonly?} — constructor args for FileStore |
All chainblocks errors extend a common base class with a stable name, structured context, and a cause chain (when applicable).
| Class | Thrown when | Recoverable? |
|---|---|---|
ChainblocksError |
Base class for all chainblocks errors | — |
LockHeldError |
Another writer holds the lockfile (REQ-CB-005) | Yes — wait or close the other writer |
LedgerMismatchError |
Genesis block on disk does not match the supplied {name, writer} |
No — wrong ledger path or wrong opening args |
MalformedBlockError |
A block read from the store fails structural validation | No — the ledger is corrupted |
TornWriteError |
The last line of the JSONL file is incomplete (REQ-CB-NF-011) | Yes — manual repair via a future repair command |
ChainTamperedError |
A verify call escalated a tampering finding via throw rather than return |
Investigation required |
AppendListenerError |
An 'append' event listener threw (REQ-CB-071) |
Yes — fix the listener |
StoreClosedError |
A method was called on a closed ledger | Yes — reopen |
Every error carries a .context object with relevant fields (block, seq, expected, actual, path, etc.). The exact shape per class is documented in the source.
Requirements: REQ-CB-005, REQ-CB-071, REQ-CB-NF-011 · Feature: FT-CB-14
| Constant | Value | Description |
|---|---|---|
GENESIS_PREV |
"GENESIS" |
The literal prev value of the genesis block |
GENESIS_KIND |
"ledger.genesis" |
The kind of the genesis block |
HASH_PREFIX |
"sha256:" |
The prefix on every hash value (REQ-CB-016) |
Requirements: REQ-CB-016, REQ-CB-017 · Feature: FT-CB-02
chainblocks-verifyCLI — standalone verification from the shellSTORY.mdfor the doctrine that shapes this APIMODEL.mdfor the portable spec (so cross-language verifiers can match byte-for-byte)docs/USE-CASES.mdfor the nine scenarios that drove the designSECURITY.mdfor the threat model
🖇️