import { verifyCharter, httpRpc, httpIpfs, NETWORKS, CHARTER_SCHEMA } from './verifier.js';
const $ = (id) => document.getElementById(id);
const out = $('result');
$('schema').textContent = CHARTER_SCHEMA;
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
function render(r, network, label) {
const when = r.attestation.time ? new Date(r.attestation.time * 1000).toISOString().replace('.000Z', ' UTC').replace('T', ' ') : 'unknown';
const link = label ? '' : ` <a href="${NETWORKS[network].explorer}${esc(r.attestation.uid)}" target="_blank" rel="noopener">open on easscan</a>`;
const rows = r.texts.map((t) => `<tr>
<td>${esc(t.name)}<br><code>${esc(t.cid)}</code></td>
<td class="${t.match ? 'match' : 'mismatch'}">${t.match ? 'match' : t.error ? 'not fetched' : 'mismatch'}</td>
<td><code>expected ${esc(t.expected)}</code><br><code>actual ${esc(t.actual ?? 'none')}</code>${t.error ? `<br>${esc(t.error)}` : ''}</td>
</tr>`).join('');
out.innerHTML = `<div class="card">
<p class="status ${r.ok ? 'ok' : 'error'}">${r.ok ? 'Verified: every text matches the attestation.' : `Not verified: ${esc(r.reason)}.`}</p>
${label ? `<p class="muted">${esc(label)}</p>` : ''}
<dl>
<dt>UID</dt><dd>${esc(r.attestation.uid)}${link}</dd>
<dt>Network</dt><dd>${esc(network)}</dd>
<dt>Attester</dt><dd>${esc(r.attestation.attester ?? 'unknown')}</dd>
<dt>Time</dt><dd>${esc(when)}</dd>
<dt>Revoked</dt><dd>${r.attestation.revoked ? 'yes' : 'no'}</dd>
${r.charter ? `<dt>Version</dt><dd>${esc(r.charter.version)} (${esc(r.charter.date)})</dd>` : ''}
</dl>
${rows ? `<table><thead><tr><th>Text</th><th>Result</th><th>Digests</th></tr></thead><tbody>${rows}</tbody></table>` : ''}
</div>`;
}
async function run(uid, network) {
out.innerHTML = '<p class="muted">Checking...</p>';
const rpcUrl = $('rpcUrl').value.trim() || NETWORKS[network].rpcUrl;
const gateway = $('gateway').value.trim() || undefined;
const r = await verifyCharter({ uid, network, rpc: httpRpc(rpcUrl), ipfs: httpIpfs(gateway) });
render(r, network);
const u = new URL(location.href);
u.searchParams.set('network', network);
u.searchParams.set('uid', uid);
history.replaceState(null, '', u);
}
async function demo(tampered) {
const fx = await (await fetch('test/fixtures.json')).json();
const byCid = Object.fromEntries(fx.names.map((n, i) => [fx.cids[i], fx.texts[n]]));
if (tampered) byCid[fx.cids[1]] = fx.texts.constitution.replace('Article 1', 'Article 2');
const r = await verifyCharter({
uid: fx.uid,
network: 'base-sepolia',
rpc: async () => fx.attestation,
ipfs: async (cid) => new TextEncoder().encode(byCid[cid]),
});
render(r, 'base-sepolia', tampered
? 'Demo with fixture data: the constitution text was changed by one character, so only its digest differs.'
: 'Demo with fixture data: a sample attestation and its three sample texts, no network.');
}
$('lookup').addEventListener('submit', (e) => {
e.preventDefault();
run($('uid').value.trim(), $('network').value);
});
$('demo').addEventListener('click', () => demo(false));
$('demoTampered').addEventListener('click', () => demo(true));
const q = new URLSearchParams(location.search);
if (q.get('network') in NETWORKS) $('network').value = q.get('network');
if (q.get('uid')) {
$('uid').value = q.get('uid');
run(q.get('uid'), $('network').value);
} else if (q.get('demo')) {
demo(q.get('demo') === 'tampered');
}
agentgate-source: code mirror on Cloudflare Pages. All repos