@livo-build/kit 0.1.0 → 0.1.6

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 (134) hide show
  1. package/README.md +44 -4
  2. package/dist/account/Avatar.d.ts +9 -0
  3. package/dist/account/Avatar.js +15 -0
  4. package/dist/account/Identity.d.ts +9 -0
  5. package/dist/account/Identity.js +11 -0
  6. package/dist/account/account.css +3 -0
  7. package/dist/account/gates.d.ts +18 -0
  8. package/dist/account/gates.js +21 -0
  9. package/dist/account/index.d.ts +3 -0
  10. package/dist/account/index.js +3 -0
  11. package/dist/auth/SignInWithEthereum.d.ts +8 -0
  12. package/dist/auth/SignInWithEthereum.js +18 -0
  13. package/dist/auth/index.d.ts +2 -0
  14. package/dist/auth/index.js +2 -0
  15. package/dist/auth/useSiwe.d.ts +18 -0
  16. package/dist/auth/useSiwe.js +72 -0
  17. package/dist/contracts/index.d.ts +1 -0
  18. package/dist/contracts/index.js +1 -0
  19. package/dist/contracts/useContractEvent.d.ts +10 -0
  20. package/dist/contracts/useContractEvent.js +13 -0
  21. package/dist/data/Async.d.ts +17 -0
  22. package/dist/data/Async.js +22 -0
  23. package/dist/data/DataTable.d.ts +26 -0
  24. package/dist/data/DataTable.js +15 -0
  25. package/dist/data/data.css +22 -0
  26. package/dist/data/index.d.ts +5 -0
  27. package/dist/data/index.js +5 -0
  28. package/dist/data/useCollection.d.ts +42 -0
  29. package/dist/data/useCollection.js +67 -0
  30. package/dist/data/useCollection.test.d.ts +1 -0
  31. package/dist/data/useCollection.test.js +27 -0
  32. package/dist/data/useEventSource.d.ts +15 -0
  33. package/dist/data/useEventSource.js +37 -0
  34. package/dist/data/useWebSocket.d.ts +17 -0
  35. package/dist/data/useWebSocket.js +61 -0
  36. package/dist/dataviz/Stat.d.ts +18 -0
  37. package/dist/dataviz/Stat.js +14 -0
  38. package/dist/dataviz/dataviz.css +15 -0
  39. package/dist/dataviz/index.d.ts +2 -0
  40. package/dist/dataviz/index.js +2 -0
  41. package/dist/dataviz/sparkline.d.ts +15 -0
  42. package/dist/dataviz/sparkline.js +31 -0
  43. package/dist/dataviz/sparkline.test.d.ts +1 -0
  44. package/dist/dataviz/sparkline.test.js +14 -0
  45. package/dist/format.d.ts +3 -0
  46. package/dist/format.js +9 -0
  47. package/dist/hooks/index.d.ts +20 -0
  48. package/dist/hooks/index.js +97 -0
  49. package/dist/hyperliquid/PriceTicker.d.ts +11 -0
  50. package/dist/hyperliquid/PriceTicker.js +24 -0
  51. package/dist/hyperliquid/client.d.ts +9 -0
  52. package/dist/hyperliquid/client.js +17 -0
  53. package/dist/hyperliquid/hl.css +10 -0
  54. package/dist/hyperliquid/index.d.ts +3 -0
  55. package/dist/hyperliquid/index.js +3 -0
  56. package/dist/hyperliquid/useHyperliquid.d.ts +76 -0
  57. package/dist/hyperliquid/useHyperliquid.js +52 -0
  58. package/dist/index.d.ts +10 -0
  59. package/dist/index.js +26 -5
  60. package/dist/nft/MintButton.d.ts +20 -0
  61. package/dist/nft/MintButton.js +9 -0
  62. package/dist/nft/NFTCard.d.ts +15 -0
  63. package/dist/nft/NFTCard.js +14 -0
  64. package/dist/nft/NFTMedia.d.ts +11 -0
  65. package/dist/nft/NFTMedia.js +16 -0
  66. package/dist/nft/index.d.ts +5 -0
  67. package/dist/nft/index.js +5 -0
  68. package/dist/nft/nft.css +16 -0
  69. package/dist/nft/resolveUri.d.ts +1 -0
  70. package/dist/nft/resolveUri.js +15 -0
  71. package/dist/nft/resolveUri.test.d.ts +1 -0
  72. package/dist/nft/resolveUri.test.js +13 -0
  73. package/dist/nft/useNFT.d.ts +34 -0
  74. package/dist/nft/useNFT.js +65 -0
  75. package/dist/provider/LivoApp.d.ts +13 -0
  76. package/dist/provider/LivoApp.js +16 -0
  77. package/dist/provider/index.d.ts +1 -0
  78. package/dist/provider/index.js +1 -0
  79. package/dist/telegram/LinkTelegramButton.d.ts +12 -0
  80. package/dist/telegram/LinkTelegramButton.js +19 -0
  81. package/dist/telegram/index.d.ts +5 -0
  82. package/dist/telegram/index.js +5 -0
  83. package/dist/telegram/miniapp.d.ts +15 -0
  84. package/dist/telegram/miniapp.js +63 -0
  85. package/dist/telegram/telegram.css +6 -0
  86. package/dist/telegram/useTelegramLink.d.ts +20 -0
  87. package/dist/telegram/useTelegramLink.js +114 -0
  88. package/dist/telegram/useTelegramMiniApp.d.ts +17 -0
  89. package/dist/telegram/useTelegramMiniApp.js +24 -0
  90. package/dist/telegram/useTelegramTheme.d.ts +2 -0
  91. package/dist/telegram/useTelegramTheme.js +20 -0
  92. package/dist/theme/ThemeProvider.d.ts +17 -0
  93. package/dist/theme/ThemeProvider.js +23 -0
  94. package/dist/theme/index.d.ts +1 -0
  95. package/dist/theme/index.js +1 -0
  96. package/dist/token/ApproveButton.d.ts +12 -0
  97. package/dist/token/ApproveButton.js +9 -0
  98. package/dist/token/index.d.ts +5 -0
  99. package/dist/token/index.js +5 -0
  100. package/dist/token/useAllowance.d.ts +13 -0
  101. package/dist/token/useAllowance.js +22 -0
  102. package/dist/token/useApprove.d.ts +10 -0
  103. package/dist/token/useApprove.js +14 -0
  104. package/dist/token/useToken.d.ts +7 -0
  105. package/dist/token/useToken.js +15 -0
  106. package/dist/token/useTokenGate.d.ts +17 -0
  107. package/dist/token/useTokenGate.js +24 -0
  108. package/dist/tx/TxProgress.d.ts +13 -0
  109. package/dist/tx/TxProgress.js +22 -0
  110. package/dist/tx/index.d.ts +1 -0
  111. package/dist/tx/index.js +1 -0
  112. package/dist/tx/tx.css +9 -0
  113. package/dist/ui/index.d.ts +1 -1
  114. package/dist/ui/index.js +1 -1
  115. package/dist/ui/ui.css +18 -0
  116. package/dist/ui/ui.d.ts +19 -1
  117. package/dist/ui/ui.js +31 -0
  118. package/dist/user/UserMenu.d.ts +10 -0
  119. package/dist/user/UserMenu.js +18 -0
  120. package/dist/user/index.d.ts +2 -0
  121. package/dist/user/index.js +2 -0
  122. package/dist/user/useUser.d.ts +32 -0
  123. package/dist/user/useUser.js +36 -0
  124. package/dist/user/user.css +15 -0
  125. package/dist/web3/AddressInput.d.ts +10 -0
  126. package/dist/web3/AddressInput.js +20 -0
  127. package/dist/web3/ChainSwitcher.d.ts +5 -0
  128. package/dist/web3/ChainSwitcher.js +11 -0
  129. package/dist/web3/TokenAmount.d.ts +16 -0
  130. package/dist/web3/TokenAmount.js +17 -0
  131. package/dist/web3/index.d.ts +3 -0
  132. package/dist/web3/index.js +3 -0
  133. package/dist/web3/web3.css +12 -1
  134. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ import { useEffect, useState } from "react";
2
+ // Consume a Server-Sent Events stream (e.g. a runtime `sse()` endpoint in your api/).
3
+ // Auto-reconnects (EventSource does), parses JSON payloads, and tears down on unmount.
4
+ export function useEventSource(url, opts = {}) {
5
+ const [data, setData] = useState(undefined);
6
+ const [status, setStatus] = useState("connecting");
7
+ const [error, setError] = useState(null);
8
+ useEffect(() => {
9
+ if (!url || opts.enabled === false || typeof EventSource === "undefined") {
10
+ setStatus("closed");
11
+ return;
12
+ }
13
+ setStatus("connecting");
14
+ setError(null);
15
+ const es = new EventSource(url, { withCredentials: opts.withCredentials });
16
+ const onMsg = (e) => {
17
+ setStatus("open");
18
+ try {
19
+ setData(JSON.parse(e.data));
20
+ }
21
+ catch {
22
+ setData(e.data);
23
+ }
24
+ };
25
+ es.onopen = () => setStatus("open");
26
+ es.onerror = () => {
27
+ setStatus("error");
28
+ setError(new Error("EventSource connection error"));
29
+ };
30
+ if (opts.event)
31
+ es.addEventListener(opts.event, onMsg);
32
+ else
33
+ es.onmessage = onMsg;
34
+ return () => es.close();
35
+ }, [url, opts.enabled, opts.withCredentials, opts.event]);
36
+ return { data, status, error };
37
+ }
@@ -0,0 +1,17 @@
1
+ import { type StreamStatus } from "./useEventSource";
2
+ export interface WebSocketState<T> {
3
+ /** Last message, JSON-parsed when possible (else the raw string). */
4
+ data: T | undefined;
5
+ status: StreamStatus;
6
+ /** Send a message (objects are JSON-stringified). No-op until the socket is open. */
7
+ send: (message: unknown) => void;
8
+ }
9
+ export interface UseWebSocketOptions {
10
+ enabled?: boolean;
11
+ protocols?: string | string[];
12
+ /** Reconnect after an unexpected close. Default true. */
13
+ reconnect?: boolean;
14
+ /** Reconnect delay in ms (default 2000). */
15
+ reconnectDelayMs?: number;
16
+ }
17
+ export declare function useWebSocket<T = unknown>(url?: string, opts?: UseWebSocketOptions): WebSocketState<T>;
@@ -0,0 +1,61 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import {} from "./useEventSource";
3
+ // Subscribe to a WebSocket with JSON parsing, an imperative `send`, and optional
4
+ // auto-reconnect. Tears down on unmount / url change.
5
+ export function useWebSocket(url, opts = {}) {
6
+ const { enabled = true, protocols, reconnect = true, reconnectDelayMs = 2000 } = opts;
7
+ const [data, setData] = useState(undefined);
8
+ const [status, setStatus] = useState("connecting");
9
+ const sockRef = useRef(null);
10
+ useEffect(() => {
11
+ if (!url || !enabled || typeof WebSocket === "undefined") {
12
+ setStatus("closed");
13
+ return;
14
+ }
15
+ let closed = false;
16
+ let timer;
17
+ const connect = () => {
18
+ if (closed)
19
+ return;
20
+ setStatus("connecting");
21
+ const ws = new WebSocket(url, protocols);
22
+ sockRef.current = ws;
23
+ ws.onopen = () => setStatus("open");
24
+ ws.onmessage = (e) => {
25
+ try {
26
+ setData(JSON.parse(typeof e.data === "string" ? e.data : ""));
27
+ }
28
+ catch {
29
+ setData(e.data);
30
+ }
31
+ };
32
+ ws.onerror = () => setStatus("error");
33
+ ws.onclose = () => {
34
+ if (closed)
35
+ return;
36
+ setStatus("closed");
37
+ if (reconnect)
38
+ timer = setTimeout(connect, reconnectDelayMs);
39
+ };
40
+ };
41
+ connect();
42
+ return () => {
43
+ closed = true;
44
+ if (timer)
45
+ clearTimeout(timer);
46
+ try {
47
+ sockRef.current?.close();
48
+ }
49
+ catch {
50
+ /* already closed */
51
+ }
52
+ sockRef.current = null;
53
+ };
54
+ }, [url, enabled, protocols, reconnect, reconnectDelayMs]);
55
+ const send = (message) => {
56
+ const ws = sockRef.current;
57
+ if (ws && ws.readyState === WebSocket.OPEN)
58
+ ws.send(typeof message === "string" ? message : JSON.stringify(message));
59
+ };
60
+ return { data, status, send };
61
+ }
@@ -0,0 +1,18 @@
1
+ import "./dataviz.css";
2
+ import { type ReactNode } from "react";
3
+ export interface StatProps {
4
+ label: ReactNode;
5
+ value: ReactNode;
6
+ /** Signed change shown with +/- and up/down color. */
7
+ change?: number;
8
+ /** Suffix for the change (default "%"). */
9
+ changeSuffix?: string;
10
+ hint?: ReactNode;
11
+ className?: string;
12
+ }
13
+ export declare function Stat({ label, value, change, changeSuffix, hint, className }: StatProps): import("react").JSX.Element;
14
+ export interface StatGroupProps {
15
+ children: ReactNode;
16
+ className?: string;
17
+ }
18
+ export declare function StatGroup({ children, className }: StatGroupProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./dataviz.css";
3
+ import {} from "react";
4
+ import { cx } from "../util";
5
+ // A single dashboard metric: label, big value, optional colored change + hint.
6
+ // Neutral — style via .kit-stat*.
7
+ export function Stat({ label, value, change, changeSuffix = "%", hint, className }) {
8
+ const dir = change == null ? undefined : change > 0 ? "up" : change < 0 ? "down" : "flat";
9
+ return (_jsxs("div", { className: cx("kit-stat", className), children: [_jsx("div", { className: "kit-stat-label", children: label }), _jsx("div", { className: "kit-stat-value", children: value }), change != null && (_jsxs("div", { className: "kit-stat-change", "data-dir": dir, children: [change > 0 ? "+" : "", change, changeSuffix] })), hint != null && _jsx("div", { className: "kit-stat-hint", children: hint })] }));
10
+ }
11
+ // A responsive row/grid of <Stat> cards for a dashboard header.
12
+ export function StatGroup({ children, className }) {
13
+ return _jsx("div", { className: cx("kit-stat-group", className), children: children });
14
+ }
@@ -0,0 +1,15 @@
1
+ /* Sparkline + Stat — neutral dashboard primitives. */
2
+ .kit-spark{display:inline-block;vertical-align:middle;overflow:visible}
3
+
4
+ .kit-stat-group{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px}
5
+ .kit-stat{display:flex;flex-direction:column;gap:2px;padding:14px 16px;border:1px solid var(--kit-border,#e5e7eb);border-radius:var(--kit-radius,14px);background:var(--kit-bg,#fff);font:inherit}
6
+ .kit-stat-label{font-size:12px;color:var(--kit-muted,#6b7280);text-transform:uppercase;letter-spacing:.03em}
7
+ .kit-stat-value{font-size:22px;font-weight:700;color:var(--kit-fg,#111827);font-variant-numeric:tabular-nums}
8
+ .kit-stat-change{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}
9
+ .kit-stat-change[data-dir="up"]{color:var(--kit-up,#15803d)}
10
+ .kit-stat-change[data-dir="down"]{color:var(--kit-down,#b91c1c)}
11
+ .kit-stat-change[data-dir="flat"]{color:var(--kit-muted,#6b7280)}
12
+ .kit-stat-hint{font-size:12px;color:var(--kit-muted,#6b7280)}
13
+ @media (prefers-color-scheme:dark){
14
+ .kit-stat{--kit-border:#2c2e36;--kit-bg:#15171c;--kit-fg:#f5f5f7}
15
+ }
@@ -0,0 +1,2 @@
1
+ export { Sparkline, sparklinePath, type SparklineProps } from "./sparkline";
2
+ export { Stat, StatGroup, type StatProps, type StatGroupProps } from "./Stat";
@@ -0,0 +1,2 @@
1
+ export { Sparkline, sparklinePath } from "./sparkline";
2
+ export { Stat, StatGroup } from "./Stat";
@@ -0,0 +1,15 @@
1
+ import "./dataviz.css";
2
+ /** Pure: map a value series to an SVG polyline path string in a width×height box. */
3
+ export declare function sparklinePath(data: number[], width: number, height: number): string;
4
+ export interface SparklineProps {
5
+ data: number[];
6
+ width?: number;
7
+ height?: number;
8
+ /** Stroke color. Defaults to green/red by overall direction. */
9
+ stroke?: string;
10
+ strokeWidth?: number;
11
+ /** Fill under the line (e.g. "rgba(22,163,74,.12)"). */
12
+ fill?: string;
13
+ className?: string;
14
+ }
15
+ export declare function Sparkline({ data, width, height, stroke, strokeWidth, fill, className }: SparklineProps): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./dataviz.css";
3
+ import { cx } from "../util";
4
+ /** Pure: map a value series to an SVG polyline path string in a width×height box. */
5
+ export function sparklinePath(data, width, height) {
6
+ const n = data.length;
7
+ if (n === 0)
8
+ return "";
9
+ const min = Math.min(...data);
10
+ const max = Math.max(...data);
11
+ const range = max - min || 1;
12
+ const dx = n > 1 ? width / (n - 1) : 0;
13
+ return data
14
+ .map((v, i) => {
15
+ const x = i * dx;
16
+ const y = height - ((v - min) / range) * height;
17
+ return `${i ? "L" : "M"}${x.toFixed(2)} ${y.toFixed(2)}`;
18
+ })
19
+ .join(" ");
20
+ }
21
+ // A tiny inline SVG trend line — great in table rows / stat cards. Dependency-free,
22
+ // auto-colored by direction (last vs first). Neutral; override via props or CSS.
23
+ export function Sparkline({ data, width = 100, height = 28, stroke, strokeWidth = 1.5, fill, className }) {
24
+ const d = sparklinePath(data, width, height);
25
+ if (!d)
26
+ return _jsx("svg", { className: cx("kit-spark", className), width: width, height: height });
27
+ const up = data[data.length - 1] >= data[0];
28
+ const color = stroke ?? (up ? "var(--kit-up, #16a34a)" : "var(--kit-down, #dc2626)");
29
+ const area = fill ? `${d} L ${width} ${height} L 0 ${height} Z` : "";
30
+ return (_jsxs("svg", { className: cx("kit-spark", className), width: width, height: height, viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "none", children: [fill && _jsx("path", { d: area, fill: fill, stroke: "none" }), _jsx("path", { d: d, fill: "none", stroke: color, strokeWidth: strokeWidth, strokeLinejoin: "round", strokeLinecap: "round" })] }));
31
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { sparklinePath } from "./sparkline";
3
+ describe("sparklinePath", () => {
4
+ it("maps min→bottom and max→top across the width", () => {
5
+ // data [0,1,2] in a 100×10 box: x at 0,50,100; y at 10 (min), 5, 0 (max)
6
+ expect(sparklinePath([0, 1, 2], 100, 10)).toBe("M0.00 10.00 L50.00 5.00 L100.00 0.00");
7
+ });
8
+ it("flat series sits on the baseline (range guarded)", () => {
9
+ expect(sparklinePath([5, 5], 10, 10)).toBe("M0.00 10.00 L10.00 10.00");
10
+ });
11
+ it("empty data yields an empty path", () => {
12
+ expect(sparklinePath([], 100, 10)).toBe("");
13
+ });
14
+ });
package/dist/format.d.ts CHANGED
@@ -12,5 +12,8 @@ export declare function parseToken(value: string, decimals?: number): bigint;
12
12
  export declare function formatUsd(n: number, opts?: {
13
13
  compact?: boolean;
14
14
  }): string;
15
+ /** Format a market price with sensible precision (big numbers grouped, small ones
16
+ * given more decimals). `formatPrice(64210.5)` → "64,210.50"; `formatPrice(0.00001234)` → "0.00001234". */
17
+ export declare function formatPrice(n: number): string;
15
18
  /** Short relative time from a unix-seconds (or ms) timestamp. `timeAgo(t)` → "3m ago". */
16
19
  export declare function timeAgo(ts: number, nowMs?: number): string;
package/dist/format.js CHANGED
@@ -42,6 +42,15 @@ export function formatUsd(n, opts = {}) {
42
42
  maximumFractionDigits: opts.compact ? 2 : undefined,
43
43
  }).format(n);
44
44
  }
45
+ /** Format a market price with sensible precision (big numbers grouped, small ones
46
+ * given more decimals). `formatPrice(64210.5)` → "64,210.50"; `formatPrice(0.00001234)` → "0.00001234". */
47
+ export function formatPrice(n) {
48
+ if (!isFinite(n))
49
+ return "—";
50
+ const abs = Math.abs(n);
51
+ const digits = abs >= 1000 ? 2 : abs >= 1 ? 2 : abs >= 0.01 ? 4 : 8;
52
+ return n.toLocaleString("en-US", { minimumFractionDigits: digits <= 2 ? 2 : 0, maximumFractionDigits: digits });
53
+ }
45
54
  /** Short relative time from a unix-seconds (or ms) timestamp. `timeAgo(t)` → "3m ago". */
46
55
  export function timeAgo(ts, nowMs = Date.now()) {
47
56
  const tsMs = ts > 1e12 ? ts : ts * 1000;
@@ -0,0 +1,20 @@
1
+ /** Copy text to the clipboard; returns [copied, copy]. `copied` flips true for ~1.2s. */
2
+ export declare function useCopyToClipboard(): [boolean, (text: string) => void];
3
+ /** State persisted to localStorage (SSR-safe; falls back to in-memory). */
4
+ export declare function useLocalStorage<T>(key: string, initial: T): [T, (value: T) => void];
5
+ /** Debounce a changing value by `ms` milliseconds. */
6
+ export declare function useDebounce<T>(value: T, ms?: number): T;
7
+ export interface CountdownState {
8
+ days: number;
9
+ hours: number;
10
+ minutes: number;
11
+ seconds: number;
12
+ /** Milliseconds remaining (0 once elapsed). */
13
+ total: number;
14
+ /** True once the target time has passed. */
15
+ done: boolean;
16
+ }
17
+ /** Live countdown to a target (Date, unix seconds, or ms). Ticks every second; stops at 0. */
18
+ export declare function useCountdown(to: Date | number): CountdownState;
19
+ /** Run a callback every `ms` milliseconds. Pass `ms = null` to pause. */
20
+ export declare function useInterval(callback: () => void, ms: number | null): void;
@@ -0,0 +1,97 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ /** Copy text to the clipboard; returns [copied, copy]. `copied` flips true for ~1.2s. */
3
+ export function useCopyToClipboard() {
4
+ const [copied, setCopied] = useState(false);
5
+ const timer = useRef(null);
6
+ const copy = useCallback((text) => {
7
+ try {
8
+ navigator.clipboard.writeText(text);
9
+ setCopied(true);
10
+ if (timer.current)
11
+ clearTimeout(timer.current);
12
+ timer.current = setTimeout(() => setCopied(false), 1200);
13
+ }
14
+ catch {
15
+ /* clipboard unavailable */
16
+ }
17
+ }, []);
18
+ useEffect(() => () => { if (timer.current)
19
+ clearTimeout(timer.current); }, []);
20
+ return [copied, copy];
21
+ }
22
+ /** State persisted to localStorage (SSR-safe; falls back to in-memory). */
23
+ export function useLocalStorage(key, initial) {
24
+ const [value, setValue] = useState(() => {
25
+ if (typeof window === "undefined")
26
+ return initial;
27
+ try {
28
+ const raw = window.localStorage.getItem(key);
29
+ return raw ? JSON.parse(raw) : initial;
30
+ }
31
+ catch {
32
+ return initial;
33
+ }
34
+ });
35
+ const set = useCallback((next) => {
36
+ setValue(next);
37
+ try {
38
+ window.localStorage.setItem(key, JSON.stringify(next));
39
+ }
40
+ catch {
41
+ /* storage unavailable / quota */
42
+ }
43
+ }, [key]);
44
+ return [value, set];
45
+ }
46
+ /** Debounce a changing value by `ms` milliseconds. */
47
+ export function useDebounce(value, ms = 300) {
48
+ const [debounced, setDebounced] = useState(value);
49
+ useEffect(() => {
50
+ const t = setTimeout(() => setDebounced(value), ms);
51
+ return () => clearTimeout(t);
52
+ }, [value, ms]);
53
+ return debounced;
54
+ }
55
+ function remaining(targetMs) {
56
+ const total = Math.max(0, targetMs - Date.now());
57
+ const s = Math.floor(total / 1000);
58
+ return {
59
+ days: Math.floor(s / 86400),
60
+ hours: Math.floor((s % 86400) / 3600),
61
+ minutes: Math.floor((s % 3600) / 60),
62
+ seconds: s % 60,
63
+ total,
64
+ done: total === 0,
65
+ };
66
+ }
67
+ /** Live countdown to a target (Date, unix seconds, or ms). Ticks every second; stops at 0. */
68
+ export function useCountdown(to) {
69
+ const targetMs = to instanceof Date ? to.getTime() : to < 1e12 ? to * 1000 : to;
70
+ const [state, setState] = useState(() => remaining(targetMs));
71
+ useEffect(() => {
72
+ setState(remaining(targetMs));
73
+ if (targetMs <= Date.now())
74
+ return;
75
+ const id = setInterval(() => {
76
+ const next = remaining(targetMs);
77
+ setState(next);
78
+ if (next.done)
79
+ clearInterval(id);
80
+ }, 1000);
81
+ return () => clearInterval(id);
82
+ }, [targetMs]);
83
+ return state;
84
+ }
85
+ /** Run a callback every `ms` milliseconds. Pass `ms = null` to pause. */
86
+ export function useInterval(callback, ms) {
87
+ const saved = useRef(callback);
88
+ useEffect(() => {
89
+ saved.current = callback;
90
+ }, [callback]);
91
+ useEffect(() => {
92
+ if (ms == null)
93
+ return;
94
+ const id = setInterval(() => saved.current(), ms);
95
+ return () => clearInterval(id);
96
+ }, [ms]);
97
+ }
@@ -0,0 +1,11 @@
1
+ import "./hl.css";
2
+ import { type HlQueryOptions } from "./useHyperliquid";
3
+ export interface PriceTickerProps extends HlQueryOptions {
4
+ coin: string;
5
+ /** Show the coin symbol before the price. Default true. */
6
+ showSymbol?: boolean;
7
+ /** Prefix (e.g. "$"). Default "$". */
8
+ prefix?: string;
9
+ className?: string;
10
+ }
11
+ export declare function PriceTicker({ coin, showSymbol, prefix, className, ...opts }: PriceTickerProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./hl.css";
3
+ import { useEffect, useRef } from "react";
4
+ import { useHlPrice } from "./useHyperliquid";
5
+ import { formatPrice } from "../format";
6
+ import { cx } from "../util";
7
+ // Live Hyperliquid price for a coin, flashing green/red on up/down ticks. Neutral —
8
+ // style via .kit-ticker[data-dir]. No key needed (public market data).
9
+ export function PriceTicker({ coin, showSymbol = true, prefix = "$", className, ...opts }) {
10
+ const { data: price } = useHlPrice(coin, opts);
11
+ const prev = useRef(undefined);
12
+ const dir = price !== undefined && prev.current !== undefined
13
+ ? price > prev.current
14
+ ? "up"
15
+ : price < prev.current
16
+ ? "down"
17
+ : "flat"
18
+ : "flat";
19
+ useEffect(() => {
20
+ if (price !== undefined)
21
+ prev.current = price;
22
+ }, [price]);
23
+ return (_jsxs("span", { className: cx("kit-ticker", className), "data-dir": dir, children: [showSymbol && _jsx("span", { className: "kit-ticker-sym", children: coin }), _jsx("span", { className: "kit-ticker-px", children: price !== undefined ? prefix + formatPrice(price) : "—" })] }));
24
+ }
@@ -0,0 +1,9 @@
1
+ export declare const HL_MAINNET = "https://api.hyperliquid.xyz";
2
+ export declare const HL_TESTNET = "https://api.hyperliquid-testnet.xyz";
3
+ export interface HlOptions {
4
+ testnet?: boolean;
5
+ /** Override the API base URL (e.g. a CORS proxy). */
6
+ baseUrl?: string;
7
+ }
8
+ /** POST a body to the Hyperliquid `info` endpoint and return the parsed JSON. */
9
+ export declare function hlInfo<T = unknown>(body: Record<string, unknown>, opts?: HlOptions): Promise<T>;
@@ -0,0 +1,17 @@
1
+ // Frontend-safe Hyperliquid client. Reads the public `info` endpoint directly from
2
+ // the browser — no key, no SDK, no deps. (For TRADING, route orders through your
3
+ // api/ Worker using @livo-build/runtime's Hyperliquid, which signs server-side.)
4
+ export const HL_MAINNET = "https://api.hyperliquid.xyz";
5
+ export const HL_TESTNET = "https://api.hyperliquid-testnet.xyz";
6
+ /** POST a body to the Hyperliquid `info` endpoint and return the parsed JSON. */
7
+ export async function hlInfo(body, opts = {}) {
8
+ const base = opts.baseUrl ?? (opts.testnet ? HL_TESTNET : HL_MAINNET);
9
+ const res = await fetch(base + "/info", {
10
+ method: "POST",
11
+ headers: { "content-type": "application/json" },
12
+ body: JSON.stringify(body),
13
+ });
14
+ if (!res.ok)
15
+ throw new Error("Hyperliquid info " + res.status);
16
+ return (await res.json());
17
+ }
@@ -0,0 +1,10 @@
1
+ /* Hyperliquid price ticker. Neutral; restyle via .kit-ticker[data-dir]. */
2
+ .kit-ticker{display:inline-flex;align-items:baseline;gap:6px;font:inherit;font-variant-numeric:tabular-nums;transition:color .3s}
3
+ .kit-ticker-sym{color:var(--kit-muted,#6b7280);font-weight:600;font-size:.85em}
4
+ .kit-ticker-px{font-weight:700}
5
+ .kit-ticker[data-dir="up"]{color:var(--kit-up,#15803d)}
6
+ .kit-ticker[data-dir="down"]{color:var(--kit-down,#b91c1c)}
7
+ @media (prefers-color-scheme:dark){
8
+ .kit-ticker[data-dir="up"]{color:var(--kit-up,#4ade80)}
9
+ .kit-ticker[data-dir="down"]{color:var(--kit-down,#f87171)}
10
+ }
@@ -0,0 +1,3 @@
1
+ export { hlInfo, HL_MAINNET, HL_TESTNET, type HlOptions } from "./client";
2
+ export { useHlMids, useHlPrice, useHlCandles, useHlOrderBook, useHlPositions, type HlQueryOptions, type HlResult, type HlCandle, type HlCandleInterval, type HlBook, type HlBookLevel, type HlPosition, type HlClearinghouseState, type UseHlCandlesParams, } from "./useHyperliquid";
3
+ export { PriceTicker, type PriceTickerProps } from "./PriceTicker";
@@ -0,0 +1,3 @@
1
+ export { hlInfo, HL_MAINNET, HL_TESTNET } from "./client";
2
+ export { useHlMids, useHlPrice, useHlCandles, useHlOrderBook, useHlPositions, } from "./useHyperliquid";
3
+ export { PriceTicker } from "./PriceTicker";
@@ -0,0 +1,76 @@
1
+ import { type HlOptions } from "./client";
2
+ export interface HlQueryOptions extends HlOptions {
3
+ /** Poll interval in ms (default 6000). Pass false to fetch once. */
4
+ refetchInterval?: number | false;
5
+ enabled?: boolean;
6
+ }
7
+ export interface HlResult<T> {
8
+ data: T | undefined;
9
+ error: Error | null;
10
+ isLoading: boolean;
11
+ refetch: () => void;
12
+ }
13
+ export type HlCandleInterval = "1m" | "3m" | "5m" | "15m" | "30m" | "1h" | "2h" | "4h" | "8h" | "12h" | "1d" | "3d" | "1w" | "1M";
14
+ export interface HlCandle {
15
+ t: number;
16
+ T: number;
17
+ s: string;
18
+ i: string;
19
+ o: string;
20
+ c: string;
21
+ h: string;
22
+ l: string;
23
+ v: string;
24
+ n: number;
25
+ }
26
+ export interface HlBookLevel {
27
+ px: string;
28
+ sz: string;
29
+ n: number;
30
+ }
31
+ export interface HlBook {
32
+ coin: string;
33
+ time: number;
34
+ levels: [HlBookLevel[], HlBookLevel[]];
35
+ }
36
+ export interface HlPosition {
37
+ coin: string;
38
+ szi: string;
39
+ entryPx?: string;
40
+ positionValue?: string;
41
+ unrealizedPnl?: string;
42
+ leverage?: {
43
+ type: string;
44
+ value: number;
45
+ };
46
+ [k: string]: unknown;
47
+ }
48
+ export interface HlClearinghouseState {
49
+ assetPositions: Array<{
50
+ type: string;
51
+ position: HlPosition;
52
+ }>;
53
+ marginSummary: {
54
+ accountValue: string;
55
+ totalNtlPos: string;
56
+ totalRawUsd: string;
57
+ };
58
+ withdrawable: string;
59
+ [k: string]: unknown;
60
+ }
61
+ /** All mid prices, keyed by coin symbol (string values). Polls live. */
62
+ export declare function useHlMids(opts?: HlQueryOptions): HlResult<Record<string, string>>;
63
+ /** Live mid price for one coin (number), sharing the allMids cache across tickers. */
64
+ export declare function useHlPrice(coin?: string, opts?: HlQueryOptions): HlResult<number>;
65
+ export interface UseHlCandlesParams {
66
+ coin: string;
67
+ interval?: HlCandleInterval;
68
+ /** How far back from now to load. Default 24h. */
69
+ lookbackMs?: number;
70
+ }
71
+ /** Recent candles for a coin/interval. */
72
+ export declare function useHlCandles(params: UseHlCandlesParams, opts?: HlQueryOptions): HlResult<HlCandle[]>;
73
+ /** L2 order book (bids, asks) for a coin. */
74
+ export declare function useHlOrderBook(coin?: string, opts?: HlQueryOptions): HlResult<HlBook>;
75
+ /** A user's perp account state — positions, margin, withdrawable. */
76
+ export declare function useHlPositions(address?: string, opts?: HlQueryOptions): HlResult<HlClearinghouseState>;
@@ -0,0 +1,52 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { hlInfo } from "./client";
3
+ function wrap(q) {
4
+ return { data: q.data, error: q.error ?? null, isLoading: q.isLoading, refetch: () => void q.refetch() };
5
+ }
6
+ const DEFAULT_POLL = 6000;
7
+ /** All mid prices, keyed by coin symbol (string values). Polls live. */
8
+ export function useHlMids(opts = {}) {
9
+ return wrap(useQuery({
10
+ queryKey: ["hl-mids", opts.testnet, opts.baseUrl],
11
+ queryFn: () => hlInfo({ type: "allMids" }, opts),
12
+ refetchInterval: opts.refetchInterval ?? DEFAULT_POLL,
13
+ enabled: opts.enabled ?? true,
14
+ }));
15
+ }
16
+ /** Live mid price for one coin (number), sharing the allMids cache across tickers. */
17
+ export function useHlPrice(coin, opts = {}) {
18
+ const m = useHlMids({ ...opts, enabled: opts.enabled ?? Boolean(coin) });
19
+ const raw = coin ? m.data?.[coin] : undefined;
20
+ return { data: raw !== undefined ? Number(raw) : undefined, error: m.error, isLoading: m.isLoading, refetch: m.refetch };
21
+ }
22
+ /** Recent candles for a coin/interval. */
23
+ export function useHlCandles(params, opts = {}) {
24
+ const { coin, interval = "1h", lookbackMs = 86400000 } = params;
25
+ return wrap(useQuery({
26
+ queryKey: ["hl-candles", coin, interval, lookbackMs, opts.testnet],
27
+ queryFn: () => {
28
+ const endTime = Date.now();
29
+ return hlInfo({ type: "candleSnapshot", req: { coin, interval, startTime: endTime - lookbackMs, endTime } }, opts);
30
+ },
31
+ refetchInterval: opts.refetchInterval ?? DEFAULT_POLL,
32
+ enabled: (opts.enabled ?? true) && Boolean(coin),
33
+ }));
34
+ }
35
+ /** L2 order book (bids, asks) for a coin. */
36
+ export function useHlOrderBook(coin, opts = {}) {
37
+ return wrap(useQuery({
38
+ queryKey: ["hl-book", coin, opts.testnet],
39
+ queryFn: () => hlInfo({ type: "l2Book", coin }, opts),
40
+ refetchInterval: opts.refetchInterval ?? 3000,
41
+ enabled: (opts.enabled ?? true) && Boolean(coin),
42
+ }));
43
+ }
44
+ /** A user's perp account state — positions, margin, withdrawable. */
45
+ export function useHlPositions(address, opts = {}) {
46
+ return wrap(useQuery({
47
+ queryKey: ["hl-positions", address, opts.testnet],
48
+ queryFn: () => hlInfo({ type: "clearinghouseState", user: address }, opts),
49
+ refetchInterval: opts.refetchInterval ?? DEFAULT_POLL,
50
+ enabled: (opts.enabled ?? true) && Boolean(address),
51
+ }));
52
+ }
package/dist/index.d.ts CHANGED
@@ -2,8 +2,18 @@ export * from "./wallet";
2
2
  export * from "./provider";
3
3
  export * from "./contracts";
4
4
  export * from "./tx";
5
+ export * from "./auth";
5
6
  export * from "./toast";
6
7
  export * from "./data";
7
8
  export * from "./web3";
9
+ export * from "./token";
10
+ export * from "./nft";
11
+ export * from "./dataviz";
12
+ export * from "./user";
13
+ export * from "./hyperliquid";
14
+ export * from "./account";
8
15
  export * from "./ui";
16
+ export * from "./telegram";
17
+ export * from "./theme";
18
+ export * from "./hooks";
9
19
  export * from "./format";