// Tests named after the acceptance items. Offline: a fake rpc and a fake ipfs answer every call.
// CHARTER_LIVE=1 adds reads of real EAS attestations on Base and Base Sepolia.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { decodeCharterData, verifyCharter, sha256Hex, httpRpc, httpIpfs, NETWORKS, EAS_ADDRESS, CHARTER_SCHEMA, KOGNAI_REHEARSAL } from '../verifier.js';
const fx = JSON.parse(readFileSync(new URL('./fixtures.json', import.meta.url), 'utf8'));
const enc = (s) => new TextEncoder().encode(s);
const byCid = Object.fromEntries(fx.names.map((n, i) => [fx.cids[i], fx.texts[n]]));
function fakeRpc(result, calls = []) {
return async (method, params) => {
calls.push({ method, params });
if (result instanceof Error) throw result;
return result;
};
}
const fakeIpfs = (over = {}) => async (cid) => {
if (over[cid] instanceof Error) throw over[cid];
if (cid in over) return enc(over[cid]);
if (!(cid in byCid)) throw new Error('HTTP 404 from gateway');
return enc(byCid[cid]);
};
test('A1 decodeCharterData reads back what the schema encodes', async () => {
const d = decodeCharterData(fx.data);
assert.equal(d.version, '0.1');
assert.equal(d.date, '2026-10-21');
assert.deepEqual(d.names, ['charter', 'constitution', 'soul']);
assert.deepEqual(d.cids, fx.cids);
assert.deepEqual(d.digests, fx.digests);
for (const g of d.digests) assert.match(g, /^0x[0-9a-f]{64}$/);
for (const n of fx.names) assert.equal(d.digests[fx.names.indexOf(n)], await sha256Hex(enc(fx.texts[n])));
assert.equal(CHARTER_SCHEMA, fx.schema);
assert.throws(() => decodeCharterData('0x1234'), /too short/);
assert.throws(() => decodeCharterData('nothex'), /not hex/);
});
test('A2 a matching attestation shows a match for each text and ok true', async () => {
const calls = [];
const r = await verifyCharter({ uid: fx.uid, network: 'base', rpc: fakeRpc(fx.attestation, calls), ipfs: fakeIpfs() });
assert.equal(r.ok, true, r.reason);
assert.equal(r.reason, undefined);
assert.deepEqual(r.attestation, { uid: fx.uid, attester: fx.attester, time: 1792600000, revoked: false });
assert.equal(r.texts.length, 3);
for (const t of r.texts) {
assert.equal(t.match, true);
assert.equal(t.actual, t.expected);
}
assert.equal(calls.length, 1);
assert.equal(calls[0].method, 'eth_call');
assert.equal(calls[0].params[0].to, EAS_ADDRESS);
assert.equal(calls[0].params[0].data, fx.selector + fx.uid.slice(2));
});
test('A3 a tampered text shows a mismatch for that text only, with both digests, and ok false', async () => {
const tampered = fx.texts.constitution.replace('Article 1', 'Article 2');
const r = await verifyCharter({ uid: fx.uid, network: 'base', rpc: fakeRpc(fx.attestation), ipfs: fakeIpfs({ [fx.cids[1]]: tampered }) });
assert.equal(r.ok, false);
assert.match(r.reason, /1 of 3 texts do not match: constitution/);
const [charter, constitution, soul] = r.texts;
assert.equal(charter.match, true);
assert.equal(soul.match, true);
assert.equal(constitution.match, false);
assert.equal(constitution.expected, fx.digests[1]);
assert.equal(constitution.actual, await sha256Hex(enc(tampered)));
assert.notEqual(constitution.actual, constitution.expected);
});
test('A4 an unknown UID, a revoked attestation or an unfetchable text is reported plainly and never thrown', async () => {
const unknown = await verifyCharter({ uid: '0x' + 'ab'.repeat(32), network: 'base', rpc: fakeRpc(fx.empty), ipfs: fakeIpfs() });
assert.equal(unknown.ok, false);
assert.match(unknown.reason, /no attestation with this UID on base/);
const revoked = await verifyCharter({ uid: fx.uid, network: 'base', rpc: fakeRpc(fx.revoked), ipfs: fakeIpfs() });
assert.equal(revoked.ok, false);
assert.equal(revoked.attestation.revoked, true);
assert.match(revoked.reason, /revoked/);
const missing = await verifyCharter({ uid: fx.uid, network: 'base', rpc: fakeRpc(fx.attestation), ipfs: fakeIpfs({ [fx.cids[2]]: new Error('HTTP 504 from ipfs.io') }) });
assert.equal(missing.ok, false);
assert.match(missing.texts[2].error, /could not be fetched: HTTP 504/);
assert.equal(missing.texts[2].match, false);
assert.equal(missing.texts[0].match, true);
const cases = [
{ uid: 'nope', network: 'base', rpc: fakeRpc(fx.attestation), ipfs: fakeIpfs() },
{ uid: fx.uid, network: 'base', rpc: fakeRpc(new Error('timeout')), ipfs: fakeIpfs() },
{ uid: fx.uid, network: 'base', rpc: fakeRpc('0x'), ipfs: fakeIpfs() },
{ uid: fx.uid, network: 'base', rpc: fakeRpc(fx.wrongSchemaData), ipfs: fakeIpfs() },
{ uid: fx.uid, network: 'mainnet', rpc: fakeRpc(fx.attestation), ipfs: fakeIpfs() },
{},
undefined,
];
for (const c of cases) {
const r = await verifyCharter(c);
assert.equal(r.ok, false);
assert.equal(typeof r.reason, 'string');
assert.ok(r.reason.length > 0);
}
});
test('A5 works on Base and Base Sepolia, the network choosing the chain and the RPC', async () => {
assert.deepEqual(Object.keys(NETWORKS).sort(), ['base', 'base-sepolia']);
assert.equal(NETWORKS.base.chainId, 8453);
assert.equal(NETWORKS['base-sepolia'].chainId, 84532);
assert.equal(NETWORKS.base.rpcUrl, 'https://mainnet.base.org');
assert.equal(NETWORKS['base-sepolia'].rpcUrl, 'https://sepolia.base.org');
for (const network of ['base', 'base-sepolia']) {
const r = await verifyCharter({ uid: fx.uid, network, rpc: fakeRpc(fx.attestation), ipfs: fakeIpfs() });
assert.equal(r.ok, true);
}
// The page builds the rpc from the network: check the fetch based helper posts to the given URL.
const seen = [];
const rpc = httpRpc(NETWORKS['base-sepolia'].rpcUrl, async (url, init) => {
seen.push({ url, body: JSON.parse(init.body) });
return new Response(JSON.stringify({ jsonrpc: '2.0', id: 1, result: fx.attestation }));
});
const ipfs = httpIpfs('https://gw.example/ipfs', async (url) => new Response(byCid[url.split('/ipfs/')[1]] ?? '', { status: url.split('/ipfs/')[1] in byCid ? 200 : 404 }));
const r = await verifyCharter({ uid: fx.uid, network: 'base-sepolia', rpc, ipfs });
assert.equal(r.ok, true, r.reason);
assert.equal(seen[0].url, 'https://sepolia.base.org');
assert.equal(seen[0].body.method, 'eth_call');
});
test('A5 live: real EAS reads on Base and Base Sepolia', { skip: !process.env.CHARTER_LIVE }, async () => {
const ipfs = httpIpfs();
// Revoked attestations of other schemas, found on easscan on 21 October 2026.
const cases = [
['base', '0x000002e32c856c44f918bbe3275f3afaffa352bee8a210858781758983fc60b8'],
['base-sepolia', '0x0009b420b6ad5101662f64ccc4a5a90dbaf7bbd9bd635c20aae11843d88e9f3c'],
];
for (const [network, uid] of cases) {
const r = await verifyCharter({ uid, network, rpc: httpRpc(NETWORKS[network].rpcUrl), ipfs });
assert.equal(r.ok, false);
assert.equal(r.attestation.revoked, true, `${network}: ${r.reason}`);
assert.match(r.attestation.attester, /^0x[0-9a-f]{40}$/);
}
const none = await verifyCharter({ uid: '0x' + '12'.repeat(32), network: 'base', rpc: httpRpc(NETWORKS.base.rpcUrl), ipfs });
assert.match(none.reason, /no attestation/);
});
test('A6 the page is mobile friendly', () => {
const html = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
assert.match(html, /<meta name="viewport" content="width=device-width, initial-scale=1">/);
assert.match(html, /<script type="module" src="app.js"><\/script>/);
const css = readFileSync(new URL('../style.css', import.meta.url), 'utf8');
assert.match(css, /max-width/);
assert.doesNotMatch(css, /(?<![-\w])(min-)?width:\s*([4-9][0-9]{2}|[0-9]{4,})px/); // no fixed width over 360 px
});
test('A6 the Sepolia rehearsal UID Kognai publishes verifies', { todo: KOGNAI_REHEARSAL.uid ? false : 'Kognai has not published the rehearsal UID yet' }, async () => {
const r = await verifyCharter({ uid: KOGNAI_REHEARSAL.uid, network: 'base-sepolia', rpc: httpRpc(NETWORKS['base-sepolia'].rpcUrl), ipfs: httpIpfs() });
assert.equal(r.ok, true, r.reason);
});
agentgate-source: code mirror on Cloudflare Pages. All repos