npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@proofchain/verify-core

v0.2.0

Published

Trustless browser verification for ProofChain events and Score Receipts — Merkle proofs + on-chain root checks, zero dependencies

Readme

@proofchain/verify-core

Trustless, browser-friendly verification for ProofChain events and Score Receipts. Zero runtime dependencies — recomputes Merkle proofs and checks on-chain roots using nothing but fetch and the Web Crypto API, so it can run entirely client-side without trusting the ProofChain API.

Install

npm install @proofchain/verify-core

Requires Node 18+ (or any modern browser) for global fetch and crypto.subtle.

Usage

1. Verify a single event from the proof-bundle endpoint

import { verifyEvent } from '@proofchain/verify-core';

const bundle = await fetch(
  `https://api.proofchain.co.za/verify/event/${eventId}/proof-bundle`,
).then((r) => r.json());

const result = await verifyEvent(bundle);

console.log(result.level); // 'invalid' | 'unverified' | 'merkle_valid' | 'chain_confirmed'

2. Verify a receipt fetched from /verify/receipt/{id}

import { verifyReceipt } from '@proofchain/verify-core';

const manifest = await fetch(
  `https://api.proofchain.co.za/verify/receipt/${receiptId}`,
).then((r) => r.json());

const result = await verifyReceipt(manifest);

console.log(result.level);           // overall verification level
console.log(result.partial);         // true when truncated / has unresolved events
console.log(result.reason);          // 'malformed_manifest' | 'contract_mismatch' when invalid
console.log(result.events);          // { total, merkleValid, chainConfirmed, unresolved }
console.log(result.scoreCheck);      // advisory recomputed score sum

3. Override the RPC endpoint (or skip chain checks entirely)

import { verifyReceipt } from '@proofchain/verify-core';

// Use your own Polygon RPC provider instead of the public default. The RPC
// endpoint is the trust anchor for 'chain_confirmed', so it must be https://
// (http:// is rejected except for loopback hosts or `allowInsecureHttp: true`).
const result = await verifyReceipt(manifest, {
  rpcUrl: 'https://polygon-mainnet.g.alchemy.com/v2/<key>',
  timeoutMs: 5_000,
});

// Or verify Merkle inclusion only, with no network calls at all.
const offlineResult = await verifyReceipt(manifest, { skipChainCheck: true });

4. Verification levels and reasons

| level | Meaning | |-------------------|---------| | invalid | A check failed: bad receipt hash, epoch leaf, or Merkle proof; chain.contract differs from the pinned contract (reason: 'contract_mismatch'); or the manifest is malformed (reason: 'malformed_manifest', with error). Malformed input never throws. | | unverified | Nothing failed, but no event could be checked (zero events, or all unresolved). receiptHashValid alone is an integrity check of a self-hash, not proof of authenticity. | | merkle_valid | Every checked event's proof recomputes to its claimed root. | | chain_confirmed | Additionally, every root was found in a successful attestBatch transaction to the pinned contract on the expected chain. |

partial: true (manifest truncated, or any unresolved event) caps level at merkle_valid: unchecked events are unknowns, never confirmed.

Each entry in chainChecks reports checked: false with a reason when the check could not run: unknown_chain (no pinned contract for chain_id), chain_id_mismatch (the RPC's eth_chainId differs from the manifest's), or rpc_error (network failure, non-2xx HTTP status, or JSON-RPC error).

5. Pinned contracts (KNOWN_CONTRACTS)

The package ships KNOWN_CONTRACTS, a registry of ProofChain batch-attestation contracts by chain ID. A chain may list several, because older batches were anchored by an earlier contract; currently Polygon 137 → 0x6b5a2cafef6e1b631715d664670ceb446b382d91 (original) and 0x311bdccb0ae0d85fe74f100e4a5d0e28e7788e34 (current). verifyReceipt only trusts a pinned address: the manifest's chain.contract must be in the list for its chain_id (and transactions are then checked against that one address), otherwise the receipt is invalid with reason: 'contract_mismatch'. A chain_id without a registry entry yields unknown_chain chain checks. verifyEvent (whose bundle carries no contract) accepts a transaction sent to any pinned contract for chain_id. The RPC's eth_chainId is checked once per call against the claimed chain_id, and the transaction calldata must start with the attestBatch selector (ATTEST_BATCH_SELECTOR).

Passing opts.contract replaces the registry with an address you vouch for. This weakens the guarantee — anyone can deploy a contract that accepts arbitrary roots — so only do it for chains/contracts you have verified out of band, never from a value found in the manifest itself.

Threat model

This package answers: "does this event/receipt data match what ProofChain committed on-chain?" It does not answer "is ProofChain trustworthy" or "is the API currently serving correct data" — it lets you check the API's claims independently.

Verified in v1:

  • The leaf hash is recomputed from the event's own public fields (certificate_id, document_hash, event_id) — the API cannot silently substitute a different document and still pass.
  • Merkle inclusion — the leaf really is included under the claimed root, via an independently-walked proof (OpenZeppelin sorted-pair convention).
  • On-chain confirmation — the RPC serves the manifest's chain_id, and for a given tx_hash the transaction succeeded, was sent to the pinned contract for that chain (KNOWN_CONTRACTS, not the manifest's own claim), and its calldata is an attestBatch(...) call (selector-checked) whose merkleRoot argument equals the claimed root, via a direct RPC call the verifier controls. The RPC endpoint is therefore the trust anchor: a compromised RPC can fabricate receipts, so supply a provider you trust.

NOT verified in v1 (explicitly deferred):

  • The batchId and tenantId calldata arguments are not bound via keccak256 (that needs a vendored keccak implementation or an extra dependency); the selector is checked but only the 256-bit merkleRoot equality is binding.
  • Block finality depth — a confirmed transaction could still be reorged on a very recent block; this package does not wait for or check confirmation depth.
  • Epoch Merkle inclusion — epochLeafValid recomputes and compares the epoch leaf hash, but does not walk a Merkle proof against an epoch tree root (the manifest carries only the leaf fields in v1, not an inclusion proof).

Score-recompute caveat

scoreCheck in verifyReceipt's result is advisory, not proof. It sums each event's contribution as a 4-decimal-place fixed-point BigInt (never floating point, so no drift) and compares it to totals.total_score. When the receipt is truncated, or any event is unresolved, the sum is reported as an upper bound with exact: false and an explanatory note. Even when exact is true, this is not a cryptographic guarantee: daily-cap-zeroed events are not flagged in the v1 manifest, so a capped event's true contribution to the score may differ from what's shown, and the recomputed sum can only ever be checked against the manifest's own totals — not against data the manifest doesn't carry.

API

See src/types.ts and src/receipt.ts for full type definitions, including EventProofBundle, ReceiptManifest, VerifyOptions, EventVerifyResult, and ReceiptVerifyResult.