erc8004-identity-viewer / tree / abi.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Minimal ABI encoding and decoding for the few ERC-8004 calls the viewer makes. No dependency.

export const SELECTORS = {
  ownerOf: '0x6352211e', // ownerOf(uint256)
  tokenURI: '0xc87b56dd', // tokenURI(uint256)
  readAllFeedback: '0xd9d84224', // readAllFeedback(uint256,address[],string,string,bool)
};

const ZERO_WORD = '0'.repeat(64);

export function toUint256(value) {
  let n;
  try {
    n = BigInt(value);
  } catch {
    throw new Error('agentId must be a whole number');
  }
  if (n < 0n || n >= 2n ** 256n) throw new Error('agentId is out of range');
  return n.toString(16).padStart(64, '0');
}

export function encodeOwnerOf(agentId) {
  return SELECTORS.ownerOf + toUint256(agentId);
}

export function encodeTokenURI(agentId) {
  return SELECTORS.tokenURI + toUint256(agentId);
}

// readAllFeedback(agentId, [], '', '', false): every client, no tag filter, revoked left out.
export function encodeReadAllFeedback(agentId) {
  const head = [toUint256(agentId), word(0xa0), word(0xc0), word(0xe0), ZERO_WORD];
  const tail = [ZERO_WORD, ZERO_WORD, ZERO_WORD];
  return SELECTORS.readAllFeedback + head.join('') + tail.join('');
}

function word(n) {
  return BigInt(n).toString(16).padStart(64, '0');
}

function strip(hex) {
  if (typeof hex !== 'string' || !/^0x[0-9a-fA-F]*$/.test(hex)) throw new Error('the RPC answer is not hex data');
  return hex.slice(2);
}

function reader(hex) {
  const h = strip(hex);
  const wordAt = (byteOffset) => {
    const s = h.slice(byteOffset * 2, byteOffset * 2 + 64);
    if (s.length !== 64) throw new Error('the RPC answer is too short');
    return s;
  };
  const uint = (byteOffset) => BigInt('0x' + wordAt(byteOffset));
  const num = (byteOffset) => {
    const v = uint(byteOffset);
    if (v > BigInt(h.length / 2)) throw new Error('the RPC answer has a bad offset');
    return Number(v);
  };
  const bytesAt = (byteOffset) => {
    const len = num(byteOffset);
    const s = h.slice((byteOffset + 32) * 2, (byteOffset + 32 + len) * 2);
    if (s.length !== len * 2) throw new Error('the RPC answer is too short');
    return s;
  };
  return { h, wordAt, uint, num, bytesAt };
}

function hexToUtf8(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
  return new TextDecoder().decode(bytes);
}

function wordToAddress(w) {
  return '0x' + w.slice(24);
}

function wordToInt(w, bits) {
  let v = BigInt('0x' + w);
  if (v >= 2n ** 255n) v -= 2n ** 256n; // two's complement over 256 bits
  const limit = 2n ** BigInt(bits - 1);
  if (v >= limit || v < -limit) throw new Error('the RPC answer has an out of range number');
  return v;
}

export function decodeAddress(hex) {
  const r = reader(hex);
  return wordToAddress(r.wordAt(0)).toLowerCase();
}

export function decodeString(hex) {
  const r = reader(hex);
  return hexToUtf8(r.bytesAt(r.num(0)));
}

// The seven arrays of readAllFeedback, zipped into entries.
export function decodeFeedback(hex) {
  const r = reader(hex);
  const arr = (i) => {
    const off = r.num(i * 32);
    const len = r.num(off);
    return { off, len, base: off + 32 };
  };
  const words = (i, map) => {
    const a = arr(i);
    return Array.from({ length: a.len }, (_, k) => map(r.wordAt(a.base + k * 32)));
  };
  const strings = (i) => {
    const a = arr(i);
    return Array.from({ length: a.len }, (_, k) => hexToUtf8(r.bytesAt(a.base + r.num(a.base + k * 32))));
  };
  const clients = words(0, (w) => wordToAddress(w).toLowerCase());
  const indexes = words(1, (w) => Number(BigInt('0x' + w)));
  const values = words(2, (w) => wordToInt(w, 128));
  const decimals = words(3, (w) => Number(BigInt('0x' + w)));
  const tag1s = strings(4);
  const tag2s = strings(5);
  const revoked = words(6, (w) => BigInt('0x' + w) !== 0n);
  const n = clients.length;
  for (const a of [indexes, values, decimals, tag1s, tag2s, revoked]) {
    if (a.length !== n) throw new Error('the feedback arrays do not line up');
  }
  return clients.map((client, k) => ({
    client,
    feedbackIndex: indexes[k],
    value: values[k],
    valueDecimals: decimals[k],
    tag1: tag1s[k],
    tag2: tag2s[k],
    revoked: revoked[k],
  }));
}

agentgate-source: code mirror on Cloudflare Pages. All repos