@0xinsider/mcp 1.3.1 → 2.0.1

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.
Files changed (57) hide show
  1. package/README.md +98 -0
  2. package/dist/cli-lib.d.ts.map +1 -1
  3. package/dist/cli-lib.js +9 -5
  4. package/dist/cli-lib.js.map +1 -1
  5. package/dist/cli.js +6 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/constants.d.ts +1 -1
  8. package/dist/constants.js +1 -1
  9. package/dist/product-cli/client.d.ts +9 -0
  10. package/dist/product-cli/client.d.ts.map +1 -0
  11. package/dist/product-cli/client.js +78 -0
  12. package/dist/product-cli/client.js.map +1 -0
  13. package/dist/product-cli/errors.d.ts +7 -0
  14. package/dist/product-cli/errors.d.ts.map +1 -0
  15. package/dist/product-cli/errors.js +13 -0
  16. package/dist/product-cli/errors.js.map +1 -0
  17. package/dist/product-cli/index.d.ts +4 -0
  18. package/dist/product-cli/index.d.ts.map +1 -0
  19. package/dist/product-cli/index.js +354 -0
  20. package/dist/product-cli/index.js.map +1 -0
  21. package/dist/product-cli/oauth.d.ts +10 -0
  22. package/dist/product-cli/oauth.d.ts.map +1 -0
  23. package/dist/product-cli/oauth.js +246 -0
  24. package/dist/product-cli/oauth.js.map +1 -0
  25. package/dist/product-cli/storage.d.ts +20 -0
  26. package/dist/product-cli/storage.d.ts.map +1 -0
  27. package/dist/product-cli/storage.js +158 -0
  28. package/dist/product-cli/storage.js.map +1 -0
  29. package/dist/sdk/client.d.ts +765 -0
  30. package/dist/sdk/client.d.ts.map +1 -0
  31. package/dist/sdk/client.js +974 -0
  32. package/dist/sdk/client.js.map +1 -0
  33. package/dist/sdk/errors.d.ts +313 -0
  34. package/dist/sdk/errors.d.ts.map +1 -0
  35. package/dist/sdk/errors.js +521 -0
  36. package/dist/sdk/errors.js.map +1 -0
  37. package/dist/sdk/index.d.ts +26 -0
  38. package/dist/sdk/index.d.ts.map +1 -0
  39. package/dist/sdk/index.js +25 -0
  40. package/dist/sdk/index.js.map +1 -0
  41. package/dist/sdk/pagination.d.ts +76 -0
  42. package/dist/sdk/pagination.d.ts.map +1 -0
  43. package/dist/sdk/pagination.js +106 -0
  44. package/dist/sdk/pagination.js.map +1 -0
  45. package/dist/sdk/schema.d.ts +2425 -0
  46. package/dist/sdk/schema.d.ts.map +1 -0
  47. package/dist/sdk/schema.js +7 -0
  48. package/dist/sdk/schema.js.map +1 -0
  49. package/dist/sdk/stream.d.ts +182 -0
  50. package/dist/sdk/stream.d.ts.map +1 -0
  51. package/dist/sdk/stream.js +391 -0
  52. package/dist/sdk/stream.js.map +1 -0
  53. package/dist/sdk/webhooks.d.ts +163 -0
  54. package/dist/sdk/webhooks.d.ts.map +1 -0
  55. package/dist/sdk/webhooks.js +122 -0
  56. package/dist/sdk/webhooks.js.map +1 -0
  57. package/package.json +6 -3
@@ -0,0 +1,391 @@
1
+ /**
2
+ * SSE consumer for `GET /api/v1/stream`.
3
+ *
4
+ * The endpoint forwards the platform's live feed envelopes as Server-Sent
5
+ * Events. Source of truth: `web/public/api/v1/openapi.json` -> paths./api/v1/stream.
6
+ *
7
+ * Wire format (per the spec's response description):
8
+ * - Each data frame is `id: <seq>\ndata: <json-envelope>\n\n`, where the JSON
9
+ * envelope is `{ seq, published_at, type, ...event-specific }`. The SSE id
10
+ * equals the envelope `seq`.
11
+ * - A resync marker is `event: resync\nid: <seq>\ndata: <json>\n\n`, where the
12
+ * JSON is `{ type: "resync", completeness, from_sequence, to_sequence }`.
13
+ * It means the requested resume point is outside the retained window or a
14
+ * live sequence gap was observed; treat it as "refetch current state".
15
+ * - Idle connections emit `: keep-alive` comment lines (ignored).
16
+ *
17
+ * Resume is via the `Last-Event-ID` header set to the last `seq` you processed;
18
+ * the spec also accepts `last_event_id` / `seq` query fallbacks. This consumer
19
+ * sends the header and auto-tracks the last seen seq so a caller-driven
20
+ * reconnect resumes from the right place.
21
+ *
22
+ * Filters (per-connection, subscribe-time): `event` (comma-separated frame
23
+ * types), `condition_id`, and `min_grade` (S|A|B|C|D|F).
24
+ */
25
+ import { errorFromResponse, OxinsiderApiError } from "./errors.js";
26
+ /**
27
+ * Open the stream and async-iterate its frames. Yields a discriminated
28
+ * `StreamEvent`: `kind: "event"` carries the `FeedEnvelope`, `kind: "resync"`
29
+ * carries the `ResyncMarker`.
30
+ *
31
+ * Pass `options.cursor` (a `{ seq?: number }` object) to have the last
32
+ * delivered `seq` written back as frames arrive; reconnect with
33
+ * `lastEventId: cursor.seq` to resume after a transport drop.
34
+ *
35
+ * @example
36
+ * for await (const frame of streamFeed(client, { event: ["WhaleTradesInserted"], min_grade: "S" })) {
37
+ * if (frame.kind === "event") console.log(frame.envelope.type, frame.seq);
38
+ * }
39
+ */
40
+ export async function* streamFeed(client, options = {}) {
41
+ const apiKey = client.getApiKey();
42
+ if (!apiKey) {
43
+ throw new Error("getStream requires an API key (oxi_sk_*)");
44
+ }
45
+ const url = buildStreamUrl(client, options);
46
+ const headers = new Headers({ accept: "text/event-stream" });
47
+ headers.set("authorization", `Bearer ${apiKey}`);
48
+ if (options.lastEventId !== undefined) {
49
+ headers.set("last-event-id", String(options.lastEventId));
50
+ }
51
+ const fetchImpl = client.getFetch();
52
+ const response = await fetchImpl(url, {
53
+ method: "GET",
54
+ headers,
55
+ signal: options.signal,
56
+ });
57
+ if (!response.ok) {
58
+ const body = await safeText(response);
59
+ throw errorFromResponse(response.status, tryParse(body), retryAfterSeconds(response));
60
+ }
61
+ if (!response.body) {
62
+ throw new Error("Stream response has no readable body");
63
+ }
64
+ const reader = response.body.getReader();
65
+ const decoder = new TextDecoder();
66
+ let buffer = "";
67
+ try {
68
+ for (;;) {
69
+ const { value, done } = await reader.read();
70
+ if (done)
71
+ break;
72
+ buffer += decoder.decode(value, { stream: true });
73
+ // SSE frames are separated by a blank line. Handle CRLF and LF.
74
+ let sepIndex = nextFrameBoundary(buffer);
75
+ while (sepIndex.index !== -1) {
76
+ const rawFrame = buffer.slice(0, sepIndex.index);
77
+ buffer = buffer.slice(sepIndex.index + sepIndex.length);
78
+ const parsed = parseSseFrame(rawFrame);
79
+ if (parsed) {
80
+ if (parsed.event === "resync") {
81
+ const marker = (tryParse(parsed.data) ?? {});
82
+ options.onResync?.(marker);
83
+ yield { kind: "resync", seq: parsed.id ?? null, marker };
84
+ }
85
+ else {
86
+ const envelope = tryParse(parsed.data);
87
+ if (envelope && typeof envelope === "object") {
88
+ const seq = typeof envelope.seq === "number"
89
+ ? envelope.seq
90
+ : (parsed.id ?? NaN);
91
+ if (Number.isFinite(seq) && options.cursor) {
92
+ options.cursor.seq = seq;
93
+ }
94
+ yield { kind: "event", seq, envelope };
95
+ }
96
+ }
97
+ }
98
+ sepIndex = nextFrameBoundary(buffer);
99
+ }
100
+ }
101
+ }
102
+ finally {
103
+ // `releaseLock()` detaches the reader but leaves the body -- and the
104
+ // underlying HTTP connection -- open. Any early exit from the consumer
105
+ // loop (`break`, `return`, or a throw from a handler) runs this block via
106
+ // the generator's `return()`, so without `cancel()` every early exit leaks
107
+ // one open connection to the SSE endpoint. The documented reconnect loop
108
+ // makes that one leak per reconnect, until the process runs out of sockets
109
+ // or the server's per-user SSE lease cap rejects the user's own
110
+ // reconnects. `cancel()` releases the lock as part of cancelling, and can
111
+ // reject on an already-errored stream, so the rejection is swallowed to
112
+ // keep the `finally` non-throwing (#9682).
113
+ await reader.cancel().catch(() => undefined);
114
+ }
115
+ }
116
+ /** Default for `ResilientStreamOptions.maxReconnects`. */
117
+ export const DEFAULT_MAX_STREAM_RECONNECTS = 10;
118
+ const STREAM_RECONNECT_BASE_MS = 1_000;
119
+ const STREAM_RECONNECT_MAX_MS = 30_000;
120
+ const STREAM_RECONNECT_JITTER_MS = 250;
121
+ /**
122
+ * `streamFeedResilient` gave up after `maxReconnects` consecutive failed
123
+ * connections. `lastSeq` is the seq to resume after (pass it as `lastEventId`
124
+ * later); `cause` is the last connection's failure.
125
+ */
126
+ export class StreamReconnectsExhaustedError extends Error {
127
+ lastSeq;
128
+ attempts;
129
+ constructor(attempts, lastSeq, cause) {
130
+ super(`0xinsider stream gave up after ${String(attempts)} consecutive reconnect attempts`, { cause });
131
+ this.name = "StreamReconnectsExhaustedError";
132
+ this.attempts = attempts;
133
+ this.lastSeq = lastSeq;
134
+ }
135
+ }
136
+ /**
137
+ * A 4xx other than 429 will fail the same way on every reconnect: a bad filter
138
+ * (400), a key that is invalid (401), lapsed (402), refused (403), or locked
139
+ * (423). Reconnecting would only hammer the API with a credential a person
140
+ * has to fix.
141
+ */
142
+ function isPermanentStreamError(error) {
143
+ return (error instanceof OxinsiderApiError &&
144
+ error.status >= 400 &&
145
+ error.status < 500 &&
146
+ error.status !== 429);
147
+ }
148
+ function streamReconnectDelayMs(attempt, cause) {
149
+ const retryAfter = cause instanceof OxinsiderApiError &&
150
+ "retryAfterSeconds" in cause &&
151
+ typeof cause.retryAfterSeconds === "number"
152
+ ? cause.retryAfterSeconds
153
+ : null;
154
+ if (retryAfter !== null) {
155
+ return Math.max(0, retryAfter * 1000) + Math.random() * STREAM_RECONNECT_JITTER_MS;
156
+ }
157
+ const ceiling = Math.min(STREAM_RECONNECT_MAX_MS, STREAM_RECONNECT_BASE_MS * 2 ** (attempt - 1));
158
+ return ceiling / 2 + Math.random() * (ceiling / 2);
159
+ }
160
+ /** Resolve `true` after `ms`, or `false` the moment `signal` aborts. */
161
+ function waitUnlessAborted(ms, signal) {
162
+ return new Promise((resolve) => {
163
+ if (signal?.aborted) {
164
+ resolve(false);
165
+ return;
166
+ }
167
+ const onAbort = () => {
168
+ clearTimeout(timer);
169
+ resolve(false);
170
+ };
171
+ const timer = setTimeout(() => {
172
+ signal?.removeEventListener("abort", onAbort);
173
+ resolve(true);
174
+ }, ms);
175
+ signal?.addEventListener("abort", onAbort, { once: true });
176
+ });
177
+ }
178
+ /**
179
+ * `streamFeed` with the reconnect-and-resume loop built in (#14286).
180
+ *
181
+ * - A clean server close, a network error, a 429 or a 5xx reconnects,
182
+ * resuming after the last delivered seq (`Last-Event-ID`). Before any frame
183
+ * arrives it resumes from `lastEventId`, or attaches live when that is unset.
184
+ * - The wait is `Retry-After` plus jitter when the refusal carries one,
185
+ * otherwise a jittered backoff from 1 s capped at 30 s.
186
+ * - Any other 4xx (400, 401, 402, 403, 423) is thrown at once: it cannot heal
187
+ * on its own.
188
+ * - After `maxReconnects` consecutive connections that delivered nothing, it
189
+ * throws `StreamReconnectsExhaustedError` carrying `lastSeq`.
190
+ * - `resync` markers are yielded unchanged, and `onResync` still fires. The
191
+ * marker's `id` becomes the resume cursor, as the server intends, so a
192
+ * reconnect after one does not request the aged window again.
193
+ * - Aborting `signal` ends the iterator without throwing, during a connection
194
+ * or a backoff. Each connection's body reader is cancelled when it ends, so
195
+ * reconnects do not accumulate sockets.
196
+ * - No request deadline applies, as with `streamFeed`.
197
+ *
198
+ * `options.cursor`, when passed, tracks the last delivered seq across every
199
+ * connection.
200
+ *
201
+ * @example
202
+ * const controller = new AbortController();
203
+ * for await (const frame of streamFeedResilient(client, {
204
+ * event: ["WhaleTradesInserted"],
205
+ * signal: controller.signal,
206
+ * onReconnect: (attempt, lastSeq, cause) => console.warn("reconnecting", attempt, lastSeq, cause),
207
+ * })) {
208
+ * if (frame.kind === "resync") await refetchState();
209
+ * else handle(frame.envelope);
210
+ * }
211
+ */
212
+ export async function* streamFeedResilient(client, options = {}) {
213
+ const { maxReconnects = DEFAULT_MAX_STREAM_RECONNECTS, onReconnect, ...streamOptions } = options;
214
+ if (!Number.isInteger(maxReconnects) || maxReconnects < 0) {
215
+ throw new Error(`maxReconnects must be a non-negative integer, got ${String(maxReconnects)}`);
216
+ }
217
+ // The one configuration error `streamFeed` throws before any request; it
218
+ // would otherwise count as a transport failure and burn every reconnect.
219
+ if (!client.getApiKey()) {
220
+ throw new Error("streamFeedResilient requires an API key (oxi_sk_*)");
221
+ }
222
+ const signal = streamOptions.signal;
223
+ const cursor = streamOptions.cursor ?? {};
224
+ const initialLastEventId = streamOptions.lastEventId;
225
+ let attempt = 0;
226
+ for (;;) {
227
+ if (signal?.aborted)
228
+ return;
229
+ let cause;
230
+ try {
231
+ for await (const frame of streamFeed(client, {
232
+ ...streamOptions,
233
+ cursor,
234
+ lastEventId: cursor.seq ?? initialLastEventId,
235
+ })) {
236
+ attempt = 0;
237
+ if (frame.kind === "resync" && frame.seq !== null) {
238
+ cursor.seq = frame.seq;
239
+ }
240
+ yield frame;
241
+ }
242
+ }
243
+ catch (error) {
244
+ if (signal?.aborted)
245
+ return;
246
+ if (isPermanentStreamError(error))
247
+ throw error;
248
+ cause = error;
249
+ }
250
+ if (signal?.aborted)
251
+ return;
252
+ attempt += 1;
253
+ const lastSeq = cursor.seq ?? toSeq(initialLastEventId);
254
+ if (attempt > maxReconnects) {
255
+ throw new StreamReconnectsExhaustedError(attempt - 1, lastSeq, cause);
256
+ }
257
+ onReconnect?.(attempt, lastSeq, cause);
258
+ if (!(await waitUnlessAborted(streamReconnectDelayMs(attempt, cause), signal))) {
259
+ return;
260
+ }
261
+ }
262
+ }
263
+ function toSeq(value) {
264
+ if (value === undefined)
265
+ return undefined;
266
+ const seq = Number(value);
267
+ return Number.isFinite(seq) ? seq : undefined;
268
+ }
269
+ /**
270
+ * Callback-style stream consumer for environments where an async iterator is
271
+ * awkward. Returns a promise that resolves when the stream ends (or rejects on
272
+ * error / abort). Pass a `signal` to stop it.
273
+ */
274
+ export async function consumeStream(client, handlers, options = {}) {
275
+ for await (const frame of streamFeed(client, options)) {
276
+ if (frame.kind === "event") {
277
+ await handlers.onEvent?.(frame.envelope, frame.seq);
278
+ }
279
+ else {
280
+ await handlers.onResync?.(frame.marker, frame.seq);
281
+ }
282
+ }
283
+ }
284
+ function buildStreamUrl(client, options) {
285
+ const url = new URL("api/v1/stream", `${client.getBaseUrl()}/`);
286
+ if (options.event && options.event.length > 0) {
287
+ url.searchParams.set("event", options.event.join(","));
288
+ }
289
+ if (options.condition_id) {
290
+ url.searchParams.set("condition_id", options.condition_id);
291
+ }
292
+ if (options.min_grade) {
293
+ url.searchParams.set("min_grade", options.min_grade);
294
+ }
295
+ return url.toString();
296
+ }
297
+ /** Parse one SSE frame (without the trailing blank line). */
298
+ export function parseSseFrame(raw) {
299
+ const lines = raw.split(/\r?\n/);
300
+ let event;
301
+ let id;
302
+ const dataParts = [];
303
+ let sawField = false;
304
+ for (const line of lines) {
305
+ if (line === "" || line.startsWith(":")) {
306
+ // Blank or comment line (e.g. ": keep-alive"): ignore.
307
+ continue;
308
+ }
309
+ const colon = line.indexOf(":");
310
+ const field = colon === -1 ? line : line.slice(0, colon);
311
+ let value = colon === -1 ? "" : line.slice(colon + 1);
312
+ if (value.startsWith(" "))
313
+ value = value.slice(1);
314
+ switch (field) {
315
+ case "event":
316
+ event = value;
317
+ sawField = true;
318
+ break;
319
+ case "id": {
320
+ const n = Number(value);
321
+ if (Number.isFinite(n))
322
+ id = n;
323
+ sawField = true;
324
+ break;
325
+ }
326
+ case "data":
327
+ dataParts.push(value);
328
+ sawField = true;
329
+ break;
330
+ default:
331
+ // Unknown SSE field; ignore per the spec.
332
+ break;
333
+ }
334
+ }
335
+ if (!sawField && dataParts.length === 0) {
336
+ return null;
337
+ }
338
+ return { event, id, data: dataParts.join("\n") };
339
+ }
340
+ /** Find the next SSE frame boundary (blank line); handles `\n\n` and `\r\n\r\n`. */
341
+ function nextFrameBoundary(buffer) {
342
+ // Three terminator shapes, not two (#9682). A producer that ends FIELD lines
343
+ // with CRLF but the blank separator line with a bare LF yields "\r\n\n",
344
+ // which the old two-way scan cut at the "\n\n" -- one byte late, leaving a
345
+ // trailing "\r" on the last field. `parseSseFrame` splits on /\r?\n/ and so
346
+ // cannot strip a "\r" from a line with no newline after it, which turned
347
+ // `event: resync` into `event: "resync\r"` and made the exact `=== "resync"`
348
+ // routing check fail: the resume marker the cursor protocol depends on was
349
+ // silently delivered as a malformed envelope. The current Axum backend emits
350
+ // LF only, so this hardens a latent path rather than fixing a live break.
351
+ const candidates = [
352
+ { index: buffer.indexOf("\r\n\r\n"), length: 4 },
353
+ { index: buffer.indexOf("\r\n\n"), length: 3 },
354
+ { index: buffer.indexOf("\n\n"), length: 2 },
355
+ ].filter((candidate) => candidate.index !== -1);
356
+ if (candidates.length === 0)
357
+ return { index: -1, length: 0 };
358
+ // Earliest boundary wins; on a tie the LONGEST terminator wins, because
359
+ // "\r\n\r\n" and "\r\n\n" both also match "\n\n" at a later index and a
360
+ // short match would leave terminator bytes in the next frame.
361
+ return candidates.reduce((best, candidate) => candidate.index < best.index ||
362
+ (candidate.index === best.index && candidate.length > best.length)
363
+ ? candidate
364
+ : best);
365
+ }
366
+ function tryParse(text) {
367
+ if (text === null || text === "")
368
+ return null;
369
+ try {
370
+ return JSON.parse(text);
371
+ }
372
+ catch {
373
+ return null;
374
+ }
375
+ }
376
+ async function safeText(response) {
377
+ try {
378
+ return await response.text();
379
+ }
380
+ catch {
381
+ return "";
382
+ }
383
+ }
384
+ function retryAfterSeconds(response) {
385
+ const header = response.headers.get("retry-after");
386
+ if (!header)
387
+ return null;
388
+ const seconds = Number(header);
389
+ return Number.isFinite(seconds) ? seconds : null;
390
+ }
391
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../../sdk/src/stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Webhook signature verification and typed delivery payloads.
3
+ *
4
+ * The backend signs every webhook delivery exactly like this (source of truth:
5
+ * `backend/src/api_v1/handlers/webhooks/endpoint_security.rs::sign_webhook_payload`):
6
+ *
7
+ * signature = "v1=" + hex( HMAC_SHA256( signing_secret, `${timestamp}.${raw_body}` ) )
8
+ *
9
+ * It is sent on two headers:
10
+ * - `x-0xinsider-timestamp: <unix-seconds>`
11
+ * - `x-0xinsider-signature: v1=<hex>` (may be a comma-separated list)
12
+ *
13
+ * Verification, the receiver side. The backend only signs; the published
14
+ * recipe is the delivery-signing paragraph on POST /api/v1/webhooks in
15
+ * `web/public/api/v1/openapi.json`:
16
+ * 1. Reject if `|now - timestamp| > 300` seconds (replay tolerance).
17
+ * 2. Recompute the expected `v1=<hex>` over `${timestamp}.${rawBody}`.
18
+ * 3. Constant-time compare against each candidate in the header.
19
+ *
20
+ * `rawBody` MUST be the exact bytes received: verify before JSON parsing, since
21
+ * re-serializing changes whitespace/key order and breaks the HMAC.
22
+ *
23
+ * ENDPOINT ACTIVATION (`verifyWebhook`, `POST /api/v1/webhooks/{id}/verify`):
24
+ * the one-time `verification.token` is necessary but not sufficient. That call
25
+ * POSTs a challenge to your webhook url and activates the endpoint only if you
26
+ * answer 2xx within 10 seconds. The challenge body is
27
+ * `{ "type": "webhook.verification", "token": "<verification_token>",
28
+ * "webhook_id": <id> }`, it carries `x-0xinsider-event-type:
29
+ * webhook.verification`, and it is signed with the endpoint's `signing_secret`
30
+ * exactly like a delivery -- so `verifySignature` below validates it unchanged.
31
+ * The challenge follows no redirects and never reads your response body. A
32
+ * non-2xx answer, no answer inside 10 seconds, or a url that does not resolve to
33
+ * a publicly routable address returns 422 and leaves the endpoint
34
+ * `pending_verification`.
35
+ */
36
+ /** Default replay tolerance; the published contract is 300 seconds. */
37
+ export declare const SIGNATURE_TOLERANCE_SECONDS = 300;
38
+ /** Header carrying the `v1=<hex>` signature. */
39
+ export declare const SIGNATURE_HEADER = "x-0xinsider-signature";
40
+ /** Header carrying the unix-seconds signing timestamp. */
41
+ export declare const TIMESTAMP_HEADER = "x-0xinsider-timestamp";
42
+ export interface VerifySignatureInput {
43
+ /** The endpoint's signing secret (returned on create / rotate-secret). */
44
+ secret: string;
45
+ /** The `x-0xinsider-timestamp` value (unix seconds, number or string). */
46
+ timestamp: number | string;
47
+ /** The exact raw request body bytes/string, pre-JSON-parse. */
48
+ body: string | Uint8Array;
49
+ /** The `x-0xinsider-signature` header value (`v1=<hex>`, possibly a list). */
50
+ signature: string;
51
+ /** Replay tolerance in seconds; defaults to 300. */
52
+ toleranceSeconds?: number;
53
+ /** Reference "now" in unix seconds; defaults to `Date.now()/1000`. */
54
+ nowSeconds?: number;
55
+ }
56
+ /**
57
+ * Recompute the canonical signature for a payload. Returns `v1=<hex>`.
58
+ * Exposed so callers can sign in tests; matches the backend byte-for-byte.
59
+ */
60
+ export declare function computeSignature(secret: string, timestamp: number | string, body: string | Uint8Array): string;
61
+ /**
62
+ * Verify a webhook signature. Returns `true` only when the timestamp is within
63
+ * tolerance AND a candidate signature constant-time-matches the recomputed one.
64
+ * Never throws on a bad signature; throws only on a missing/empty secret.
65
+ */
66
+ export declare function verifySignature(input: VerifySignatureInput): boolean;
67
+ /**
68
+ * Webhook event types. Source of truth:
69
+ * `web/public/api/v1/openapi.json` -> components.schemas.WebhookEventType.enum.
70
+ * `whale_trades_inserted`, `wallet_grade_changed`, `insider_radar_flag_raised`,
71
+ * and `smart_money_flow_detected` are Pro-only: they deliver only to API keys
72
+ * on an active Pro subscription.
73
+ */
74
+ export declare const WEBHOOK_EVENT_TYPES: readonly ["whale_trades_inserted", "live_sports_updated", "whale_trader_synced", "large_positions_updated", "wallet_grade_changed", "insider_radar_flag_raised", "smart_money_flow_detected"];
75
+ export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
76
+ /**
77
+ * Common delivery envelope every webhook POST body carries. Source of truth:
78
+ * the `enqueue_webhook_event` call sites in the backend
79
+ * (`{ id, type, created_at, data }`).
80
+ */
81
+ export interface WebhookEventEnvelope<TType extends WebhookEventType = WebhookEventType, TData = unknown> {
82
+ /** Stable event id; identical across retries of the same logical event. */
83
+ id: string;
84
+ type: TType;
85
+ /** ISO-8601 timestamp of when the event was enqueued. */
86
+ created_at: string;
87
+ data: TData;
88
+ }
89
+ /** `whale_trades_inserted` data (Pro-only): a batch of new whale trades was ingested. */
90
+ export interface WhaleTradesInsertedData {
91
+ count: number;
92
+ }
93
+ /** `whale_trader_synced` data: a tracked trader finished a sync pass. */
94
+ export interface WhaleTraderSyncedData {
95
+ trader_id: number;
96
+ wallet: string;
97
+ }
98
+ /** `large_positions_updated` data. */
99
+ export interface LargePositionsUpdatedData {
100
+ count: number;
101
+ }
102
+ /**
103
+ * `wallet_grade_changed` data (Pro-only): a tracked wallet's grade moved.
104
+ * Source: `backend/crates/sync/src/periodic/heavy_pipeline.rs`.
105
+ */
106
+ export interface WalletGradeChangedData {
107
+ wallet: string;
108
+ trader_id: number;
109
+ old_grade: string;
110
+ new_grade: string;
111
+ /** Direction of the grade move, e.g. "up" / "down". */
112
+ direction: string;
113
+ skill_index: number;
114
+ final_score: number;
115
+ date: string;
116
+ }
117
+ /**
118
+ * `insider_radar_flag_raised` data (Pro-only): a suspicion threshold was
119
+ * crossed on a fresh fill. Source: `backend/src/polymarket_rtds_ingest/flushing.rs`.
120
+ */
121
+ export interface InsiderRadarFlagRaisedData {
122
+ trade_id: string;
123
+ wallet: string;
124
+ trader_id: number;
125
+ condition_id: string;
126
+ suspicion_score: number;
127
+ track: string;
128
+ /** Outcome side the flagged fill took. */
129
+ side: string;
130
+ size: number;
131
+ price: number;
132
+ }
133
+ /**
134
+ * `smart_money_flow_detected` payload (Pro-only). Fires when a scheduled
135
+ * scanner detects ranked-trader net flow crossing a threshold on a market.
136
+ */
137
+ export interface SmartMoneyFlowDetectedData {
138
+ condition_id: string;
139
+ /** Signed net YES/NO exposure from ranked traders. */
140
+ net_flow_usd: number;
141
+ abs_net_flow_usd: number;
142
+ /** Side the net flow leans: "yes" or "no". */
143
+ dominant_side: string;
144
+ /** Minimum trader grade included: "S", "A", "B", "C", "D", or "F". */
145
+ grade_floor: string;
146
+ whale_trade_count: number;
147
+ /** Detection window, e.g. "24h". */
148
+ window: string;
149
+ }
150
+ export type WhaleTradesInsertedEvent = WebhookEventEnvelope<"whale_trades_inserted", WhaleTradesInsertedData>;
151
+ export type WhaleTraderSyncedEvent = WebhookEventEnvelope<"whale_trader_synced", WhaleTraderSyncedData>;
152
+ export type LargePositionsUpdatedEvent = WebhookEventEnvelope<"large_positions_updated", LargePositionsUpdatedData>;
153
+ export type WalletGradeChangedEvent = WebhookEventEnvelope<"wallet_grade_changed", WalletGradeChangedData>;
154
+ export type InsiderRadarFlagRaisedEvent = WebhookEventEnvelope<"insider_radar_flag_raised", InsiderRadarFlagRaisedData>;
155
+ export type SmartMoneyFlowDetectedEvent = WebhookEventEnvelope<"smart_money_flow_detected", SmartMoneyFlowDetectedData>;
156
+ /** Discriminated union over every typed webhook delivery payload. */
157
+ export type WebhookEvent = WhaleTradesInsertedEvent | WhaleTraderSyncedEvent | LargePositionsUpdatedEvent | WalletGradeChangedEvent | InsiderRadarFlagRaisedEvent | SmartMoneyFlowDetectedEvent | WebhookEventEnvelope<"live_sports_updated", unknown>;
158
+ /**
159
+ * Parse a verified raw body into the typed delivery envelope. Call only AFTER
160
+ * `verifySignature` passes (parsing first would let an unverified payload in).
161
+ */
162
+ export declare function parseWebhookEvent(body: string): WebhookEvent;
163
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../sdk/src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,uEAAuE;AACvE,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AACxD,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,IAAI,EAAE,MAAM,GAAG,UAAU,GACxB,MAAM,CAMR;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CA8BpE;AAcD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,YAC9B,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,EACtB,2BAA2B,EAI3B,2BAA2B,CACnB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CACnC,KAAK,SAAS,gBAAgB,GAAG,gBAAgB,EACjD,KAAK,GAAG,OAAO;IAEf,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,KAAK,CAAC;IACZ,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,KAAK,CAAC;CACb;AAED,yFAAyF;AACzF,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sCAAsC;AACtC,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,CACzD,uBAAuB,EACvB,uBAAuB,CACxB,CAAC;AACF,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CACvD,qBAAqB,EACrB,qBAAqB,CACtB,CAAC;AACF,MAAM,MAAM,0BAA0B,GAAG,oBAAoB,CAC3D,yBAAyB,EACzB,yBAAyB,CAC1B,CAAC;AACF,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,CACxD,sBAAsB,EACtB,sBAAsB,CACvB,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAC5D,2BAA2B,EAC3B,0BAA0B,CAC3B,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAC5D,2BAA2B,EAC3B,0BAA0B,CAC3B,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,YAAY,GACpB,wBAAwB,GACxB,sBAAsB,GACtB,0BAA0B,GAC1B,uBAAuB,GACvB,2BAA2B,GAC3B,2BAA2B,GAC3B,oBAAoB,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAE5D"}