@skeinlabs/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +101 -2
- package/dist/index.d.ts +376 -0
- package/dist/index.js +293 -0
- package/dist/index.js.map +1 -0
- package/dist/telegraph.d.ts +174 -0
- package/dist/telegraph.js +358 -0
- package/dist/telegraph.js.map +1 -0
- package/package.json +71 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/sse.ts
|
|
2
|
+
async function* parseSse(body, signal) {
|
|
3
|
+
const reader = body.getReader();
|
|
4
|
+
const decoder = new TextDecoder();
|
|
5
|
+
let buf = "";
|
|
6
|
+
let event = "message";
|
|
7
|
+
let data = [];
|
|
8
|
+
let id;
|
|
9
|
+
const onAbort = () => void reader.cancel().catch(() => {
|
|
10
|
+
});
|
|
11
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
12
|
+
try {
|
|
13
|
+
for (; ; ) {
|
|
14
|
+
const { value, done } = await reader.read();
|
|
15
|
+
if (done) break;
|
|
16
|
+
buf += decoder.decode(value, { stream: true });
|
|
17
|
+
let nl;
|
|
18
|
+
while ((nl = buf.search(/\r\n|\r(?!$)|\n/)) >= 0) {
|
|
19
|
+
const line = buf.slice(0, nl);
|
|
20
|
+
buf = buf.slice(nl + (buf.startsWith("\r\n", nl) ? 2 : 1));
|
|
21
|
+
if (line === "") {
|
|
22
|
+
if (data.length) yield { event, data: data.join("\n"), ...id !== void 0 ? { id } : {} };
|
|
23
|
+
event = "message";
|
|
24
|
+
data = [];
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (line.startsWith(":")) continue;
|
|
28
|
+
const colon = line.indexOf(":");
|
|
29
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
30
|
+
const value2 = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
31
|
+
if (field === "event") event = value2;
|
|
32
|
+
else if (field === "data") data.push(value2);
|
|
33
|
+
else if (field === "id") id = value2;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
signal?.removeEventListener("abort", onAbort);
|
|
38
|
+
await reader.cancel().catch(() => {
|
|
39
|
+
});
|
|
40
|
+
reader.releaseLock();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/index.ts
|
|
45
|
+
var SKEIN_INTENT = "FRAUD_RISK_TOKEN_LISTING";
|
|
46
|
+
var CHAIN_NAMES = { 1: "ethereum", 56: "bsc", 8453: "base", 42161: "arbitrum" };
|
|
47
|
+
var SkeinError = class extends Error {
|
|
48
|
+
constructor(code, message, status = 0, details, payment) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.status = status;
|
|
52
|
+
this.details = details;
|
|
53
|
+
this.payment = payment;
|
|
54
|
+
this.name = "SkeinError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var SpendCapError = class extends SkeinError {
|
|
58
|
+
constructor(priceUsdc, capUsdc) {
|
|
59
|
+
super("SPEND_CAP", `quoted ${priceUsdc} USDC is above the cap of ${capUsdc} USDC; nothing was paid`);
|
|
60
|
+
this.priceUsdc = priceUsdc;
|
|
61
|
+
this.capUsdc = capUsdc;
|
|
62
|
+
this.name = "SpendCapError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var FINAL = /* @__PURE__ */ new Set(["UPSTREAM_UNAVAILABLE", "DAILY_CAP_REACHED", "MARGIN_GUARD", "SELF_ROUTING", "BUDGET_TOO_LOW"]);
|
|
66
|
+
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
67
|
+
const t = setTimeout(resolve, ms);
|
|
68
|
+
signal?.addEventListener("abort", () => (clearTimeout(t), reject(signal.reason)), { once: true });
|
|
69
|
+
});
|
|
70
|
+
var Skein = class {
|
|
71
|
+
constructor(opts) {
|
|
72
|
+
this.opts = opts;
|
|
73
|
+
this.base = opts.apiUrl.replace(/\/$/, "").replace(/\/v1$/, "");
|
|
74
|
+
this.fetch = opts.fetch ?? ((...a) => globalThis.fetch(...a));
|
|
75
|
+
this.retries = opts.retries ?? 2;
|
|
76
|
+
this.timeoutMs = opts.timeoutMs ?? 12e4;
|
|
77
|
+
this.maxRetryWaitMs = opts.maxRetryWaitMs ?? 1e4;
|
|
78
|
+
this.requestTimeoutMs = opts.requestTimeoutMs ?? 15e3;
|
|
79
|
+
}
|
|
80
|
+
base;
|
|
81
|
+
fetch;
|
|
82
|
+
retries;
|
|
83
|
+
timeoutMs;
|
|
84
|
+
maxRetryWaitMs;
|
|
85
|
+
requestTimeoutMs;
|
|
86
|
+
/** One HTTP request with its own deadline, still cancellable by the caller's signal. */
|
|
87
|
+
async request(url, init) {
|
|
88
|
+
const ctl = new AbortController();
|
|
89
|
+
const timer = setTimeout(() => ctl.abort(new DOMException("request timed out", "TimeoutError")), this.requestTimeoutMs);
|
|
90
|
+
const onAbort = () => ctl.abort(init.signal?.reason);
|
|
91
|
+
init.signal?.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
try {
|
|
93
|
+
const res = await this.fetch(url, { ...init, signal: ctl.signal });
|
|
94
|
+
const json = await res.json().catch(() => null);
|
|
95
|
+
return { res, json };
|
|
96
|
+
} finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
init.signal?.removeEventListener("abort", onAbort);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A raw call with typed errors and retries. POSTs are retried only with an
|
|
103
|
+
* idempotency key. Retries sleep at most maxRetryWaitMs in total: a
|
|
104
|
+
* Retry-After past that (a daily cap until midnight) throws at once.
|
|
105
|
+
*/
|
|
106
|
+
async call(path, init = {}, retryable = (init.method ?? "GET") === "GET") {
|
|
107
|
+
let waited = 0;
|
|
108
|
+
for (let attempt = 0; ; attempt++) {
|
|
109
|
+
let res;
|
|
110
|
+
let json;
|
|
111
|
+
try {
|
|
112
|
+
({ res, json } = await this.request(`${this.base}${path}`, init));
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (init.signal?.aborted) throw e;
|
|
115
|
+
const backoff = 500 * 2 ** attempt;
|
|
116
|
+
if (retryable && attempt < this.retries && waited + backoff <= this.maxRetryWaitMs) {
|
|
117
|
+
waited += backoff;
|
|
118
|
+
await sleep(backoff, init.signal ?? void 0);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const timedOut = e.name === "TimeoutError";
|
|
122
|
+
throw new SkeinError(timedOut ? "TIMEOUT" : "NETWORK", timedOut ? `no answer from Skein in ${this.requestTimeoutMs} ms` : `could not reach Skein: ${e.message}`);
|
|
123
|
+
}
|
|
124
|
+
if (res.ok) return { status: res.status, body: json };
|
|
125
|
+
const err = json?.error ?? { code: `HTTP_${res.status}`, message: res.statusText || `HTTP ${res.status}` };
|
|
126
|
+
const after = Number(res.headers.get("retry-after"));
|
|
127
|
+
const wait = Number.isFinite(after) && after > 0 ? after * 1e3 : 500 * 2 ** attempt;
|
|
128
|
+
const again = retryable && attempt < this.retries && (res.status === 429 || res.status >= 500 && !FINAL.has(err.code)) && waited + wait <= this.maxRetryWaitMs;
|
|
129
|
+
if (!again) throw new SkeinError(err.code, err.message, res.status, err.details);
|
|
130
|
+
waited += wait;
|
|
131
|
+
await sleep(wait, init.signal ?? void 0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** What a verdict would buy and cost, without buying. Free. */
|
|
135
|
+
async quote(req) {
|
|
136
|
+
const q = new URLSearchParams({ chain: String(req.subject.chainId), token: req.subject.token });
|
|
137
|
+
if (req.policy) q.set("policy", req.policy);
|
|
138
|
+
return (await this.call(`/v1/quote?${q}`)).body;
|
|
139
|
+
}
|
|
140
|
+
/** A stored verdict, or { status: "running" } while it is being made. */
|
|
141
|
+
async get(id) {
|
|
142
|
+
return (await this.call(`/v1/verdicts/${encodeURIComponent(id)}`)).body;
|
|
143
|
+
}
|
|
144
|
+
/** The bill of materials: every paid input and its receipt. */
|
|
145
|
+
async bom(id) {
|
|
146
|
+
return (await this.call(`/v1/verdicts/${encodeURIComponent(id)}/bom`)).body;
|
|
147
|
+
}
|
|
148
|
+
/** Recompute a verdict from its raw paid answers. */
|
|
149
|
+
async verify(id) {
|
|
150
|
+
return (await this.call("/v1/verify", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id }) }, true)).body;
|
|
151
|
+
}
|
|
152
|
+
/** The live events of a verdict, replayed from the start; ends after done, refused or error. */
|
|
153
|
+
async *stream(id, opts = {}) {
|
|
154
|
+
const res = await this.fetch(`${this.base}/v1/verdicts/${encodeURIComponent(id)}/events`, {
|
|
155
|
+
headers: { accept: "text/event-stream" },
|
|
156
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok || !res.body) throw new SkeinError(`HTTP_${res.status}`, `event stream failed: HTTP ${res.status}`, res.status);
|
|
159
|
+
for await (const raw of parseSse(res.body, opts.signal)) {
|
|
160
|
+
let data;
|
|
161
|
+
try {
|
|
162
|
+
data = JSON.parse(raw.data);
|
|
163
|
+
} catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const e = { type: raw.event, data };
|
|
167
|
+
yield e;
|
|
168
|
+
if (e.type === "done" || e.type === "refused" || e.type === "error") return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Buys one verdict and returns it once it is sealed. With maxUsdcPerCall
|
|
173
|
+
* set, the price is quoted first and the call refused before any payment.
|
|
174
|
+
*/
|
|
175
|
+
async verdict(req) {
|
|
176
|
+
const payer = this.opts.payer;
|
|
177
|
+
if (!payer) throw new SkeinError("NO_PAYER", "verdict() needs a payer: { kind: 'apiKey', key } or { kind: 'telegraph', client }");
|
|
178
|
+
if (this.opts.maxUsdcPerCall !== void 0) {
|
|
179
|
+
const q = req.quote ?? await this.quote({ subject: req.subject, ...req.policy ? { policy: req.policy } : {} });
|
|
180
|
+
if (!q.ok && q.refusal) throw new SkeinError(q.refusal.code, q.refusal.detail, q.refusal.code === "BUDGET_TOO_LOW" ? 422 : 503, q.refusal);
|
|
181
|
+
if (q.priceUsdc > this.opts.maxUsdcPerCall) throw new SpendCapError(q.priceUsdc, this.opts.maxUsdcPerCall);
|
|
182
|
+
}
|
|
183
|
+
return payer.kind === "apiKey" ? this.viaApi(req) : this.viaTelegraph(payer, req);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Starts a verdict on the partner-key rail and returns at once: the id while
|
|
187
|
+
* it runs, or the verdict itself when it is cached or replayed. Pair with
|
|
188
|
+
* wait(id). Spend-cap checks are the caller's (verdict() does them).
|
|
189
|
+
*/
|
|
190
|
+
async submit(req) {
|
|
191
|
+
const payer = this.opts.payer;
|
|
192
|
+
if (payer?.kind !== "apiKey") throw new SkeinError("NO_PAYER", "submit() needs an apiKey payer");
|
|
193
|
+
const { status, body } = await this.call(
|
|
194
|
+
"/v1/verdicts",
|
|
195
|
+
{
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${payer.key}`, "idempotency-key": req.idempotencyKey ?? crypto.randomUUID() },
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
subject: req.subject,
|
|
200
|
+
...req.policy ? { policy: req.policy } : {},
|
|
201
|
+
...req.context ? { context: req.context } : {},
|
|
202
|
+
...this.opts.maxUsdcPerCall !== void 0 ? { budgetUsdcMax: this.opts.maxUsdcPerCall } : {},
|
|
203
|
+
mode: "async"
|
|
204
|
+
}),
|
|
205
|
+
...req.signal ? { signal: req.signal } : {}
|
|
206
|
+
},
|
|
207
|
+
true
|
|
208
|
+
);
|
|
209
|
+
return status === 202 ? body : { ...body, payment: { rail: "apiKey" } };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Waits for a submitted verdict: follows its event stream (falling back to
|
|
213
|
+
* polling if the stream drops) and throws a SkeinError if it is refused.
|
|
214
|
+
*/
|
|
215
|
+
async wait(id, opts = {}) {
|
|
216
|
+
const deadline = Date.now() + (opts.timeoutMs ?? this.timeoutMs);
|
|
217
|
+
const timeout = AbortSignal.timeout(Math.max(1, deadline - Date.now()));
|
|
218
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
|
|
219
|
+
try {
|
|
220
|
+
for await (const e of this.stream(id, { signal })) {
|
|
221
|
+
opts.onEvent?.(e);
|
|
222
|
+
if (e.type === "refused") throw new SkeinError(e.data.code, e.data.detail, e.data.code === "BUDGET_TOO_LOW" ? 422 : 503, e.data);
|
|
223
|
+
if (e.type === "error") throw new SkeinError("VERDICT_FAILED", e.data.message, 503);
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
if (e instanceof SkeinError && !e.code.startsWith("HTTP_")) throw e;
|
|
227
|
+
if (opts.signal?.aborted) throw e;
|
|
228
|
+
}
|
|
229
|
+
for (; ; ) {
|
|
230
|
+
const v = await this.get(id);
|
|
231
|
+
if (!("status" in v && v.status === "running")) return { ...v, payment: { rail: "apiKey" } };
|
|
232
|
+
if (Date.now() >= deadline) throw new SkeinError("TIMEOUT", `verdict ${id} is still running; call wait(id) again`, 504, { id });
|
|
233
|
+
await sleep(Math.min(1e3, Math.max(0, deadline - Date.now())), opts.signal);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async viaApi(req) {
|
|
237
|
+
const started = await this.submit(req);
|
|
238
|
+
if (!("status" in started && started.status === "running")) return started;
|
|
239
|
+
return this.wait(started.id, { ...req.onEvent ? { onEvent: req.onEvent } : {}, ...req.signal ? { signal: req.signal } : {} });
|
|
240
|
+
}
|
|
241
|
+
async viaTelegraph(payer, req) {
|
|
242
|
+
const params = {
|
|
243
|
+
token: req.subject.token,
|
|
244
|
+
chain: CHAIN_NAMES[req.subject.chainId] ?? String(req.subject.chainId),
|
|
245
|
+
subject: req.subject,
|
|
246
|
+
...req.policy ? { policy: req.policy } : {},
|
|
247
|
+
...req.context ? { context: req.context } : {}
|
|
248
|
+
};
|
|
249
|
+
const out = await payer.client.ask({ intent: payer.intent ?? SKEIN_INTENT, params }, { ...payer.minerId ? { minerId: payer.minerId } : {}, timeoutMs: this.timeoutMs });
|
|
250
|
+
if (!out.ok) {
|
|
251
|
+
const paidUusdc = out.paidUusdc ?? 0;
|
|
252
|
+
throw new SkeinError(
|
|
253
|
+
`TELEGRAPH_${out.kind.toUpperCase()}`,
|
|
254
|
+
out.error,
|
|
255
|
+
out.httpStatus ?? 0,
|
|
256
|
+
void 0,
|
|
257
|
+
paidUusdc > 0 ? { amountUsdc: paidUusdc / 1e6, billing: "unknown" } : void 0
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const payment = {
|
|
261
|
+
rail: "telegraph",
|
|
262
|
+
minerId: out.response.miner_id,
|
|
263
|
+
amountUsdc: out.amountUusdc / 1e6,
|
|
264
|
+
receiptHash: out.receipt?.receipt_hash ?? null,
|
|
265
|
+
signalHash: out.response.signal_hash ?? null
|
|
266
|
+
};
|
|
267
|
+
const paid = { amountUsdc: payment.amountUsdc, billing: "paid", receiptHash: payment.receiptHash };
|
|
268
|
+
const raw = out.response.result ?? {};
|
|
269
|
+
const id = raw.id ?? raw.result?.id;
|
|
270
|
+
const pending = raw.pending_id ?? raw.result?.pending_id;
|
|
271
|
+
if (!id && pending) {
|
|
272
|
+
const v2 = await this.wait(pending, req.signal ? { signal: req.signal } : {});
|
|
273
|
+
return { ...v2, payment };
|
|
274
|
+
}
|
|
275
|
+
if (!id) {
|
|
276
|
+
throw new SkeinError("TELEGRAPH_NO_VERDICT", `the miner answered ${raw.verdict ?? "without a verdict"}, with no verdict id`, 502, raw, paid);
|
|
277
|
+
}
|
|
278
|
+
const v = await this.get(id);
|
|
279
|
+
if ("status" in v && v.status === "running") {
|
|
280
|
+
const sealed = await this.wait(id, req.signal ? { signal: req.signal } : {});
|
|
281
|
+
return { ...sealed, payment };
|
|
282
|
+
}
|
|
283
|
+
return { ...v, payment };
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
export {
|
|
287
|
+
SKEIN_INTENT,
|
|
288
|
+
Skein,
|
|
289
|
+
SkeinError,
|
|
290
|
+
SpendCapError,
|
|
291
|
+
parseSse
|
|
292
|
+
};
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sse.ts","../src/index.ts"],"sourcesContent":["/** One parsed server-sent event. */\nexport interface RawEvent {\n event: string;\n data: string;\n id?: string;\n}\n\n/**\n * Parses a text/event-stream body into events (WHATWG rules: fields per line,\n * a blank line dispatches, \"data\" lines join with \\n, \":\" lines are comments).\n * Works on any fetch Response, in Node or the browser.\n */\nexport async function* parseSse(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<RawEvent> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n let event = \"message\";\n let data: string[] = [];\n let id: string | undefined;\n const onAbort = () => void reader.cancel().catch(() => {});\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n let nl: number;\n // A lone \"\\r\" at the end may be the first half of a \"\\r\\n\" split across chunks: wait for the next one.\n while ((nl = buf.search(/\\r\\n|\\r(?!$)|\\n/)) >= 0) {\n const line = buf.slice(0, nl);\n buf = buf.slice(nl + (buf.startsWith(\"\\r\\n\", nl) ? 2 : 1));\n if (line === \"\") {\n if (data.length) yield { event, data: data.join(\"\\n\"), ...(id !== undefined ? { id } : {}) };\n event = \"message\";\n data = [];\n continue;\n }\n if (line.startsWith(\":\")) continue;\n const colon = line.indexOf(\":\");\n const field = colon < 0 ? line : line.slice(0, colon);\n const value = colon < 0 ? \"\" : line.slice(colon + 1).replace(/^ /, \"\");\n if (field === \"event\") event = value;\n else if (field === \"data\") data.push(value);\n else if (field === \"id\") id = value;\n }\n }\n // A stream that ends mid-event drops it (WHATWG: an event is dispatched only by its blank line).\n } finally {\n signal?.removeEventListener(\"abort\", onAbort);\n // Stopping early (a terminal event, a break) must release the connection, not just the lock.\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n","import { parseSse } from \"./sse\";\nimport type { Bom, Quote, Subject, TelegraphAsker, VerdictEvent, VerdictObject, VerifyReport } from \"./types\";\n\nexport * from \"./types\";\nexport { parseSse } from \"./sse\";\n\n/** The intent Skein serves on Telegraph (plan A; see miner/INTENT.md). */\nexport const SKEIN_INTENT = \"FRAUD_RISK_TOKEN_LISTING\";\n\nconst CHAIN_NAMES: Record<number, string> = { 1: \"ethereum\", 56: \"bsc\", 8453: \"base\", 42161: \"arbitrum\" };\n\n/**\n * How a verdict is paid for:\n * - apiKey: a Skein partner key against the HTTP API (free during the hackathon);\n * - telegraph: one paid Telegraph ask, routed to the Skein miner or pinned to it.\n * `client` is any Telegraph client, e.g. new TelegraphClient(...) from @skeinlabs/sdk/telegraph,\n * which enforces its own per-call price cap on the 402 challenge.\n */\nexport type Payer =\n | { kind: \"apiKey\"; key: string }\n | { kind: \"telegraph\"; client: TelegraphAsker; minerId?: string; intent?: string };\n\nexport interface SkeinOptions {\n /** e.g. https://api.skeinlabs.site (with or without /v1). */\n apiUrl: string;\n payer?: Payer;\n /** Refuse, before paying, any verdict whose quoted price is above this. */\n maxUsdcPerCall?: number;\n /** Retries for network errors, 429 and retryable 5xx. Default 2. */\n retries?: number;\n /** The most the SDK sleeps between retries of one call, in total; a longer Retry-After throws instead. Default 10 s. */\n maxRetryWaitMs?: number;\n /** Each HTTP request (submit, quote, get, verify) gives up after this. Default 15 s. */\n requestTimeoutMs?: number;\n /** How long verdict() waits for a result. Default 120 s. */\n timeoutMs?: number;\n fetch?: typeof fetch;\n}\n\nexport interface VerdictRequest {\n subject: Subject;\n /** \"dex-list@0.1.0\", or an id for its latest version. */\n policy?: string;\n context?: { deployer?: string; social?: string; repo?: string; [k: string]: unknown };\n /** Reuse to make a retried call return the original verdict. Generated when omitted. */\n idempotencyKey?: string;\n /** Called with each live event (API key path). */\n onEvent?: (e: VerdictEvent) => void;\n signal?: AbortSignal;\n /** A quote you already fetched for this request, so verdict() does not fetch another. */\n quote?: Quote;\n}\n\nexport type Payment =\n | { rail: \"apiKey\" }\n | { rail: \"telegraph\"; minerId: string; amountUsdc: number; receiptHash: string | null; signalHash: string | null };\n\nexport type VerdictResult = VerdictObject & { payment: Payment };\n\n/** Every API error, with the API's code (BUDGET_TOO_LOW, RATE_LIMITED, …) or an SDK code. */\nexport class SkeinError extends Error {\n constructor(\n readonly code: string,\n message: string,\n readonly status = 0,\n readonly details?: unknown,\n /**\n * Set when money moved (or may have) before the failure: a Telegraph ask\n * that was paid but produced no verdict. Count it against your caps.\n */\n readonly payment?: { amountUsdc: number; billing: \"paid\" | \"unknown\"; receiptHash?: string | null },\n ) {\n super(message);\n this.name = \"SkeinError\";\n }\n}\n\n/** The quoted price is above maxUsdcPerCall; nothing was paid. */\nexport class SpendCapError extends SkeinError {\n constructor(\n readonly priceUsdc: number,\n readonly capUsdc: number,\n ) {\n super(\"SPEND_CAP\", `quoted ${priceUsdc} USDC is above the cap of ${capUsdc} USDC; nothing was paid`);\n this.name = \"SpendCapError\";\n }\n}\n\n/** Refusals that will not change on retry. */\nconst FINAL = new Set([\"UPSTREAM_UNAVAILABLE\", \"DAILY_CAP_REACHED\", \"MARGIN_GUARD\", \"SELF_ROUTING\", \"BUDGET_TOO_LOW\"]);\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\"abort\", () => (clearTimeout(t), reject(signal.reason)), { once: true });\n });\n\nexport class Skein {\n private readonly base: string;\n private readonly fetch: typeof fetch;\n private readonly retries: number;\n private readonly timeoutMs: number;\n\n constructor(private readonly opts: SkeinOptions) {\n this.base = opts.apiUrl.replace(/\\/$/, \"\").replace(/\\/v1$/, \"\");\n this.fetch = opts.fetch ?? ((...a) => globalThis.fetch(...a));\n this.retries = opts.retries ?? 2;\n this.timeoutMs = opts.timeoutMs ?? 120_000;\n this.maxRetryWaitMs = opts.maxRetryWaitMs ?? 10_000;\n this.requestTimeoutMs = opts.requestTimeoutMs ?? 15_000;\n }\n\n private readonly maxRetryWaitMs: number;\n private readonly requestTimeoutMs: number;\n\n /** One HTTP request with its own deadline, still cancellable by the caller's signal. */\n private async request(url: string, init: RequestInit): Promise<{ res: Response; json: unknown }> {\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(new DOMException(\"request timed out\", \"TimeoutError\")), this.requestTimeoutMs);\n const onAbort = () => ctl.abort(init.signal?.reason);\n init.signal?.addEventListener(\"abort\", onAbort, { once: true });\n try {\n const res = await this.fetch(url, { ...init, signal: ctl.signal });\n const json = await res.json().catch(() => null);\n return { res, json };\n } finally {\n clearTimeout(timer);\n init.signal?.removeEventListener(\"abort\", onAbort);\n }\n }\n\n /**\n * A raw call with typed errors and retries. POSTs are retried only with an\n * idempotency key. Retries sleep at most maxRetryWaitMs in total: a\n * Retry-After past that (a daily cap until midnight) throws at once.\n */\n private async call<T>(path: string, init: RequestInit = {}, retryable = (init.method ?? \"GET\") === \"GET\"): Promise<{ status: number; body: T }> {\n let waited = 0;\n for (let attempt = 0; ; attempt++) {\n let res: Response;\n let json: (T & { error?: { code: string; message: string; details?: unknown } }) | null;\n try {\n ({ res, json } = (await this.request(`${this.base}${path}`, init)) as { res: Response; json: typeof json });\n } catch (e) {\n if (init.signal?.aborted) throw e;\n const backoff = 500 * 2 ** attempt;\n if (retryable && attempt < this.retries && waited + backoff <= this.maxRetryWaitMs) {\n waited += backoff;\n await sleep(backoff, init.signal ?? undefined);\n continue;\n }\n const timedOut = (e as Error).name === \"TimeoutError\";\n throw new SkeinError(timedOut ? \"TIMEOUT\" : \"NETWORK\", timedOut ? `no answer from Skein in ${this.requestTimeoutMs} ms` : `could not reach Skein: ${(e as Error).message}`);\n }\n if (res.ok) return { status: res.status, body: json as T };\n const err = json?.error ?? { code: `HTTP_${res.status}`, message: res.statusText || `HTTP ${res.status}` };\n const after = Number(res.headers.get(\"retry-after\"));\n const wait = Number.isFinite(after) && after > 0 ? after * 1000 : 500 * 2 ** attempt;\n const again = retryable && attempt < this.retries && (res.status === 429 || (res.status >= 500 && !FINAL.has(err.code))) && waited + wait <= this.maxRetryWaitMs;\n if (!again) throw new SkeinError(err.code, err.message, res.status, err.details);\n waited += wait;\n await sleep(wait, init.signal ?? undefined);\n }\n }\n\n /** What a verdict would buy and cost, without buying. Free. */\n async quote(req: { subject: Subject; policy?: string }): Promise<Quote> {\n const q = new URLSearchParams({ chain: String(req.subject.chainId), token: req.subject.token });\n if (req.policy) q.set(\"policy\", req.policy);\n return (await this.call<Quote>(`/v1/quote?${q}`)).body;\n }\n\n /** A stored verdict, or { status: \"running\" } while it is being made. */\n async get(id: string): Promise<VerdictObject | { id: string; status: \"running\" }> {\n return (await this.call<VerdictObject | { id: string; status: \"running\" }>(`/v1/verdicts/${encodeURIComponent(id)}`)).body;\n }\n\n /** The bill of materials: every paid input and its receipt. */\n async bom(id: string): Promise<Bom> {\n return (await this.call<Bom>(`/v1/verdicts/${encodeURIComponent(id)}/bom`)).body;\n }\n\n /** Recompute a verdict from its raw paid answers. */\n async verify(id: string): Promise<VerifyReport> {\n return (\n await this.call<VerifyReport>(\"/v1/verify\", { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body: JSON.stringify({ id }) }, true)\n ).body;\n }\n\n /** The live events of a verdict, replayed from the start; ends after done, refused or error. */\n async *stream(id: string, opts: { signal?: AbortSignal } = {}): AsyncGenerator<VerdictEvent> {\n const res = await this.fetch(`${this.base}/v1/verdicts/${encodeURIComponent(id)}/events`, {\n headers: { accept: \"text/event-stream\" },\n ...(opts.signal ? { signal: opts.signal } : {}),\n });\n if (!res.ok || !res.body) throw new SkeinError(`HTTP_${res.status}`, `event stream failed: HTTP ${res.status}`, res.status);\n for await (const raw of parseSse(res.body, opts.signal)) {\n let data: unknown;\n try {\n data = JSON.parse(raw.data);\n } catch {\n continue;\n }\n const e = { type: raw.event, data } as VerdictEvent;\n yield e;\n if (e.type === \"done\" || e.type === \"refused\" || e.type === \"error\") return;\n }\n }\n\n /**\n * Buys one verdict and returns it once it is sealed. With maxUsdcPerCall\n * set, the price is quoted first and the call refused before any payment.\n */\n async verdict(req: VerdictRequest): Promise<VerdictResult> {\n const payer = this.opts.payer;\n if (!payer) throw new SkeinError(\"NO_PAYER\", \"verdict() needs a payer: { kind: 'apiKey', key } or { kind: 'telegraph', client }\");\n if (this.opts.maxUsdcPerCall !== undefined) {\n const q = req.quote ?? (await this.quote({ subject: req.subject, ...(req.policy ? { policy: req.policy } : {}) }));\n if (!q.ok && q.refusal) throw new SkeinError(q.refusal.code, q.refusal.detail, q.refusal.code === \"BUDGET_TOO_LOW\" ? 422 : 503, q.refusal);\n if (q.priceUsdc > this.opts.maxUsdcPerCall) throw new SpendCapError(q.priceUsdc, this.opts.maxUsdcPerCall);\n }\n return payer.kind === \"apiKey\" ? this.viaApi(req) : this.viaTelegraph(payer, req);\n }\n\n /**\n * Starts a verdict on the partner-key rail and returns at once: the id while\n * it runs, or the verdict itself when it is cached or replayed. Pair with\n * wait(id). Spend-cap checks are the caller's (verdict() does them).\n */\n async submit(req: VerdictRequest): Promise<{ id: string; status: \"running\" } | VerdictResult> {\n const payer = this.opts.payer;\n if (payer?.kind !== \"apiKey\") throw new SkeinError(\"NO_PAYER\", \"submit() needs an apiKey payer\");\n const { status, body } = await this.call<VerdictObject | { id: string; status: \"running\" }>(\n \"/v1/verdicts\",\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", authorization: `Bearer ${payer.key}`, \"idempotency-key\": req.idempotencyKey ?? crypto.randomUUID() },\n body: JSON.stringify({\n subject: req.subject,\n ...(req.policy ? { policy: req.policy } : {}),\n ...(req.context ? { context: req.context } : {}),\n ...(this.opts.maxUsdcPerCall !== undefined ? { budgetUsdcMax: this.opts.maxUsdcPerCall } : {}),\n mode: \"async\",\n }),\n ...(req.signal ? { signal: req.signal } : {}),\n },\n true,\n );\n return status === 202 ? (body as { id: string; status: \"running\" }) : { ...(body as VerdictObject), payment: { rail: \"apiKey\" } };\n }\n\n /**\n * Waits for a submitted verdict: follows its event stream (falling back to\n * polling if the stream drops) and throws a SkeinError if it is refused.\n */\n async wait(id: string, opts: { onEvent?: (e: VerdictEvent) => void; signal?: AbortSignal; timeoutMs?: number } = {}): Promise<VerdictResult> {\n const deadline = Date.now() + (opts.timeoutMs ?? this.timeoutMs);\n const timeout = AbortSignal.timeout(Math.max(1, deadline - Date.now()));\n const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;\n try {\n for await (const e of this.stream(id, { signal })) {\n opts.onEvent?.(e);\n if (e.type === \"refused\") throw new SkeinError(e.data.code, e.data.detail, e.data.code === \"BUDGET_TOO_LOW\" ? 422 : 503, e.data);\n if (e.type === \"error\") throw new SkeinError(\"VERDICT_FAILED\", e.data.message, 503);\n }\n } catch (e) {\n if (e instanceof SkeinError && !e.code.startsWith(\"HTTP_\")) throw e;\n if (opts.signal?.aborted) throw e;\n // The stream dropped or timed out: fall back to polling until the deadline.\n }\n for (;;) {\n const v = await this.get(id);\n if (!(\"status\" in v && v.status === \"running\")) return { ...(v as VerdictObject), payment: { rail: \"apiKey\" } };\n if (Date.now() >= deadline) throw new SkeinError(\"TIMEOUT\", `verdict ${id} is still running; call wait(id) again`, 504, { id });\n await sleep(Math.min(1000, Math.max(0, deadline - Date.now())), opts.signal);\n }\n }\n\n private async viaApi(req: VerdictRequest): Promise<VerdictResult> {\n const started = await this.submit(req);\n if (!(\"status\" in started && started.status === \"running\")) return started as VerdictResult;\n return this.wait(started.id, { ...(req.onEvent ? { onEvent: req.onEvent } : {}), ...(req.signal ? { signal: req.signal } : {}) });\n }\n\n private async viaTelegraph(payer: Extract<Payer, { kind: \"telegraph\" }>, req: VerdictRequest): Promise<VerdictResult> {\n const params = {\n token: req.subject.token,\n chain: CHAIN_NAMES[req.subject.chainId] ?? String(req.subject.chainId),\n subject: req.subject,\n ...(req.policy ? { policy: req.policy } : {}),\n ...(req.context ? { context: req.context } : {}),\n };\n const out = await payer.client.ask({ intent: payer.intent ?? SKEIN_INTENT, params }, { ...(payer.minerId ? { minerId: payer.minerId } : {}), timeoutMs: this.timeoutMs });\n if (!out.ok) {\n const paidUusdc = (out as { paidUusdc?: number }).paidUusdc ?? 0;\n throw new SkeinError(\n `TELEGRAPH_${out.kind.toUpperCase()}`,\n out.error,\n out.httpStatus ?? 0,\n undefined,\n paidUusdc > 0 ? { amountUsdc: paidUusdc / 1e6, billing: \"unknown\" } : undefined,\n );\n }\n const payment: Payment = {\n rail: \"telegraph\",\n minerId: out.response.miner_id,\n amountUsdc: out.amountUusdc / 1e6,\n receiptHash: out.receipt?.receipt_hash ?? null,\n signalHash: out.response.signal_hash ?? null,\n };\n const paid = { amountUsdc: payment.amountUsdc, billing: \"paid\" as const, receiptHash: payment.receiptHash };\n // The miner's JSON, either as the result itself or wrapped once by the node.\n const raw = (out.response.result ?? {}) as { id?: string; pending_id?: string; verdict?: string; reasons?: unknown; result?: { id?: string; pending_id?: string } };\n const id = raw.id ?? raw.result?.id;\n const pending = raw.pending_id ?? raw.result?.pending_id;\n if (!id && pending) {\n // The miner answered before its verdict was sealed (its deadline); the verdict is still being made.\n const v = await this.wait(pending, req.signal ? { signal: req.signal } : {});\n return { ...v, payment };\n }\n if (!id) {\n // Paid, but the miner abstained without a verdict (out of budget, upstream down): say what was paid.\n throw new SkeinError(\"TELEGRAPH_NO_VERDICT\", `the miner answered ${raw.verdict ?? \"without a verdict\"}, with no verdict id`, 502, raw, paid);\n }\n const v = await this.get(id);\n if (\"status\" in v && v.status === \"running\") {\n const sealed = await this.wait(id, req.signal ? { signal: req.signal } : {});\n return { ...sealed, payment };\n }\n return { ...(v as VerdictObject), payment };\n }\n}\n"],"mappings":"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","names":["value","v"]}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network settings. Defaults are the testnet values from the docs' Addresses
|
|
3
|
+
* page (read 2026-10-30); every one can be overridden from the environment,
|
|
4
|
+
* and the purchaser refuses a 402 challenge that disagrees with them.
|
|
5
|
+
*/
|
|
6
|
+
interface NetworkConfig {
|
|
7
|
+
nodeUrl: string;
|
|
8
|
+
/** CAIP-2 id of the chain the escrow lives on, e.g. eip155:11155111. */
|
|
9
|
+
caip2: string;
|
|
10
|
+
chainId: number;
|
|
11
|
+
diamond: string;
|
|
12
|
+
usdc: string;
|
|
13
|
+
rpcUrl: string;
|
|
14
|
+
}
|
|
15
|
+
declare const TESTNET: Readonly<NetworkConfig>;
|
|
16
|
+
declare function networkFromEnv(env?: Record<string, string | undefined>): NetworkConfig;
|
|
17
|
+
|
|
18
|
+
/** The standard Telegraph receipt (X-PAYMENT-RESPONSE, or X-TELEGRAPH-RECEIPT on x402). */
|
|
19
|
+
interface Receipt {
|
|
20
|
+
receipt_hash: string;
|
|
21
|
+
rail: string;
|
|
22
|
+
payer: string;
|
|
23
|
+
miner: string;
|
|
24
|
+
intent?: string;
|
|
25
|
+
amount_uusdc: string;
|
|
26
|
+
epoch_id?: number;
|
|
27
|
+
settlement?: string;
|
|
28
|
+
chain?: string;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
tx_hash?: string;
|
|
31
|
+
}
|
|
32
|
+
interface MessageSigner {
|
|
33
|
+
address: string;
|
|
34
|
+
signMessage(message: string): Promise<string>;
|
|
35
|
+
}
|
|
36
|
+
declare class ChallengeMismatchError extends Error {
|
|
37
|
+
readonly code = "CHALLENGE_MISMATCH";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** POST /engine/v1/ask[/{minerId}] bodies (docs: "Make a Request"). */
|
|
41
|
+
type AskRequest = {
|
|
42
|
+
intent: string;
|
|
43
|
+
params?: Record<string, unknown>;
|
|
44
|
+
query?: string;
|
|
45
|
+
context?: Record<string, unknown>;
|
|
46
|
+
} | {
|
|
47
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
48
|
+
endpoint: string;
|
|
49
|
+
payload: Record<string, unknown>;
|
|
50
|
+
};
|
|
51
|
+
interface AskResponse {
|
|
52
|
+
miner_id: string;
|
|
53
|
+
miner_name?: string;
|
|
54
|
+
endpoint?: string;
|
|
55
|
+
result: unknown;
|
|
56
|
+
result_shape?: "intent" | "raw";
|
|
57
|
+
shape_error?: string;
|
|
58
|
+
field_status?: Record<string, "ok" | "missing" | "rejected" | "unavailable">;
|
|
59
|
+
intent?: string;
|
|
60
|
+
cost_usd?: number;
|
|
61
|
+
duration_ms?: number;
|
|
62
|
+
signal_hash?: string;
|
|
63
|
+
warnings?: string[];
|
|
64
|
+
}
|
|
65
|
+
/** incompatible: the node refused the request before payment (no_compatible_miner, invalid_params); try another miner. */
|
|
66
|
+
type AskFailureKind = "timeout" | "network" | "refused" | "client_error" | "server_error" | "incompatible" | "mismatch" | "preseason";
|
|
67
|
+
/**
|
|
68
|
+
* paidUusdc: what was signed and sent with X-PAYMENT on the failing attempt.
|
|
69
|
+
* After a paid POST fails (timeout, dropped connection, 5xx, unreadable 200)
|
|
70
|
+
* the node may still have billed it, so the amount is reported with
|
|
71
|
+
* billing "unknown" and the call is never retried.
|
|
72
|
+
*/
|
|
73
|
+
type AskOutcome = {
|
|
74
|
+
ok: true;
|
|
75
|
+
response: AskResponse;
|
|
76
|
+
receipt: Receipt | null;
|
|
77
|
+
/** Why the receipt is not evidence of this purchase; empty when it checks out. */
|
|
78
|
+
receiptProblems: string[];
|
|
79
|
+
httpStatus: number;
|
|
80
|
+
amountUusdc: number;
|
|
81
|
+
durationMs: number;
|
|
82
|
+
attempts: number;
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
kind: AskFailureKind;
|
|
86
|
+
httpStatus?: number;
|
|
87
|
+
error: string;
|
|
88
|
+
durationMs: number;
|
|
89
|
+
attempts: number;
|
|
90
|
+
paidUusdc: number;
|
|
91
|
+
billing: "none" | "unknown";
|
|
92
|
+
};
|
|
93
|
+
type SignalStatus = "verified" | "missing" | "mismatch" | "unchecked";
|
|
94
|
+
interface MinerRecord {
|
|
95
|
+
id: string;
|
|
96
|
+
slug: string;
|
|
97
|
+
name: string;
|
|
98
|
+
base_url: string;
|
|
99
|
+
supported_intents: string[];
|
|
100
|
+
activation_status: string;
|
|
101
|
+
min_price_usdc: number;
|
|
102
|
+
scored?: boolean;
|
|
103
|
+
total_requests_served?: number;
|
|
104
|
+
}
|
|
105
|
+
interface ClientOptions {
|
|
106
|
+
network: NetworkConfig;
|
|
107
|
+
signer?: MessageSigner;
|
|
108
|
+
fetch?: typeof fetch;
|
|
109
|
+
/** Hard cap on what one call may cost, in micro-USDC. */
|
|
110
|
+
maxUusdcPerCall: number;
|
|
111
|
+
now?: () => number;
|
|
112
|
+
nonce?: () => string;
|
|
113
|
+
/** Allow paid calls before the season opens (pre-season smoke tests only; never event evidence). */
|
|
114
|
+
allowPreSeason?: boolean;
|
|
115
|
+
}
|
|
116
|
+
declare class PreSeasonError extends Error {
|
|
117
|
+
readonly code = "PRE_SEASON";
|
|
118
|
+
}
|
|
119
|
+
declare function viemSigner(privateKey: `0x${string}`): MessageSigner;
|
|
120
|
+
declare class TelegraphClient {
|
|
121
|
+
private readonly opts;
|
|
122
|
+
private readonly fetch;
|
|
123
|
+
private readonly now;
|
|
124
|
+
private readonly nonce;
|
|
125
|
+
constructor(opts: ClientOptions);
|
|
126
|
+
private url;
|
|
127
|
+
private getJson;
|
|
128
|
+
/**
|
|
129
|
+
* One request with a referenced timer (unlike AbortSignal.timeout(), it keeps
|
|
130
|
+
* a worker or CLI alive while an upstream call hangs) that also covers
|
|
131
|
+
* reading the body, so a slow body cannot hang a verdict.
|
|
132
|
+
*/
|
|
133
|
+
private send;
|
|
134
|
+
listMiners(q?: {
|
|
135
|
+
intent?: string;
|
|
136
|
+
status?: string;
|
|
137
|
+
limit?: number;
|
|
138
|
+
}): Promise<MinerRecord[]>;
|
|
139
|
+
getIntents(): Promise<{
|
|
140
|
+
count: number;
|
|
141
|
+
intents: {
|
|
142
|
+
intent_id: string;
|
|
143
|
+
miner_count: number;
|
|
144
|
+
canonical: boolean;
|
|
145
|
+
}[];
|
|
146
|
+
}>;
|
|
147
|
+
getIntent(intent: string): Promise<Record<string, unknown>>;
|
|
148
|
+
getSignal(signalHash: string): Promise<Record<string, unknown>>;
|
|
149
|
+
/**
|
|
150
|
+
* Looks a paid answer's signal_hash up on the node, the network's own record
|
|
151
|
+
* of it. "verified": the node has the signal (and, when it says which miner,
|
|
152
|
+
* that miner is the one paid); "missing": the node has no such signal;
|
|
153
|
+
* "mismatch": it does, from another miner; "unchecked": the node did not answer.
|
|
154
|
+
*/
|
|
155
|
+
verifySignal(signalHash: string | undefined, expect?: {
|
|
156
|
+
minerId?: string;
|
|
157
|
+
}): Promise<SignalStatus>;
|
|
158
|
+
/**
|
|
159
|
+
* Buys one answer on the escrow rail. Failures before payment (no answer to
|
|
160
|
+
* the unpaid probe, a 5xx) and an explicit 402 refusal of the payment are
|
|
161
|
+
* retried once with a fresh challenge and nonce: nothing was billed. A
|
|
162
|
+
* failure after X-PAYMENT was sent is never retried, because the node may
|
|
163
|
+
* have billed it; it comes back with paidUusdc set and billing "unknown".
|
|
164
|
+
* maxUusdc lowers the client-wide cap for this one call.
|
|
165
|
+
*/
|
|
166
|
+
ask(req: AskRequest, opts: {
|
|
167
|
+
minerId?: string;
|
|
168
|
+
timeoutMs: number;
|
|
169
|
+
maxUusdc?: number;
|
|
170
|
+
}): Promise<AskOutcome>;
|
|
171
|
+
private attempt;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { type AskFailureKind, type AskOutcome, type AskRequest, type AskResponse, ChallengeMismatchError, type ClientOptions, type MessageSigner, type NetworkConfig, PreSeasonError, type Receipt, TESTNET, TelegraphClient, networkFromEnv, viemSigner };
|