// Minimal ABI decoding for EAS getAttestation and the Charter schema. No dependency.
export const GET_ATTESTATION = '0xa3112a64'; // getAttestation(bytes32)
const UID_RE = /^0x[0-9a-fA-F]{64}$/;
export function encodeGetAttestation(uid) {
if (typeof uid !== 'string' || !UID_RE.test(uid)) throw new Error('the UID must be 0x and 64 hex characters');
return GET_ATTESTATION + uid.slice(2).toLowerCase();
}
function reader(hex, what) {
if (typeof hex !== 'string' || !/^0x([0-9a-fA-F]{2})*$/.test(hex)) throw new Error(`${what} is not hex data`);
const h = hex.slice(2).toLowerCase();
const size = h.length / 2;
const word = (at) => {
if (!Number.isInteger(at) || at < 0 || at + 32 > size) throw new Error(`${what} is too short`);
return h.slice(at * 2, at * 2 + 64);
};
const uint = (at) => BigInt('0x' + word(at));
const offset = (at, base = 0) => {
const v = uint(at) + BigInt(base);
if (v > BigInt(size)) throw new Error(`${what} has a bad offset`);
return Number(v);
};
const bytes = (at) => {
const len = offset(at);
if (at + 32 + len > size) throw new Error(`${what} is too short`);
return h.slice((at + 32) * 2, (at + 32 + len) * 2);
};
return { h, word, uint, offset, bytes };
}
export function hexToBytes(hex) {
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
return out;
}
const utf8 = (hex) => new TextDecoder('utf-8', { fatal: true }).decode(hexToBytes(hex));
// decodeCharterData(dataHex) -> {version, date, names, digests, cids}. Throws on data that is not the schema.
export function decodeCharterData(dataHex) {
const r = reader(dataHex, 'the attestation data');
const str = (base, at) => utf8(r.bytes(r.offset(at, base)));
const arr = (headAt) => {
const start = r.offset(headAt);
const len = Number(r.uint(start));
if (len > 64) throw new Error('the attestation data has too many entries');
return { len, base: start + 32 };
};
const strings = (headAt) => {
const a = arr(headAt);
return Array.from({ length: a.len }, (_, k) => str(a.base, a.base + k * 32));
};
const version = str(0, 0);
const date = str(0, 32);
const names = strings(64);
const d = arr(96);
const digests = Array.from({ length: d.len }, (_, k) => '0x' + r.word(d.base + k * 32));
const cids = strings(128);
if (names.length !== digests.length || names.length !== cids.length) {
throw new Error('names, digests and cids do not have the same length');
}
return { version, date, names, digests, cids };
}
// The Attestation struct returned by getAttestation, with data left as hex.
export function decodeAttestation(resultHex) {
const r = reader(resultHex, 'the RPC answer');
const t = r.offset(0);
const u64 = (at) => Number(r.uint(t + at));
return {
uid: '0x' + r.word(t),
schema: '0x' + r.word(t + 32),
time: u64(64),
expirationTime: u64(96),
revocationTime: u64(128),
refUID: '0x' + r.word(t + 160),
recipient: '0x' + r.word(t + 192).slice(24),
attester: '0x' + r.word(t + 224).slice(24),
revocable: r.uint(t + 256) !== 0n,
data: '0x' + r.bytes(r.offset(t + 288, t)),
};
}
agentgate-source: code mirror on Cloudflare Pages. All repos