charter-attestation-verifier / tree / abi.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// 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