// UnyKorn chain registry API (registry.unykorn.org). Off-chain registry: stores HMAC-signed // envelopes from btc402 registry.py in KV. Not an on-chain write. const enc = new TextEncoder(); const canon = (v) => Array.isArray(v) ? "[" + v.map(canon).join(",") + "]" : v && typeof v === "object" ? "{" + Object.keys(v).sort().map(k => JSON.stringify(k) + ":" + canon(v[k])).join(",") + "}" : JSON.stringify(v); const hex = (b) => [...new Uint8Array(b)].map(x => x.toString(16).padStart(2, "0")).join(""); const json = (o, s = 200) => new Response(JSON.stringify(o, null, 2), { status: s, headers: { "content-type": "application/json", "access-control-allow-origin": "*" } }); const NOTE = "Registry API for the UnyKorn chain. Records are stored off-chain in this registry; on-chain anchoring is not live yet."; export default { async fetch(req, env) { const url = new URL(req.url); const p = url.pathname.replace(/^\/api\/registry/, "") || "/"; if (req.method === "GET" && p === "/") return json({ service: "UnyKorn chain registry API", note: NOTE, endpoints: ["POST /records", "GET /records/:id", "GET /offers"] }); if (req.method === "POST" && p === "/records") { const raw = await req.text(); if (raw.length > 20000) return json({ error: "too large" }, 413); let rec; try { rec = JSON.parse(raw); } catch { return json({ error: "bad json" }, 400); } const { digest, hmac, ...body } = rec; if (!digest || !hmac || !body.kind) return json({ error: "missing digest/hmac/kind" }, 400); const c = canon(body); if (hex(await crypto.subtle.digest("SHA-256", enc.encode(c))) !== digest) return json({ error: "digest mismatch" }, 400); const key = await crypto.subtle.importKey("raw", enc.encode(env.REGISTRY_HMAC_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); if (hex(await crypto.subtle.sign("HMAC", key, enc.encode(c))) !== hmac) return json({ error: "bad signature" }, 401); const id = (body.kind === "offer" ? "offer:" : "rec:") + digest; if (await env.REG.get(id)) return json({ id: digest, status: "exists" }, 200); const stored = { ...body, digest, received: Date.now() }; await env.REG.put(id, JSON.stringify(stored)); return json({ id: digest, status: "recorded", note: NOTE }, 201); } let m; if (req.method === "GET" && (m = p.match(/^\/records\/([0-9a-f]{64})$/))) { const v = (await env.REG.get("rec:" + m[1])) || (await env.REG.get("offer:" + m[1])); return v ? json({ ...JSON.parse(v), note: NOTE }) : json({ error: "not found" }, 404); } if (req.method === "GET" && p === "/offers") { const l = await env.REG.list({ prefix: "offer:" }); const offers = await Promise.all(l.keys.map(async k => JSON.parse(await env.REG.get(k.name)))); return json({ offers, note: NOTE }); } return json({ error: "not found" }, 404); }, };