x402-perpcheck / tree / test/mock.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Offline mock of the public Hyperliquid info API (primary api.hyperliquid.xyz and fallback api-ui.hyperliquid.xyz).
// fail: set of 'primary' | 'fallback' (host down, HTTP 503), '<type>' (that info type fails on every host, e.g. 'l2Book'),
// '<type>:timeout' (that type never answers), 'garbage' (metaAndAssetCtxs returns a wrong shape).
export const NOW = Date.UTC(2026, 9, 23, 19, 0, 0);
export const META = { universe: [
  { name: 'BTC', szDecimals: 5, maxLeverage: 40, marginTableId: 56 },
  { name: 'ETH', szDecimals: 4, maxLeverage: 25, marginTableId: 55 },
  { name: 'FTM', szDecimals: 0, maxLeverage: 3, isDelisted: true, marginTableId: 3 },
  { name: 'ISO', szDecimals: 1, maxLeverage: 10, onlyIsolated: true, marginTableId: 10 },
  { name: 'CAPD', szDecimals: 0, maxLeverage: 5, marginTableId: 5 },
], marginTables: [[56, { description: 'tiered 40x', marginTiers: [{ lowerBound: '0.0', maxLeverage: 40 }, { lowerBound: '150000000.0', maxLeverage: 20 }] }]] };
export const CTX = [
  { funding: '0.0000125', openInterest: '40000', markPx: '80000', oraclePx: '80010', premium: '-0.0001', dayNtlVlm: '2000000000' },
  { funding: '0.00002', openInterest: '500000', markPx: '3000', oraclePx: '2999', premium: '0.0002', dayNtlVlm: '1000000000' },
  { funding: '0', openInterest: '1', markPx: '0.5', oraclePx: '0.5', premium: '0', dayNtlVlm: '0' },
  { funding: '0.0001', openInterest: '1000', markPx: '2', oraclePx: '2', premium: '0', dayNtlVlm: '100000' },
  { funding: '0.0001', openInterest: '1000', markPx: '1', oraclePx: '1', premium: '0', dayNtlVlm: '100000' },
];
export const OI_CAP = ['FTM', 'CAPD'];
const book = (coin) => {
  const i = META.universe.findIndex((u) => u.name === coin); const m = Number(CTX[i]?.markPx || 1);
  return { coin, time: NOW, levels: [[{ px: String(m - m * 1e-5), sz: '2', n: 3 }, { px: String(m - m * 1e-4), sz: '10', n: 5 }], [{ px: String(m + m * 1e-5), sz: '2', n: 3 }, { px: String(m + m * 1e-4), sz: '10', n: 5 }]] };
};

export function mockVenues({ fail = new Set(), calls = [] } = {}) {
  const fetchImpl = async (url, init = {}) => {
    const u = new URL(String(url)); const b = JSON.parse(init.body || '{}'); calls.push(`${u.host} ${b.type}`);
    const host = u.host === 'api.hyperliquid.xyz' ? 'primary' : u.host === 'api-ui.hyperliquid.xyz' ? 'fallback' : 'other';
    if (host === 'other') throw new TypeError(`no network in tests: ${u.href}`);
    if (fail.has(`${b.type}:timeout`) || fail.has(`${host}:timeout`)) return new Promise((_, rej) => init.signal?.addEventListener('abort', () => rej(new DOMException('aborted', 'AbortError'))));
    if (fail.has(host) || fail.has(b.type)) return new Response('busy', { status: 503 });
    const j = (o) => Response.json(o);
    if (b.type === 'metaAndAssetCtxs') return fail.has('garbage') ? j({ oops: true }) : j([META, CTX]);
    if (b.type === 'perpsAtOpenInterestCap') return j(OI_CAP);
    if (b.type === 'l2Book') return j(fail.has('stale_book') ? { ...book(b.coin), time: NOW - 10 * 60_000 } : book(b.coin));
    return new Response('unknown type', { status: 422 });
  };
  return { calls, fetchImpl };
}

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