UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

115 lines (102 loc) 6.1 kB
// mint → sim → proof, over the programmatic client (kestrel.markets/client). // // The SDK face of the four faces (ADR-0004): the exact same canonical contract the // CLI's `--api` path and the raw HTTP face speak. This walks the day-one flow: // // 1. mint POST /capabilities/trial → an anonymous trial capability // 2. validate POST /validate → the plan parses (pure, no Operation) // 3. sim POST /sim + SSE → an Operation → a certified Blotter (the receipt) // 4. grade POST /grade + SSE → the judged verdict over that Blotter // 5. the paid boundary → a 402/Offer surfaced as DATA, carrying the // free `proof` already earned under the trial // // RUNNABLE WITH NO SECRETS. By default it runs against `contractMockFetch` — an // in-process implementation of the contract shipped from the client — so the whole // flow executes offline in CI. Set KESTREL_API=<base-url> to point it at a live API // instead (still no secret needed to mint a trial capability). // // Build first (emits dist/, which `kestrel.markets/client` resolves to): // bun run build // node examples/client-mint-sim-proof.mjs import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { KestrelClient, contractMockFetch } from "kestrel.markets/client"; const here = dirname(fileURLToPath(import.meta.url)); // A real, canonical plan (generic tickers). The server parses `source`; the mock // accepts any source and returns canned artifacts. const source = readFileSync(join(here, "momentum-breakout.kestrel"), "utf8"); // Transport: a live API if KESTREL_API is set, else the in-process contract mock. const live = process.env.KESTREL_API; const client = live ? new KestrelClient({ baseUrl: live }) : new KestrelClient({ fetch: contractMockFetch }); const mode = live ? `live @ ${live}` : "in-process contract mock"; /** Tiny assert: loud, nonzero-exit failure — never a silent pass. */ let failures = 0; const check = (label, ok) => { console.log(` ${ok ? "ok " : "FAIL"} ${label}`); if (!ok) failures++; }; console.log(`kestrel.markets/client — mint → sim → proof (${mode})\n`); // ── 1. mint a trial capability ────────────────────────────────────────────── const cap = await client.mint(); console.log(`1. minted trial capability kind=${cap.kind} scopes=[${cap.scopes.join(", ")}]`); // ── 2. validate the plan (pure; no Operation) ─────────────────────────────── const validation = await client.validate(source); console.log(`2. validate ok=${validation.ok} diagnostics=${validation.diagnostics.length}`); // ── 3. sim over a free-catalog dataset → an Operation, streamed to a Blotter ── const seen = []; const sim = await client.sim({ source, dataset: "catalog:spy-sample-2024-05-07", // a free-catalog artifact id (no 402) params: { fillModel: "maker-fair-v1", rUsd: 1 }, onEvent: (e) => seen.push(e.type), // observe the operation's SSE stream }); if (sim.gated) throw new Error("expected a free sim, got a 402/Offer"); console.log( `3. sim operation=${sim.value.operation.operationId} state=${sim.value.operation.state}\n` + ` events=[${seen.join(" → ")}]\n` + ` blotter (receipt) sessionId=${sim.value.blotter?.sessionId}`, ); // ── 4. grade the Blotter → the judged verdict ─────────────────────────────── const blotterId = sim.value.blotter?.sessionId; if (!blotterId) throw new Error("sim returned no Blotter to grade"); const graded = await client.grade({ blotters: [blotterId] }); if (graded.gated) throw new Error("expected a grade result, got a 402/Offer"); // the verdict is the portable GradeResult, or a signed CertifiedGrade that nests one const verdict = "result" in graded.value ? graded.value.result : graded.value; const metrics = Object.entries(verdict.metrics) .map(([k, v]) => `${k}=${v}`) .join(" "); console.log(`4. grade subject=${verdict.subjectSessionId} metrics: ${metrics}`); // ── 5. the paid boundary → a 402/Offer as DATA, carrying the earned proof ──── const paid = await client.sim({ source, dataset: "premium:spx-2024-05-07" }); if (!paid.gated) { console.log("5. paid boundary (this API served the premium dataset for free — no Offer)"); } else { const { offer, proof } = paid.payment; console.log( `5. paid boundary 402/Offer (surfaced as data, not a redirect)\n` + ` offer=${offer.offer_id} scope=[${offer.scope.join(", ")}] ` + `amount=${offer.amount.value} ${offer.amount.asset}\n` + ` proof (free result earned under the trial): ${proof?.url ?? "(none)"}`, ); } // ── self-verification (only in mock mode, where the canned values are known) ── if (!live) { console.log("\nverifying against the in-process contract:"); check("trial cap grants sim + grade scopes", cap.scopes.includes("sim") && cap.scopes.includes("grade")); check("validate ok", validation.ok === true); check("sim streamed started → artifact → completed", seen.join(",") === "operation.started,operation.artifact,operation.completed"); check("sim returned the certified Blotter", sim.value.blotter?.sessionId === "bl_mock_1"); check("grade produced a metric", typeof verdict.metrics.bankable_ev === "number"); check("paid dataset gated with a 402/Offer", paid.gated === true); check("the 402 carried an Offer amount", paid.gated && paid.payment.offer.amount.value === "25.00"); check("the 402 carried the earned proof", paid.gated && typeof paid.payment.proof?.url === "string"); } if (failures > 0) { console.error(`\n${failures} check(s) FAILED`); process.exit(1); } console.log(`\ndone — mint → sim → grade → proof over kestrel.markets/client${live ? "" : " (verified)"}.`);