Two Predge signed records, notarized on Base

Predge signs every settlement-risk record with an ed25519 key it publishes. On 13 October 2026 FairSeal notarized two of these records: it took the sha256 of each record's signed canonical string and anchored both hashes in one Merkle root on Base mainnet. This page lists both receipts and shows how to check each layer yourself: the Predge signature, the FairSeal Merkle proof and the Base transaction. Everything below uses public endpoints; no account or wallet is needed.

The two records

CS2: MW vs LDP, 2026-10-07, Games Total O/U 2.5

Polymarket market 5369848, disputed twice, resolved Under on chain.

Predge record
GET /v1/settlement-risk/5369848
FairSeal receipt
nr_2e090e4c78df92765ea0b600f298c8dc · JSON
payload_hash
d65f90881964b342c3de964b6ee3e6751417229ef4c2d96ee1cb8bf7d792320c
Snapshot time
2026-10-13T01:37:09Z (record's checked_at)

Montana Grizzlies vs Idaho Vandals, O/U 151.5

Polymarket market 1137824, college basketball, 2026-01-08.

Predge record
GET /v1/settlement-risk/1137824
FairSeal receipt
nr_6f99fa85e571dc8e766d014a9da49440 · JSON
payload_hash
11a2bf5505b6d25c3b04c8c0049ba837b6097061d19574e27e44526dd57cf5e2
Snapshot time
2026-10-13T01:37:12Z (as stated in the receipt)

One anchor for both

Base tx
0xf8ded45d2daa2e83cf490bc20d2e7ea0bbadd3887c30a3114729a9a5e3db683f
Block
52533131 · 2026-10-13T01:40:09Z
Contract
0xEE09671Bbc6B932BF1461CAff360bfDD9bEA55ca
Merkle root
a04116161641635e63504d2a892f3a0029b0cc584dd204234ffdcf5e5e5e6e9c

01Check the Predge signature

Fetch a record and check two things: sha256(canonical) equals content_hash, and the ed25519 signature over canonical is valid under the key with kid 13fa3d18a369e6c7 in the published key set. The rule that produces canonical is in the canonicalization spec.

This check runs on the response you fetch, not on the snapshot FairSeal hashed. Settlement-risk records are live: every request re-reads the chain and returns a new checked_at, so canonical and content_hash change on every call. The hash you compute today will not equal the payload_hash in the receipt. That is expected. The exact canonical bytes FairSeal hashed on 13 October are not published.

Node 18+, no packages

// Node 18+, no packages: node predge_check.mjs 5369848
import { createHash, createPublicKey, verify } from "node:crypto";
const id = process.argv[2] ?? "5369848";
const env = (await (await fetch(`https://api.predge.io/v1/settlement-risk/${id}`)).json()).attestation;
const keys = (await (await fetch("https://api.predge.io/.well-known/predge-keys.json")).json()).keys;
const hash = createHash("sha256").update(env.canonical, "utf8").digest("hex");
const pub = createPublicKey({ format: "der", type: "spki",
  key: Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(env.public_key, "hex")]) });
console.log("sha256(canonical)          ", hash);
console.log("matches content_hash       ", hash === env.content_hash);
console.log("ed25519 signature valid    ", verify(null, Buffer.from(env.canonical, "utf8"), pub, Buffer.from(env.signature, "hex")));
console.log("key listed, active, kid ok ", keys.some(k => k.active && k.kid === "13fa3d18a369e6c7" && k.public_key === env.public_key));

Python 3.8+, with cryptography

# Python 3.8+, needs `pip install cryptography`: python3 predge_check.py 5369848
import hashlib, json, sys, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

get = lambda url: json.load(urllib.request.urlopen(url, timeout=20))
mid = sys.argv[1] if len(sys.argv) > 1 else "5369848"
env = get(f"https://api.predge.io/v1/settlement-risk/{mid}")["attestation"]
keys = get("https://api.predge.io/.well-known/predge-keys.json")["keys"]
canon = env["canonical"].encode("utf-8")
digest = hashlib.sha256(canon).hexdigest()
print("sha256(canonical)          ", digest)
print("matches content_hash       ", digest == env["content_hash"])
Ed25519PublicKey.from_public_bytes(bytes.fromhex(env["public_key"])).verify(bytes.fromhex(env["signature"]), canon)
print("ed25519 signature valid     True")  # verify() raises InvalidSignature otherwise
print("key listed, active, kid ok ", any(k["active"] and k["kid"] == "13fa3d18a369e6c7" and k["public_key"] == env["public_key"] for k in keys))

Both print the hash of the response you just fetched, then true for each check (Python prints True):

sha256(canonical)           <changes on every call>
matches content_hash        true
ed25519 signature valid     true
key listed, active, kid ok  true

For a full check that also rebuilds canonical from payload and verifies envelope_signature, save a response and run verify-record.mjs or verify_record.py.

02Check the FairSeal receipt

A receipt commits to a hash in three steps, all of which you can recompute:

The script below does all three with the Python standard library and a public Base RPC. It takes the receipt and its Merkle path from the FairSeal API, but the leaf, the root and the on-chain check are computed locally, so a wrong path or hash cannot pass.

Python 3.8+, standard library only

# Python 3.8+, standard library only: python3 fairseal_check.py nr_2e090e4c78df92765ea0b600f298c8dc
import hashlib, json, sys, time, urllib.request

RPC = "https://mainnet.base.org"   # any public Base mainnet RPC works
CONTRACT = "0xee09671bbc6b932bf1461caff360bfdd9bea55ca"
EXPECTED = {  # the payload hashes listed on this page
    "nr_2e090e4c78df92765ea0b600f298c8dc": "d65f90881964b342c3de964b6ee3e6751417229ef4c2d96ee1cb8bf7d792320c",
    "nr_6f99fa85e571dc8e766d014a9da49440": "11a2bf5505b6d25c3b04c8c0049ba837b6097061d19574e27e44526dd57cf5e2",
}
sha = lambda s: hashlib.sha256(s.encode()).hexdigest()   # inputs are hex strings, hashed as text

def rpc(method, *params):
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": list(params)}).encode()
    req = urllib.request.Request(RPC, body, {"Content-Type": "application/json", "User-Agent": "verify"})
    return json.load(urllib.request.urlopen(req, timeout=20))["result"]

rid = sys.argv[1] if len(sys.argv) > 1 else "nr_2e090e4c78df92765ea0b600f298c8dc"
r = json.load(urllib.request.urlopen(f"https://api.fairseal.io/v1/notarize/{rid}", timeout=20))
p = r["proof"]
node = leaf = sha(rid + "||" + r["payload_hash"])
for sib in p["merkle_path"]:
    node = sha(min(node, sib) + max(node, sib))
tx = rpc("eth_getTransactionByHash", p["anchor_tx"])
ok = rpc("eth_getTransactionReceipt", p["anchor_tx"])["status"] == "0x1"
ts = int(rpc("eth_getBlockByNumber", tx["blockNumber"], False)["timestamp"], 16)
print("payload_hash as listed here ", r["payload_hash"] == EXPECTED.get(rid))
print("leaf recomputed             ", leaf == p["leaf_hash"])
print("root recomputed             ", node == p["merkle_root"], node[:16] + "...")
print("tx to FairSeal contract, ok ", tx["to"].lower() == CONTRACT and ok)
print("root in tx calldata         ", node in tx["input"].lower())
print("anchored in block", int(tx["blockNumber"], 16), "at", time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts)))

Output for either receipt:

payload_hash as listed here  True
leaf recomputed              True
root recomputed              True a04116161641635e...
tx to FairSeal contract, ok  True
root in tx calldata          True
anchored in block 52533131 at 2026-10-13T01:40:09Z

If your Python reports CERTIFICATE_VERIFY_FAILED (common with the python.org installer on macOS), run its Install Certificates.command or set SSL_CERT_FILE=/etc/ssl/cert.pem. FairSeal also documents a second route: call getBatchRoot(batch_id) on the contract and compare it with 0x + root.

03What each proof shows, and what it doesn't

Predge signature

  • The canonical string you fetched was signed by the key Predge publishes as active (kid 13fa3d18a369e6c7).
  • Changing any signed byte makes the check fail.
  • It does not show the facts are correct. The chain facts in a record (UMA state, dispute transactions, on-chain resolution) can be re-checked on any Polygon RPC; risk_level, recommendation and the statistics are Predge's reading and computation.
  • It does not show when the record was signed. checked_at is a time Predge states inside the signed payload, not an independent timestamp.
  • It says nothing about the 13 October snapshots: the bytes you fetch today are different.
  • Fields outside attestation.payload (for example the top-level market title) are display data and are not covered by signature.

FairSeal receipt

  • The receipt's payload_hash, bound to its receipt id, is a leaf under a Merkle root that a successful transaction sent to FairSeal's anchor contract on Base in block 52533131.
  • So that hash existed no later than 2026-10-13T01:40:09Z, and the receipt cannot be changed or backdated without breaking the proof.
  • It does not show which bytes the hash came from. Without the snapshot's canonical string, nobody can check that the hash is of a Predge-signed record or what that record said. That link rests on FairSeal's statement in the receipt.
  • It does not show the record's content was correct, or that it did not exist earlier.

Taken together. The two proofs are independent today. One shows that Predge signs these records with its published key. The other shows that two specific hashes were fixed on Base by 01:40:09 UTC on 13 October 2026. If a snapshot's canonical string is ever disclosed, anyone can close the gap: check that its sha256 equals the receipt's payload_hash and that the Predge signature over it is valid. That would show Predge signed that exact content no later than the anchor time.