@livo-build/runtime 0.2.5 → 0.2.12
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/aes.d.ts +4 -0
- package/dist/aes.js +38 -0
- package/dist/auth.test.d.ts +1 -0
- package/dist/auth.test.js +57 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +27 -0
- package/dist/gate.d.ts +10 -0
- package/dist/gate.js +18 -0
- package/dist/http.d.ts +48 -0
- package/dist/http.js +133 -0
- package/dist/hyperliquid.d.ts +269 -0
- package/dist/hyperliquid.js +194 -0
- package/dist/hyperliquid.test.js +17 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +16 -0
- package/dist/nft.d.ts +33 -0
- package/dist/nft.js +72 -0
- package/dist/nft.test.d.ts +1 -0
- package/dist/nft.test.js +44 -0
- package/dist/polymarket.d.ts +12 -0
- package/dist/polymarket.js +19 -6
- package/dist/polymarket.test.js +10 -0
- package/dist/ratelimit.d.ts +19 -0
- package/dist/ratelimit.js +11 -0
- package/dist/reads.d.ts +12 -0
- package/dist/reads.js +36 -0
- package/dist/sessions.d.ts +8 -0
- package/dist/sessions.js +60 -0
- package/dist/signals.d.ts +131 -0
- package/dist/signals.js +146 -0
- package/dist/siwe.d.ts +24 -0
- package/dist/siwe.js +33 -0
- package/dist/sse.test.d.ts +1 -0
- package/dist/sse.test.js +28 -0
- package/dist/telegram.d.ts +31 -0
- package/dist/telegram.js +73 -0
- package/dist/telegramAuth.d.ts +16 -0
- package/dist/telegramAuth.js +108 -0
- package/dist/telegramAuth.test.d.ts +1 -0
- package/dist/telegramAuth.test.js +68 -0
- package/dist/telegramLinks.d.ts +27 -0
- package/dist/telegramLinks.js +78 -0
- package/dist/webhook.d.ts +18 -0
- package/dist/webhook.js +49 -0
- package/dist/webhook.test.d.ts +1 -0
- package/dist/webhook.test.js +46 -0
- package/package.json +14 -4
package/dist/aes.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Encrypt a string → "ivBase64:ciphertextBase64". */
|
|
2
|
+
export declare function encrypt(plaintext: string, secret: string): Promise<string>;
|
|
3
|
+
/** Decrypt a value produced by encrypt(). Throws if tampered / wrong secret. */
|
|
4
|
+
export declare function decrypt(value: string, secret: string): Promise<string>;
|
package/dist/aes.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// AES-256-GCM encrypt/decrypt for protecting sensitive values at rest in D1/KV
|
|
2
|
+
// (e.g. per-user data). The key is a passphrase/secret; output is base64 "iv:ct".
|
|
3
|
+
// Web Crypto only (Worker-safe).
|
|
4
|
+
const enc = new TextEncoder();
|
|
5
|
+
const dec = new TextDecoder();
|
|
6
|
+
function b64(bytes) {
|
|
7
|
+
let bin = "";
|
|
8
|
+
for (let i = 0; i < bytes.length; i++)
|
|
9
|
+
bin += String.fromCharCode(bytes[i]);
|
|
10
|
+
return btoa(bin);
|
|
11
|
+
}
|
|
12
|
+
function unb64(s) {
|
|
13
|
+
const bin = atob(s);
|
|
14
|
+
const out = new Uint8Array(bin.length);
|
|
15
|
+
for (let i = 0; i < bin.length; i++)
|
|
16
|
+
out[i] = bin.charCodeAt(i);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
async function deriveKey(secret) {
|
|
20
|
+
const hash = await crypto.subtle.digest("SHA-256", enc.encode(secret));
|
|
21
|
+
return crypto.subtle.importKey("raw", hash, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
22
|
+
}
|
|
23
|
+
/** Encrypt a string → "ivBase64:ciphertextBase64". */
|
|
24
|
+
export async function encrypt(plaintext, secret) {
|
|
25
|
+
const key = await deriveKey(secret);
|
|
26
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
27
|
+
const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, enc.encode(plaintext)));
|
|
28
|
+
return `${b64(iv)}:${b64(ct)}`;
|
|
29
|
+
}
|
|
30
|
+
/** Decrypt a value produced by encrypt(). Throws if tampered / wrong secret. */
|
|
31
|
+
export async function decrypt(value, secret) {
|
|
32
|
+
const [ivB64, ctB64] = value.split(":");
|
|
33
|
+
if (!ivB64 || !ctB64)
|
|
34
|
+
throw new Error("decrypt: malformed ciphertext");
|
|
35
|
+
const key = await deriveKey(secret);
|
|
36
|
+
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: unb64(ivB64) }, key, unb64(ctB64));
|
|
37
|
+
return dec.decode(pt);
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3
|
+
import { createSiweMessage, verifySiweMessage } from "./siwe.js";
|
|
4
|
+
import { createSession, verifySession } from "./sessions.js";
|
|
5
|
+
import { encrypt, decrypt } from "./aes.js";
|
|
6
|
+
import { Router, json } from "./http.js";
|
|
7
|
+
const PK = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
|
8
|
+
const account = privateKeyToAccount(PK);
|
|
9
|
+
const ADDR = account.address;
|
|
10
|
+
describe("SIWE", () => {
|
|
11
|
+
it("builds a canonical message and verifies a real signature (cross-checked vs viem)", async () => {
|
|
12
|
+
const msg = createSiweMessage({ domain: "app.livo.build", address: ADDR, uri: "https://app.livo.build", chainId: 11155111, nonce: "abc123" });
|
|
13
|
+
expect(msg).toContain("app.livo.build wants you to sign in");
|
|
14
|
+
expect(msg).toContain("Nonce: abc123");
|
|
15
|
+
const signature = await account.signMessage({ message: msg });
|
|
16
|
+
const r = await verifySiweMessage(msg, signature, { nonce: "abc123", domain: "app.livo.build" });
|
|
17
|
+
expect(r.valid).toBe(true);
|
|
18
|
+
expect(r.address?.toLowerCase()).toBe(ADDR.toLowerCase());
|
|
19
|
+
});
|
|
20
|
+
it("rejects a wrong nonce and an expired message", async () => {
|
|
21
|
+
const good = createSiweMessage({ domain: "d", address: ADDR, uri: "u", chainId: 1, nonce: "n1" });
|
|
22
|
+
const sig = await account.signMessage({ message: good });
|
|
23
|
+
expect((await verifySiweMessage(good, sig, { nonce: "n2" })).valid).toBe(false);
|
|
24
|
+
const expired = createSiweMessage({ domain: "d", address: ADDR, uri: "u", chainId: 1, nonce: "n1", expirationTime: new Date(Date.now() - 1000).toISOString() });
|
|
25
|
+
const sig2 = await account.signMessage({ message: expired });
|
|
26
|
+
expect((await verifySiweMessage(expired, sig2)).valid).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("sessions", () => {
|
|
30
|
+
it("round-trips a payload and rejects tampering / expiry", async () => {
|
|
31
|
+
const token = await createSession({ sub: "0xabc", role: "member" }, "secret");
|
|
32
|
+
const p = await verifySession(token, "secret");
|
|
33
|
+
expect(p?.sub).toBe("0xabc");
|
|
34
|
+
expect(await verifySession(token, "wrong-secret")).toBeNull();
|
|
35
|
+
expect(await verifySession(token + "x", "secret")).toBeNull();
|
|
36
|
+
const expired = await createSession({ sub: "x" }, "s", { ttlSeconds: -1 });
|
|
37
|
+
expect(await verifySession(expired, "s")).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe("aes", () => {
|
|
41
|
+
it("encrypts and decrypts; wrong secret throws", async () => {
|
|
42
|
+
const ct = await encrypt("hello world", "k1");
|
|
43
|
+
expect(ct).not.toContain("hello");
|
|
44
|
+
expect(await decrypt(ct, "k1")).toBe("hello world");
|
|
45
|
+
await expect(decrypt(ct, "k2")).rejects.toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("Router", () => {
|
|
49
|
+
it("matches method + :params and 404s otherwise", async () => {
|
|
50
|
+
const r = new Router("/api").get("/u/:id", (_req, { params }) => json({ id: params.id }));
|
|
51
|
+
const res = await r.handle(new Request("https://x/api/u/42"), {});
|
|
52
|
+
expect(res.status).toBe(200);
|
|
53
|
+
expect(await res.json()).toEqual({ id: "42" });
|
|
54
|
+
const miss = await r.handle(new Request("https://x/api/nope"), {});
|
|
55
|
+
expect(miss.status).toBe(404);
|
|
56
|
+
});
|
|
57
|
+
});
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KvLike } from "./ratelimit.js";
|
|
2
|
+
export interface CacheOptions {
|
|
3
|
+
/** Time-to-live in seconds. */
|
|
4
|
+
ttlSeconds: number;
|
|
5
|
+
/** Optional key namespace (default "cache:"). */
|
|
6
|
+
prefix?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Return the cached JSON value for `key`, or run `produce()`, store it with a TTL,
|
|
10
|
+
* and return it. A miss or a corrupt cached value falls through to `produce()`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function cached<T>(kv: KvLike, key: string, produce: () => Promise<T>, opts: CacheOptions): Promise<T>;
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// KV-backed memoize — cache an expensive computation (an RPC read, an upstream API
|
|
2
|
+
// call) under a key with a TTL, so repeat requests are cheap. Pairs with any
|
|
3
|
+
// Workers-KV-shaped binding.
|
|
4
|
+
/**
|
|
5
|
+
* Return the cached JSON value for `key`, or run `produce()`, store it with a TTL,
|
|
6
|
+
* and return it. A miss or a corrupt cached value falls through to `produce()`.
|
|
7
|
+
*/
|
|
8
|
+
export async function cached(kv, key, produce, opts) {
|
|
9
|
+
const k = (opts.prefix ?? "cache:") + key;
|
|
10
|
+
const hit = await kv.get(k);
|
|
11
|
+
if (hit != null) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(hit);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
/* corrupt entry — recompute */
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const value = await produce();
|
|
20
|
+
try {
|
|
21
|
+
await kv.put(k, JSON.stringify(value), { expirationTtl: opts.ttlSeconds });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* cache write best-effort */
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Chain } from "./chain.js";
|
|
2
|
+
export declare function tokenBalanceOf(chain: Chain, token: string, owner: string): Promise<bigint>;
|
|
3
|
+
export interface TokenGate {
|
|
4
|
+
/** ERC-20 / ERC-721 token address. */
|
|
5
|
+
token: string;
|
|
6
|
+
/** Minimum balance (base units for ERC-20; token count for ERC-721). */
|
|
7
|
+
minBalance: bigint;
|
|
8
|
+
}
|
|
9
|
+
/** Whether an owner currently meets a token gate. */
|
|
10
|
+
export declare function meetsGate(chain: Chain, owner: string, gate: TokenGate): Promise<boolean>;
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Token-gating helpers — does a wallet hold enough of a token to enter / stay in a
|
|
2
|
+
// gated room? Works for ERC-20 (balance) and ERC-721 (count) since both expose
|
|
3
|
+
// balanceOf(address). Read-only — a bot/keeper Chain needs only an RPC_URL.
|
|
4
|
+
// The balanceOf(address)→uint256 of a token for an owner. Returns 0n on a failed
|
|
5
|
+
// read (treat as "doesn't hold") rather than throwing the whole keeper sweep.
|
|
6
|
+
export async function tokenBalanceOf(chain, token, owner) {
|
|
7
|
+
try {
|
|
8
|
+
const r = await chain.call(token, "balanceOf(address)(uint256)", [owner]);
|
|
9
|
+
return BigInt(r);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return 0n;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Whether an owner currently meets a token gate. */
|
|
16
|
+
export async function meetsGate(chain, owner, gate) {
|
|
17
|
+
return (await tokenBalanceOf(chain, gate.token, owner)) >= gate.minBalance;
|
|
18
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
|
|
2
|
+
export declare function error(message: string, status?: number): Response;
|
|
3
|
+
export declare const CORS_HEADERS: Record<string, string>;
|
|
4
|
+
/** Add permissive CORS headers to a response. (Same-origin api/ usually doesn't need this.) */
|
|
5
|
+
export declare function withCors(res: Response): Response;
|
|
6
|
+
export interface SseConnection {
|
|
7
|
+
/** Push an event. Non-string `data` is JSON-stringified. */
|
|
8
|
+
send(data: unknown, opts?: {
|
|
9
|
+
event?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
}): void;
|
|
12
|
+
/** Send a comment line to keep the connection alive (heartbeat). */
|
|
13
|
+
ping(): void;
|
|
14
|
+
/** End the stream. */
|
|
15
|
+
close(): void;
|
|
16
|
+
/** True once the stream has been closed or cancelled by the client. */
|
|
17
|
+
readonly closed: boolean;
|
|
18
|
+
/** The Response to return from your fetch handler. */
|
|
19
|
+
readonly response: Response;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Open a Server-Sent Events stream from an api/ Worker — push realtime updates to a
|
|
23
|
+
* browser (e.g. live prices feeding a chart/dashboard) with no WebSocket plumbing.
|
|
24
|
+
* Return `conn.response` from your handler, then `conn.send(...)` as data arrives.
|
|
25
|
+
* Pass `{ signal: req.signal }` so it tears down when the client disconnects.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sse(init?: {
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}): SseConnection;
|
|
31
|
+
export interface RouteContext<Env = unknown> {
|
|
32
|
+
params: Record<string, string>;
|
|
33
|
+
url: URL;
|
|
34
|
+
env: Env;
|
|
35
|
+
}
|
|
36
|
+
export type RouteHandler<Env = unknown> = (req: Request, ctx: RouteContext<Env>) => Response | Promise<Response>;
|
|
37
|
+
export declare class Router<Env = unknown> {
|
|
38
|
+
private readonly basePath;
|
|
39
|
+
private routes;
|
|
40
|
+
constructor(basePath?: string);
|
|
41
|
+
private add;
|
|
42
|
+
get(path: string, h: RouteHandler<Env>): this;
|
|
43
|
+
post(path: string, h: RouteHandler<Env>): this;
|
|
44
|
+
put(path: string, h: RouteHandler<Env>): this;
|
|
45
|
+
delete(path: string, h: RouteHandler<Env>): this;
|
|
46
|
+
/** Match + dispatch a request. Returns the handler's response, or a 404. */
|
|
47
|
+
handle(req: Request, env: Env): Promise<Response>;
|
|
48
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Tiny HTTP helpers for api/ Workers, bots, and servers — stop hand-rolling JSON
|
|
2
|
+
// responses and request routing.
|
|
3
|
+
export function json(body, status = 200, headers = {}) {
|
|
4
|
+
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", ...headers } });
|
|
5
|
+
}
|
|
6
|
+
export function error(message, status = 400) {
|
|
7
|
+
return json({ error: message }, status);
|
|
8
|
+
}
|
|
9
|
+
export const CORS_HEADERS = {
|
|
10
|
+
"access-control-allow-origin": "*",
|
|
11
|
+
"access-control-allow-methods": "GET,POST,PUT,DELETE,OPTIONS",
|
|
12
|
+
"access-control-allow-headers": "content-type,authorization",
|
|
13
|
+
};
|
|
14
|
+
/** Add permissive CORS headers to a response. (Same-origin api/ usually doesn't need this.) */
|
|
15
|
+
export function withCors(res) {
|
|
16
|
+
const h = new Headers(res.headers);
|
|
17
|
+
for (const [k, v] of Object.entries(CORS_HEADERS))
|
|
18
|
+
h.set(k, v);
|
|
19
|
+
return new Response(res.body, { status: res.status, headers: h });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Open a Server-Sent Events stream from an api/ Worker — push realtime updates to a
|
|
23
|
+
* browser (e.g. live prices feeding a chart/dashboard) with no WebSocket plumbing.
|
|
24
|
+
* Return `conn.response` from your handler, then `conn.send(...)` as data arrives.
|
|
25
|
+
* Pass `{ signal: req.signal }` so it tears down when the client disconnects.
|
|
26
|
+
*/
|
|
27
|
+
export function sse(init = {}) {
|
|
28
|
+
const encoder = new TextEncoder();
|
|
29
|
+
let controller = null;
|
|
30
|
+
let isClosed = false;
|
|
31
|
+
const stream = new ReadableStream({
|
|
32
|
+
start(c) {
|
|
33
|
+
controller = c;
|
|
34
|
+
},
|
|
35
|
+
cancel() {
|
|
36
|
+
isClosed = true;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const write = (s) => {
|
|
40
|
+
if (isClosed || !controller)
|
|
41
|
+
return;
|
|
42
|
+
try {
|
|
43
|
+
controller.enqueue(encoder.encode(s));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
isClosed = true;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const conn = {
|
|
50
|
+
send(data, opts) {
|
|
51
|
+
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
52
|
+
let frame = "";
|
|
53
|
+
if (opts?.event)
|
|
54
|
+
frame += `event: ${opts.event}\n`;
|
|
55
|
+
if (opts?.id)
|
|
56
|
+
frame += `id: ${opts.id}\n`;
|
|
57
|
+
for (const line of payload.split("\n"))
|
|
58
|
+
frame += `data: ${line}\n`; // SSE: one data: line per \n
|
|
59
|
+
write(frame + "\n");
|
|
60
|
+
},
|
|
61
|
+
ping() {
|
|
62
|
+
write(": ping\n\n");
|
|
63
|
+
},
|
|
64
|
+
close() {
|
|
65
|
+
if (isClosed)
|
|
66
|
+
return;
|
|
67
|
+
isClosed = true;
|
|
68
|
+
try {
|
|
69
|
+
controller?.close();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* already closed */
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
get closed() {
|
|
76
|
+
return isClosed;
|
|
77
|
+
},
|
|
78
|
+
response: new Response(stream, {
|
|
79
|
+
headers: {
|
|
80
|
+
"content-type": "text/event-stream",
|
|
81
|
+
"cache-control": "no-cache",
|
|
82
|
+
connection: "keep-alive",
|
|
83
|
+
...(init.headers ?? {}),
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
init.signal?.addEventListener("abort", () => conn.close());
|
|
88
|
+
return conn;
|
|
89
|
+
}
|
|
90
|
+
// A minimal method + path router with `:param` segments. `basePath` is stripped
|
|
91
|
+
// first (e.g. "/api/telegram"). Returns 404 when nothing matches.
|
|
92
|
+
export class Router {
|
|
93
|
+
basePath;
|
|
94
|
+
routes = [];
|
|
95
|
+
constructor(basePath = "") {
|
|
96
|
+
this.basePath = basePath;
|
|
97
|
+
}
|
|
98
|
+
add(method, path, handler) {
|
|
99
|
+
const keys = [];
|
|
100
|
+
const pattern = new RegExp("^" +
|
|
101
|
+
path.replace(/:[A-Za-z0-9_]+/g, (m) => {
|
|
102
|
+
keys.push(m.slice(1));
|
|
103
|
+
return "([^/]+)";
|
|
104
|
+
}) +
|
|
105
|
+
"/?$");
|
|
106
|
+
this.routes.push({ method, keys, pattern, handler });
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
get(path, h) { return this.add("GET", path, h); }
|
|
110
|
+
post(path, h) { return this.add("POST", path, h); }
|
|
111
|
+
put(path, h) { return this.add("PUT", path, h); }
|
|
112
|
+
delete(path, h) { return this.add("DELETE", path, h); }
|
|
113
|
+
/** Match + dispatch a request. Returns the handler's response, or a 404. */
|
|
114
|
+
async handle(req, env) {
|
|
115
|
+
const url = new URL(req.url);
|
|
116
|
+
let path = url.pathname;
|
|
117
|
+
if (this.basePath && path.startsWith(this.basePath))
|
|
118
|
+
path = path.slice(this.basePath.length) || "/";
|
|
119
|
+
if (req.method === "OPTIONS")
|
|
120
|
+
return withCors(new Response(null, { status: 204 }));
|
|
121
|
+
for (const r of this.routes) {
|
|
122
|
+
if (r.method !== req.method)
|
|
123
|
+
continue;
|
|
124
|
+
const m = r.pattern.exec(path);
|
|
125
|
+
if (!m)
|
|
126
|
+
continue;
|
|
127
|
+
const params = {};
|
|
128
|
+
r.keys.forEach((k, i) => (params[k] = decodeURIComponent(m[i + 1])));
|
|
129
|
+
return r.handler(req, { params, url, env });
|
|
130
|
+
}
|
|
131
|
+
return error("not_found", 404);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/hyperliquid.d.ts
CHANGED
|
@@ -49,6 +49,51 @@ export interface AccountBalance {
|
|
|
49
49
|
totalMarginUsed: number;
|
|
50
50
|
totalNtlPos: number;
|
|
51
51
|
}
|
|
52
|
+
/** A parsed open perp position. */
|
|
53
|
+
export interface PositionInfo {
|
|
54
|
+
coin: string;
|
|
55
|
+
/** Absolute size (always ≥ 0; see `side`). */
|
|
56
|
+
size: number;
|
|
57
|
+
side: "long" | "short" | "flat";
|
|
58
|
+
entryPx: number;
|
|
59
|
+
positionValue: number;
|
|
60
|
+
unrealizedPnl: number;
|
|
61
|
+
/** Return on equity as a fraction (0.12 = +12%). */
|
|
62
|
+
returnOnEquity: number;
|
|
63
|
+
leverage: number;
|
|
64
|
+
liquidationPx: number | null;
|
|
65
|
+
marginUsed: number;
|
|
66
|
+
}
|
|
67
|
+
/** Best bid / offer snapshot. */
|
|
68
|
+
export interface Bbo {
|
|
69
|
+
bid: number | null;
|
|
70
|
+
ask: number | null;
|
|
71
|
+
mid: number | null;
|
|
72
|
+
spread: number | null;
|
|
73
|
+
}
|
|
74
|
+
interface RawPosition {
|
|
75
|
+
coin: string;
|
|
76
|
+
szi: string;
|
|
77
|
+
entryPx?: string | null;
|
|
78
|
+
positionValue?: string;
|
|
79
|
+
unrealizedPnl?: string;
|
|
80
|
+
returnOnEquity?: string;
|
|
81
|
+
leverage?: {
|
|
82
|
+
value?: number;
|
|
83
|
+
};
|
|
84
|
+
liquidationPx?: string | null;
|
|
85
|
+
marginUsed?: string;
|
|
86
|
+
}
|
|
87
|
+
/** Parse a Hyperliquid clearinghouse position into a clean PositionInfo. */
|
|
88
|
+
export declare function parseHlPosition(p: RawPosition): PositionInfo;
|
|
89
|
+
/** Parse an L2 book into a best bid/offer + mid + spread. */
|
|
90
|
+
export declare function parseBbo(book: {
|
|
91
|
+
levels?: [Array<{
|
|
92
|
+
px: string;
|
|
93
|
+
}>, Array<{
|
|
94
|
+
px: string;
|
|
95
|
+
}>];
|
|
96
|
+
}): Bbo;
|
|
52
97
|
export interface PlaceOrderOptions {
|
|
53
98
|
/** Reduce-only (close, never flip). Default false. */
|
|
54
99
|
reduceOnly?: boolean;
|
|
@@ -133,6 +178,32 @@ export declare class Hyperliquid {
|
|
|
133
178
|
assetCtx(coin: string): Promise<AssetContext>;
|
|
134
179
|
/** OHLCV candles for charting. `interval` default "1h"; window defaults to the last 24h. */
|
|
135
180
|
candles(coin: string, interval?: CandleInterval, opts?: CandlesOptions): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").CandleSnapshotResponse>;
|
|
181
|
+
/** Recent public trades for a coin. */
|
|
182
|
+
recentTrades(coin: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").RecentTradesResponse>;
|
|
183
|
+
/** Best bid/offer + mid + spread for a coin (parsed from the L2 book). */
|
|
184
|
+
bbo(coin: string): Promise<Bbo>;
|
|
185
|
+
/** All open positions as clean PositionInfo[] (default: the signer's account). */
|
|
186
|
+
positionsList(user?: string): Promise<PositionInfo[]>;
|
|
187
|
+
/** A single open position for a coin, or null if flat. */
|
|
188
|
+
position(coin: string, user?: string): Promise<PositionInfo | null>;
|
|
189
|
+
/** Current hourly funding rate for a coin (fraction, e.g. 0.0000125). */
|
|
190
|
+
fundingRate(coin: string): Promise<number>;
|
|
191
|
+
/** Historical funding rates for a coin (default: the last 7 days). */
|
|
192
|
+
fundingHistory(coin: string, opts?: {
|
|
193
|
+
startTime?: number;
|
|
194
|
+
endTime?: number;
|
|
195
|
+
lookbackMs?: number;
|
|
196
|
+
}): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").FundingHistoryResponse>;
|
|
197
|
+
/** Funding payments paid/received by an account (default signer; last 7 days). */
|
|
198
|
+
userFunding(opts?: {
|
|
199
|
+
startTime?: number;
|
|
200
|
+
endTime?: number;
|
|
201
|
+
lookbackMs?: number;
|
|
202
|
+
}, user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").UserFundingResponse>;
|
|
203
|
+
/** Predicted next funding rates across venues (no key). */
|
|
204
|
+
predictedFundings(): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").PredictedFundingsResponse>;
|
|
205
|
+
/** Portfolio history (PnL/account-value time series) for an account (default: the signer). */
|
|
206
|
+
portfolio(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").PortfolioResponse>;
|
|
136
207
|
/** Raw order passthrough (the @nktkas `order` shape) for full control. */
|
|
137
208
|
order(params: Parameters<ExchangeClient["order"]>[0]): Promise<{
|
|
138
209
|
status: "ok";
|
|
@@ -333,6 +404,130 @@ export declare class Hyperliquid {
|
|
|
333
404
|
};
|
|
334
405
|
};
|
|
335
406
|
}>;
|
|
407
|
+
/** Cancel a resting order by its client order id (cloid). */
|
|
408
|
+
cancelByCloid(coin: string, cloid: Hex): Promise<{
|
|
409
|
+
status: "ok";
|
|
410
|
+
response: {
|
|
411
|
+
type: "cancel";
|
|
412
|
+
data: {
|
|
413
|
+
statuses: "success"[];
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
}>;
|
|
417
|
+
/** Cancel ALL resting orders (optionally only for one coin). Returns the count cancelled. */
|
|
418
|
+
cancelAll(coin?: string): Promise<{
|
|
419
|
+
cancelled: number;
|
|
420
|
+
}>;
|
|
421
|
+
/** Modify a resting limit order in place (new size/price/side). */
|
|
422
|
+
modifyOrder(oid: number, coin: string, isBuy: boolean, size: number, price: number, opts?: PlaceOrderOptions): Promise<{
|
|
423
|
+
status: "ok";
|
|
424
|
+
response: {
|
|
425
|
+
type: "default";
|
|
426
|
+
};
|
|
427
|
+
}>;
|
|
428
|
+
/**
|
|
429
|
+
* Place a trigger order (stop / take-profit). `triggerPx` is the activation price;
|
|
430
|
+
* `tpsl` is "sl" (stop) or "tp". Market trigger by default (fills at market once
|
|
431
|
+
* armed); reduce-only by default (closing). `isBuy` is the side that executes —
|
|
432
|
+
* to protect a LONG use isBuy=false (sell), for a SHORT use isBuy=true (buy).
|
|
433
|
+
*/
|
|
434
|
+
trigger(coin: string, isBuy: boolean, size: number, triggerPx: number, opts: {
|
|
435
|
+
tpsl: "tp" | "sl";
|
|
436
|
+
isMarket?: boolean;
|
|
437
|
+
reduceOnly?: boolean;
|
|
438
|
+
price?: number;
|
|
439
|
+
}): Promise<{
|
|
440
|
+
status: "ok";
|
|
441
|
+
response: {
|
|
442
|
+
type: "order";
|
|
443
|
+
data: {
|
|
444
|
+
statuses: ({
|
|
445
|
+
resting: {
|
|
446
|
+
oid: number;
|
|
447
|
+
cloid?: `0x${string}`;
|
|
448
|
+
};
|
|
449
|
+
} | {
|
|
450
|
+
filled: {
|
|
451
|
+
totalSz: string;
|
|
452
|
+
avgPx: string;
|
|
453
|
+
oid: number;
|
|
454
|
+
cloid?: `0x${string}`;
|
|
455
|
+
};
|
|
456
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
}>;
|
|
460
|
+
/** Stop-loss trigger (reduce-only market by default). See `trigger` for `isBuy`. */
|
|
461
|
+
stopLoss(coin: string, isBuy: boolean, size: number, triggerPx: number, opts?: {
|
|
462
|
+
isMarket?: boolean;
|
|
463
|
+
price?: number;
|
|
464
|
+
}): Promise<{
|
|
465
|
+
status: "ok";
|
|
466
|
+
response: {
|
|
467
|
+
type: "order";
|
|
468
|
+
data: {
|
|
469
|
+
statuses: ({
|
|
470
|
+
resting: {
|
|
471
|
+
oid: number;
|
|
472
|
+
cloid?: `0x${string}`;
|
|
473
|
+
};
|
|
474
|
+
} | {
|
|
475
|
+
filled: {
|
|
476
|
+
totalSz: string;
|
|
477
|
+
avgPx: string;
|
|
478
|
+
oid: number;
|
|
479
|
+
cloid?: `0x${string}`;
|
|
480
|
+
};
|
|
481
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
}>;
|
|
485
|
+
/** Take-profit trigger (reduce-only market by default). See `trigger` for `isBuy`. */
|
|
486
|
+
takeProfit(coin: string, isBuy: boolean, size: number, triggerPx: number, opts?: {
|
|
487
|
+
isMarket?: boolean;
|
|
488
|
+
price?: number;
|
|
489
|
+
}): Promise<{
|
|
490
|
+
status: "ok";
|
|
491
|
+
response: {
|
|
492
|
+
type: "order";
|
|
493
|
+
data: {
|
|
494
|
+
statuses: ({
|
|
495
|
+
resting: {
|
|
496
|
+
oid: number;
|
|
497
|
+
cloid?: `0x${string}`;
|
|
498
|
+
};
|
|
499
|
+
} | {
|
|
500
|
+
filled: {
|
|
501
|
+
totalSz: string;
|
|
502
|
+
avgPx: string;
|
|
503
|
+
oid: number;
|
|
504
|
+
cloid?: `0x${string}`;
|
|
505
|
+
};
|
|
506
|
+
} | "waitingForFill" | "waitingForTrigger")[];
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
}>;
|
|
510
|
+
/** Dead-man's switch: auto-cancel all orders at `timeMs` (epoch) unless re-armed. Omit to clear. */
|
|
511
|
+
scheduleCancel(timeMs?: number): Promise<{
|
|
512
|
+
status: "ok";
|
|
513
|
+
response: {
|
|
514
|
+
type: "default";
|
|
515
|
+
};
|
|
516
|
+
}>;
|
|
517
|
+
/** Move USDC from spot into the perp wallet (margin top-up; not a withdrawal). */
|
|
518
|
+
transferToPerp(usd: number): Promise<{
|
|
519
|
+
status: "ok";
|
|
520
|
+
response: {
|
|
521
|
+
type: "default";
|
|
522
|
+
};
|
|
523
|
+
}>;
|
|
524
|
+
/** Move USDC from the perp wallet back to spot. */
|
|
525
|
+
transferToSpot(usd: number): Promise<{
|
|
526
|
+
status: "ok";
|
|
527
|
+
response: {
|
|
528
|
+
type: "default";
|
|
529
|
+
};
|
|
530
|
+
}>;
|
|
336
531
|
/** Set leverage for a coin (cross by default). */
|
|
337
532
|
updateLeverage(coin: string, leverage: number, opts?: {
|
|
338
533
|
cross?: boolean;
|
|
@@ -342,6 +537,80 @@ export declare class Hyperliquid {
|
|
|
342
537
|
type: "default";
|
|
343
538
|
};
|
|
344
539
|
}>;
|
|
540
|
+
/**
|
|
541
|
+
* Place a TWAP order — slice `size` evenly over `minutes` (5–1440) to reduce impact.
|
|
542
|
+
* `randomize` jitters slice timing. Returns the @nktkas response (incl. the TWAP id).
|
|
543
|
+
*/
|
|
544
|
+
twap(coin: string, isBuy: boolean, size: number, minutes: number, opts?: {
|
|
545
|
+
reduceOnly?: boolean;
|
|
546
|
+
randomize?: boolean;
|
|
547
|
+
}): Promise<{
|
|
548
|
+
status: "ok";
|
|
549
|
+
response: {
|
|
550
|
+
type: "twapOrder";
|
|
551
|
+
data: {
|
|
552
|
+
status: {
|
|
553
|
+
running: {
|
|
554
|
+
twapId: number;
|
|
555
|
+
};
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
};
|
|
559
|
+
}>;
|
|
560
|
+
/** Cancel a running TWAP by its id (from the `twap()` response). */
|
|
561
|
+
cancelTwap(coin: string, twapId: number): Promise<{
|
|
562
|
+
status: "ok";
|
|
563
|
+
response: {
|
|
564
|
+
type: "twapCancel";
|
|
565
|
+
data: {
|
|
566
|
+
status: "success";
|
|
567
|
+
};
|
|
568
|
+
};
|
|
569
|
+
}>;
|
|
570
|
+
/** Create a named sub-account (1–16 chars). Returns its address. */
|
|
571
|
+
createSubAccount(name: string): Promise<{
|
|
572
|
+
status: "ok";
|
|
573
|
+
response: {
|
|
574
|
+
type: "createSubAccount";
|
|
575
|
+
data: `0x${string}`;
|
|
576
|
+
};
|
|
577
|
+
}>;
|
|
578
|
+
/** List sub-accounts for an address (default: the signer). */
|
|
579
|
+
subAccounts(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").SubAccountsResponse>;
|
|
580
|
+
/** Move USDC from the master into a sub-account (`usd` in dollars). */
|
|
581
|
+
transferToSubAccount(subAccount: string, usd: number): Promise<{
|
|
582
|
+
status: "ok";
|
|
583
|
+
response: {
|
|
584
|
+
type: "default";
|
|
585
|
+
};
|
|
586
|
+
}>;
|
|
587
|
+
/** Move USDC from a sub-account back to the master (`usd` in dollars). */
|
|
588
|
+
transferFromSubAccount(subAccount: string, usd: number): Promise<{
|
|
589
|
+
status: "ok";
|
|
590
|
+
response: {
|
|
591
|
+
type: "default";
|
|
592
|
+
};
|
|
593
|
+
}>;
|
|
594
|
+
/** Deposit USDC into a vault (`usd` in dollars). */
|
|
595
|
+
vaultDeposit(vault: string, usd: number): Promise<{
|
|
596
|
+
status: "ok";
|
|
597
|
+
response: {
|
|
598
|
+
type: "default";
|
|
599
|
+
};
|
|
600
|
+
}>;
|
|
601
|
+
/** Withdraw USDC from a vault (`usd` in dollars). */
|
|
602
|
+
vaultWithdraw(vault: string, usd: number): Promise<{
|
|
603
|
+
status: "ok";
|
|
604
|
+
response: {
|
|
605
|
+
type: "default";
|
|
606
|
+
};
|
|
607
|
+
}>;
|
|
608
|
+
/** Vault details + your equity in it (public read; pass a user or use the signer). */
|
|
609
|
+
vaultDetails(vault: string, user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").VaultDetailsResponse>;
|
|
610
|
+
/** Your fee schedule + 14-day volume (default: the signer). */
|
|
611
|
+
userFees(user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").UserFeesResponse>;
|
|
612
|
+
/** Status of a single order by its oid (resting/filled/canceled). */
|
|
613
|
+
orderStatus(oid: number, user?: string): Promise<import("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts").OrderStatusResponse>;
|
|
345
614
|
private symbols;
|
|
346
615
|
private lookup;
|
|
347
616
|
private requireUser;
|