@livo-build/runtime 0.2.4 → 0.2.5
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/dist/eip712.d.ts +45 -0
- package/dist/eip712.js +160 -0
- package/dist/eip712.test.d.ts +1 -0
- package/dist/eip712.test.js +106 -0
- package/dist/hyperliquid.d.ts +360 -0
- package/dist/hyperliquid.js +350 -0
- package/dist/hyperliquid.test.d.ts +1 -0
- package/dist/hyperliquid.test.js +31 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +11 -0
- package/dist/polymarket.d.ts +146 -0
- package/dist/polymarket.js +495 -0
- package/dist/polymarket.test.d.ts +1 -0
- package/dist/polymarket.test.js +111 -0
- package/dist/watcher.d.ts +83 -0
- package/dist/watcher.js +155 -0
- package/package.json +2 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
interface MinimalEnv {
|
|
2
|
+
[key: string]: unknown;
|
|
3
|
+
}
|
|
4
|
+
/** Bindings the platform injects into a deployed watcher Worker. */
|
|
5
|
+
export interface WatcherEnv extends MinimalEnv {
|
|
6
|
+
LIVO_WATCHER_DISPATCH_TOKEN?: string;
|
|
7
|
+
LIVO_SIGNAL_SUBSCRIBE_URL?: string;
|
|
8
|
+
LIVO_SIGNAL_CANCEL_URL?: string;
|
|
9
|
+
LIVO_WATCHER_URL?: string;
|
|
10
|
+
LIVO_PROJECT_ID?: string;
|
|
11
|
+
LIVO_WATCHER_NAME?: string;
|
|
12
|
+
LIVO_CHAIN_ID?: string;
|
|
13
|
+
LIVO_RUN_URL?: string;
|
|
14
|
+
LIVO_RUN_TOKEN?: string;
|
|
15
|
+
}
|
|
16
|
+
export type Confidence = "high" | "medium" | "low" | "none";
|
|
17
|
+
export type MatchStatus = "confirmed" | "reverted";
|
|
18
|
+
/** A delivered match (the engine-emitted, priced event that fired a signal). */
|
|
19
|
+
export interface WatcherMatch {
|
|
20
|
+
deliveryKey: string;
|
|
21
|
+
matchKey: string;
|
|
22
|
+
status: MatchStatus;
|
|
23
|
+
signalId: string;
|
|
24
|
+
subId: number;
|
|
25
|
+
watcherId: number;
|
|
26
|
+
market: {
|
|
27
|
+
base: string;
|
|
28
|
+
quote: string;
|
|
29
|
+
pool: string;
|
|
30
|
+
};
|
|
31
|
+
event: {
|
|
32
|
+
actor: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
ts: number;
|
|
35
|
+
amountUsdMicros: number | null;
|
|
36
|
+
tokenUsdMicros: number | null;
|
|
37
|
+
confidence: Confidence;
|
|
38
|
+
deviationBps: number | null;
|
|
39
|
+
eventId: {
|
|
40
|
+
blockHash: string;
|
|
41
|
+
txHash: string;
|
|
42
|
+
logIndex: number;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** A subscription scope (token / watchlist / actor / global). */
|
|
47
|
+
export interface WatcherScope {
|
|
48
|
+
kind: "token" | "watchlist" | "actor" | "global";
|
|
49
|
+
market?: string;
|
|
50
|
+
markets?: string[];
|
|
51
|
+
actor?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface SubscribeOptions {
|
|
54
|
+
/** Stable id for this subscription (and self-cancel handle). */
|
|
55
|
+
subKey: string;
|
|
56
|
+
signalId: string;
|
|
57
|
+
scope: WatcherScope;
|
|
58
|
+
params?: Record<string, unknown>;
|
|
59
|
+
/** Where matches deliver. Defaults to this watcher's own onMatch URL. */
|
|
60
|
+
dispatchUrl?: string;
|
|
61
|
+
chain?: number;
|
|
62
|
+
/** Time-sensitive (stop-loss/take-profit) — armed immediately. Default true. */
|
|
63
|
+
ephemeral?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Context passed to onMatch: env + runtime subscribe/cancel (exit-triggers). */
|
|
66
|
+
export interface WatcherContext {
|
|
67
|
+
env: WatcherEnv;
|
|
68
|
+
/** Arm a new subscription now (e.g. a stop-loss at fill time). */
|
|
69
|
+
subscribe(opts: SubscribeOptions): Promise<boolean>;
|
|
70
|
+
/** Remove a subscription by its subKey (self-cancel on fire). */
|
|
71
|
+
cancel(subKey: string): Promise<boolean>;
|
|
72
|
+
}
|
|
73
|
+
export interface WatcherDefinition {
|
|
74
|
+
/** The signal this watcher reacts to (informational; the platform routes). */
|
|
75
|
+
signal?: string;
|
|
76
|
+
onMatch: (match: WatcherMatch, ctx: WatcherContext) => Promise<void> | void;
|
|
77
|
+
}
|
|
78
|
+
/** A deployable Worker module ({ fetch }) — the shape Cloudflare invokes. */
|
|
79
|
+
export interface WatcherModule {
|
|
80
|
+
fetch(req: Request, env: WatcherEnv, ctx?: unknown): Promise<Response>;
|
|
81
|
+
}
|
|
82
|
+
export declare function defineWatcher(def: WatcherDefinition): WatcherModule;
|
|
83
|
+
export {};
|
package/dist/watcher.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// defineWatcher — the watcher-facing primitive for Signal Radar (the onMatch
|
|
2
|
+
// counterpart to keepers' scheduled()). A watcher is an HTTP-triggered Worker:
|
|
3
|
+
// the platform delivers ONE subscription-addressed match per request to its
|
|
4
|
+
// onMatch handler (SIGNAL-RADAR-IMPL-SPEC.md §10–§11).
|
|
5
|
+
//
|
|
6
|
+
// Auth mirrors the rest of the runtime (bearer tokens, not hand-rolled crypto):
|
|
7
|
+
// each delivery carries the watcher's dispatchToken as a bearer, injected at
|
|
8
|
+
// deploy as LIVO_WATCHER_DISPATCH_TOKEN. The same token authenticates this
|
|
9
|
+
// watcher's ctx.subscribe()/cancel() calls. (HMAC-signing the body is a later
|
|
10
|
+
// hardening step; the bearer scopes a leak to this one watcher today.)
|
|
11
|
+
export function defineWatcher(def) {
|
|
12
|
+
return {
|
|
13
|
+
async fetch(req, env, _ctx) {
|
|
14
|
+
if (req.method !== "POST")
|
|
15
|
+
return jsonResponse({ error: "method_not_allowed" }, 405);
|
|
16
|
+
// Per-watcher dispatch auth. Fail closed if no token is configured.
|
|
17
|
+
const expected = env.LIVO_WATCHER_DISPATCH_TOKEN;
|
|
18
|
+
if (!expected || bearer(req) !== expected) {
|
|
19
|
+
return jsonResponse({ error: "unauthorized" }, 401);
|
|
20
|
+
}
|
|
21
|
+
let match;
|
|
22
|
+
try {
|
|
23
|
+
match = parseMatch(await req.json());
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return jsonResponse({ error: "bad_request" }, 400);
|
|
27
|
+
}
|
|
28
|
+
const ctx = makeContext(env);
|
|
29
|
+
const startedAt = Date.now();
|
|
30
|
+
try {
|
|
31
|
+
await def.onMatch(match, ctx);
|
|
32
|
+
await reportRun(env, startedAt, true);
|
|
33
|
+
return jsonResponse({ ok: true }, 200);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
await reportRun(env, startedAt, false, String(e));
|
|
37
|
+
return jsonResponse({ ok: false }, 500);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Internals.
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function makeContext(env) {
|
|
46
|
+
const token = env.LIVO_WATCHER_DISPATCH_TOKEN ?? "";
|
|
47
|
+
return {
|
|
48
|
+
env,
|
|
49
|
+
async subscribe(opts) {
|
|
50
|
+
const url = env.LIVO_SIGNAL_SUBSCRIBE_URL;
|
|
51
|
+
if (!url)
|
|
52
|
+
return false;
|
|
53
|
+
const res = await fetch(url, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
projectId: env.LIVO_PROJECT_ID,
|
|
58
|
+
watcherName: env.LIVO_WATCHER_NAME,
|
|
59
|
+
chain: opts.chain ?? Number(env.LIVO_CHAIN_ID ?? "1"),
|
|
60
|
+
signalId: opts.signalId,
|
|
61
|
+
scope: opts.scope,
|
|
62
|
+
params: opts.params ?? {},
|
|
63
|
+
dispatchUrl: opts.dispatchUrl ?? env.LIVO_WATCHER_URL ?? "",
|
|
64
|
+
subKey: opts.subKey,
|
|
65
|
+
ephemeral: opts.ephemeral ?? true,
|
|
66
|
+
}),
|
|
67
|
+
}).catch(() => undefined);
|
|
68
|
+
return Boolean(res && res.ok);
|
|
69
|
+
},
|
|
70
|
+
async cancel(subKey) {
|
|
71
|
+
const url = env.LIVO_SIGNAL_CANCEL_URL;
|
|
72
|
+
if (!url)
|
|
73
|
+
return false;
|
|
74
|
+
const res = await fetch(url, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
77
|
+
body: JSON.stringify({ subKey }),
|
|
78
|
+
}).catch(() => undefined);
|
|
79
|
+
return Boolean(res && res.ok);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function bearer(req) {
|
|
84
|
+
const h = req.headers.get("authorization") ?? req.headers.get("Authorization");
|
|
85
|
+
if (!h)
|
|
86
|
+
return null;
|
|
87
|
+
const m = h.match(/^Bearer\s+(.+)$/i);
|
|
88
|
+
return m ? m[1] : null;
|
|
89
|
+
}
|
|
90
|
+
function jsonResponse(body, status) {
|
|
91
|
+
return new Response(JSON.stringify(body), {
|
|
92
|
+
status,
|
|
93
|
+
headers: { "content-type": "application/json" },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Parse the platform's wire envelope (snake_case JSON) into a typed match. */
|
|
97
|
+
function parseMatch(raw) {
|
|
98
|
+
const b = raw;
|
|
99
|
+
const ev = (b.event ?? {});
|
|
100
|
+
const eid = (ev.event_id ?? {});
|
|
101
|
+
if (typeof b.delivery_key !== "string" || typeof b.match_key !== "string") {
|
|
102
|
+
throw new Error("malformed match envelope");
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
deliveryKey: b.delivery_key,
|
|
106
|
+
matchKey: b.match_key,
|
|
107
|
+
status: b.status === "reverted" ? "reverted" : "confirmed",
|
|
108
|
+
signalId: String(b.signal_id ?? ""),
|
|
109
|
+
subId: Number(b.sub_id ?? 0),
|
|
110
|
+
watcherId: Number(b.watcher_id ?? 0),
|
|
111
|
+
market: {
|
|
112
|
+
base: String(b.market?.base ?? ""),
|
|
113
|
+
quote: String(b.market?.quote ?? ""),
|
|
114
|
+
pool: String(b.market?.pool ?? ""),
|
|
115
|
+
},
|
|
116
|
+
event: {
|
|
117
|
+
actor: String(ev.actor ?? ""),
|
|
118
|
+
kind: String(ev.kind ?? ""),
|
|
119
|
+
ts: Number(ev.ts ?? 0),
|
|
120
|
+
amountUsdMicros: numOrNull(ev.amount_usd_micros),
|
|
121
|
+
tokenUsdMicros: numOrNull(ev.token_usd_micros),
|
|
122
|
+
confidence: ev.confidence ?? "none",
|
|
123
|
+
deviationBps: numOrNull(ev.deviation_bps),
|
|
124
|
+
eventId: {
|
|
125
|
+
blockHash: String(eid.block_hash ?? ""),
|
|
126
|
+
txHash: String(eid.tx_hash ?? ""),
|
|
127
|
+
logIndex: Number(eid.log_index ?? 0),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function numOrNull(v) {
|
|
133
|
+
return typeof v === "number" ? v : null;
|
|
134
|
+
}
|
|
135
|
+
/** Best-effort run-log report (mirrors the keeper wrapper). Never throws. */
|
|
136
|
+
async function reportRun(env, startedAt, ok, error) {
|
|
137
|
+
const url = env.LIVO_RUN_URL;
|
|
138
|
+
const token = env.LIVO_RUN_TOKEN;
|
|
139
|
+
if (!url || !token)
|
|
140
|
+
return;
|
|
141
|
+
await fetch(url, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
projectId: env.LIVO_PROJECT_ID,
|
|
146
|
+
ownerKind: "watcher",
|
|
147
|
+
ownerName: env.LIVO_WATCHER_NAME,
|
|
148
|
+
trigger: "match",
|
|
149
|
+
startedAt,
|
|
150
|
+
durationMs: Date.now() - startedAt,
|
|
151
|
+
ok,
|
|
152
|
+
error,
|
|
153
|
+
}),
|
|
154
|
+
}).catch(() => undefined);
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livo-build/runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Livo runtime — chain signing/reads, D1 state, and logging for keepers, servers, and bots.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"clean": "rm -rf dist"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@nktkas/hyperliquid": "^0.33.0",
|
|
38
39
|
"@noble/curves": "^1.9.7",
|
|
39
40
|
"@noble/hashes": "^1.8.0"
|
|
40
41
|
},
|