Public Verifier

Every event in the PAX operational ledger is hash-chained, batched into a Merkle tree, and anchored to external tamper-evident storage. This page exposes the verifier surface so any auditor, investor, or third-party developer can independently reconstruct the chain and confirm inclusion for any fill — without contacting PAX support and without trusting the PAX API.

Live status

Refreshes on page load from GET /v1/audit/status. If you see counts, the chain is running.

Hash-chained events
loading…
Merkle batches
loading…
Anchor lag (seconds)
time since last R2 publish
Publisher mode
R2 = Cloudflare tamper-evident bucket

The chain model

Three layers of tamper evidence, each independently verifiable by third parties:

Layer 1 — Hash-chained event log (row-level) event_log { seq — monotonically increasing sequence number event_type — canonical event name (e.g. fast_round.commit, order.filled) payload — the full event body (JSON) prev_event_hash — SHA-256 of the previous event (chains rows together) event_hash — SHA-256 of this event's canonical payload + prev_event_hash } Tamper-detection: modifying any past row breaks event_hash for that row AND prev_event_hash for every subsequent row. Layer 2 — Merkle-batched roots (batch-level, ~60s cadence) event_batches { batch_num — monotonically increasing batch number first_seq — inclusive lower bound last_seq — inclusive upper bound event_count — number of leaves merkle_root — OpenZeppelin sorted-pair SHA-256 over the batch's event_hash values prev_batch_hash — SHA-256 of the previous batch's merkle_root + metadata } Algorithm: identical to Solidity MerkleProof.verify — any proof returned by /v1/audit/proof/{seq} can be verified on-chain by a smart contract without modification. Layer 3 — External anchor (Cloudflare R2) Every batch is published to Cloudflare R2 as a signed JSON payload: Key: pax-audit/batches/{batch_num}.json Sig: HMAC-SHA256 with rotated key Body: { batch_num, merkle_root, prev_batch_hash, first_seq, last_seq, event_count, created_at, pax_version } Once anchored, tampering with the operational database would require also re-signing the R2 payload with the correct key AND corrupting Cloudflare's object versioning — a much higher bar than a simple DB rewrite.

Verifier endpoints (no auth required)

All six endpoints are public, read-only, and rate-limited per source IP. Response envelope is the canonical {ok, data, meta} shape.

EndpointPurpose
GET /v1/audit/statusLive counts + anchor lag + publisher mode.
GET /v1/audit/batches/latestMost recent Merkle batch metadata.
GET /v1/audit/batches/{num}Inspect any historical batch by batch_num.
GET /v1/audit/events/{seq}Inspect a single hash-chained event (full payload + event_hash + prev_event_hash).
GET /v1/audit/proof/{seq}Merkle inclusion proof: sibling hashes proving the event is in its batch's merkle_root.
GET /v1/audit/anchor/{num}Resolve batch number to external R2 anchor URL (signed payload).

Interactive verify demo

Enter an event seq below. The page will fetch the event + its Merkle inclusion proof + the batch's merkle_root, then verify the proof entirely in your browser (WebCrypto SHA-256, OpenZeppelin sorted-pair). The result is computed client-side — no need to trust the PAX response.

Try the latest seq shown in Live status above.

Reproduce it in your own code

The verify algorithm is 5 lines. Everything below runs locally against the public endpoints — you never need to trust PAX to compute the answer.

TypeScript / Node

// npm i @predictasiax/api
import { PaxClient, AuditResource } from '@predictasiax/api';

const pax = new PaxClient({ apiKey: 'anonymous' });   // audit endpoints are no-auth

const seq = 110841;
const proof = await pax.audit.getProof(seq);

// Node crypto (sync)
const ok = AuditResource.verifyProof(
  proof.event_hash,
  proof.batch.merkle_root,
  proof.merkle_proof,
);

// Browser / edge (WebCrypto async)
const okAsync = await AuditResource.verifyProofAsync(
  proof.event_hash,
  proof.batch.merkle_root,
  proof.merkle_proof,
);

console.log('Verified locally:', ok);   // true

Python

# pip install pax-api
from pax_api import PaxClient

pax = PaxClient(api_key="anonymous")   # audit endpoints are no-auth

seq = 110841
proof = pax.audit_proof(seq)["data"]

ok = PaxClient.verify_merkle_proof(
    leaf=proof["event_hash"],
    root=proof["batch"]["merkle_root"],
    proof=proof["merkle_proof"],
)
print("Verified locally:", ok)   # True

Bare curl + shasum (no SDK)

# 1. Fetch the event + its Merkle proof
curl -s https://api.predictasiax.com/v1/audit/proof/110841 | jq

# 2. Verify in shell (OpenZeppelin sorted-pair sha256)
verify() {
  local h=$1 root=$2 shift 2
  local proof=("$@")
  for s in "${proof[@]}"; do
    if [[ "$h" < "$s" ]]; then pair="${h}${s}"; else pair="${s}${h}"; fi
    h=$(printf '%s' "$pair" | shasum -a 256 | cut -c1-64)
  done
  [[ "$h" == "$root" ]] && echo OK || echo MISMATCH
}

Solidity (on-chain)

// The Merkle algorithm is bit-identical to OpenZeppelin's MerkleProof —
// the proofs returned by /v1/audit/proof/{seq} work on-chain without change.
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

function verifyPaxEvent(
  bytes32 leaf,       // event_hash from /v1/audit/proof/{seq}
  bytes32 root,       // merkle_root from same response
  bytes32[] calldata proof   // merkle_proof array
) public pure returns (bool) {
  return MerkleProof.verify(proof, root, leaf);
}

What this proves for you

Reporting a discrepancy. If your independent verification produces a mismatch, please open a ticket at Request access with the event seq, the computed hash, and the expected hash. Every legitimate discrepancy report is investigated within 24h.