@livo-build/kit 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/SignInWithEthereum.d.ts +8 -0
- package/dist/auth/SignInWithEthereum.js +18 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/useSiwe.d.ts +18 -0
- package/dist/auth/useSiwe.js +72 -0
- package/dist/data/DataTable.d.ts +26 -0
- package/dist/data/DataTable.js +15 -0
- package/dist/data/data.css +22 -0
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.js +4 -0
- package/dist/data/useCollection.d.ts +42 -0
- package/dist/data/useCollection.js +67 -0
- package/dist/data/useCollection.test.d.ts +1 -0
- package/dist/data/useCollection.test.js +27 -0
- package/dist/data/useEventSource.d.ts +15 -0
- package/dist/data/useEventSource.js +37 -0
- package/dist/data/useWebSocket.d.ts +17 -0
- package/dist/data/useWebSocket.js +61 -0
- package/dist/dataviz/Stat.d.ts +18 -0
- package/dist/dataviz/Stat.js +14 -0
- package/dist/dataviz/dataviz.css +15 -0
- package/dist/dataviz/index.d.ts +2 -0
- package/dist/dataviz/index.js +2 -0
- package/dist/dataviz/sparkline.d.ts +15 -0
- package/dist/dataviz/sparkline.js +31 -0
- package/dist/dataviz/sparkline.test.d.ts +1 -0
- package/dist/dataviz/sparkline.test.js +14 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.js +9 -0
- package/dist/hooks/index.d.ts +12 -0
- package/dist/hooks/index.js +30 -0
- package/dist/hyperliquid/PriceTicker.d.ts +11 -0
- package/dist/hyperliquid/PriceTicker.js +24 -0
- package/dist/hyperliquid/client.d.ts +9 -0
- package/dist/hyperliquid/client.js +17 -0
- package/dist/hyperliquid/hl.css +10 -0
- package/dist/hyperliquid/index.d.ts +3 -0
- package/dist/hyperliquid/index.js +3 -0
- package/dist/hyperliquid/useHyperliquid.d.ts +76 -0
- package/dist/hyperliquid/useHyperliquid.js +52 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +21 -7
- package/dist/nft/MintButton.d.ts +20 -0
- package/dist/nft/MintButton.js +9 -0
- package/dist/nft/NFTCard.d.ts +15 -0
- package/dist/nft/NFTCard.js +14 -0
- package/dist/nft/NFTMedia.d.ts +11 -0
- package/dist/nft/NFTMedia.js +16 -0
- package/dist/nft/index.d.ts +5 -0
- package/dist/nft/index.js +5 -0
- package/dist/nft/nft.css +16 -0
- package/dist/nft/resolveUri.d.ts +1 -0
- package/dist/nft/resolveUri.js +15 -0
- package/dist/nft/resolveUri.test.d.ts +1 -0
- package/dist/nft/resolveUri.test.js +13 -0
- package/dist/nft/useNFT.d.ts +34 -0
- package/dist/nft/useNFT.js +65 -0
- package/dist/polymarket/MarketCard.d.ts +11 -0
- package/dist/polymarket/MarketCard.js +16 -0
- package/dist/polymarket/MarketsList.d.ts +13 -0
- package/dist/polymarket/MarketsList.js +13 -0
- package/dist/polymarket/OddsBar.d.ts +9 -0
- package/dist/polymarket/OddsBar.js +13 -0
- package/dist/polymarket/OrderTicket.d.ts +12 -0
- package/dist/polymarket/OrderTicket.js +28 -0
- package/dist/polymarket/client.d.ts +73 -0
- package/dist/polymarket/client.js +31 -0
- package/dist/polymarket/index.d.ts +6 -0
- package/dist/polymarket/index.js +10 -0
- package/dist/polymarket/polymarket.css +49 -0
- package/dist/polymarket/usePolymarket.d.ts +59 -0
- package/dist/polymarket/usePolymarket.js +97 -0
- package/dist/telegram/index.d.ts +2 -0
- package/dist/telegram/index.js +2 -0
- package/dist/telegram/miniapp.d.ts +15 -0
- package/dist/telegram/miniapp.js +63 -0
- package/dist/telegram/useTelegramTheme.d.ts +2 -0
- package/dist/telegram/useTelegramTheme.js +20 -0
- package/dist/tx/TxProgress.d.ts +13 -0
- package/dist/tx/TxProgress.js +22 -0
- package/dist/tx/index.d.ts +1 -0
- package/dist/tx/index.js +1 -0
- package/dist/tx/tx.css +9 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/ui.css +7 -0
- package/dist/ui/ui.d.ts +13 -0
- package/dist/ui/ui.js +24 -0
- package/dist/user/UserMenu.d.ts +10 -0
- package/dist/user/UserMenu.js +18 -0
- package/dist/user/index.d.ts +2 -0
- package/dist/user/index.js +2 -0
- package/dist/user/useUser.d.ts +32 -0
- package/dist/user/useUser.js +36 -0
- package/dist/user/user.css +15 -0
- package/dist/web3/TokenAmount.d.ts +16 -0
- package/dist/web3/TokenAmount.js +17 -0
- package/dist/web3/index.d.ts +1 -0
- package/dist/web3/index.js +1 -0
- package/dist/web3/web3.css +4 -0
- package/package.json +1 -1
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;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -4,5 +4,17 @@ export declare function useCopyToClipboard(): [boolean, (text: string) => void];
|
|
|
4
4
|
export declare function useLocalStorage<T>(key: string, initial: T): [T, (value: T) => void];
|
|
5
5
|
/** Debounce a changing value by `ms` milliseconds. */
|
|
6
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;
|
|
7
19
|
/** Run a callback every `ms` milliseconds. Pass `ms = null` to pause. */
|
|
8
20
|
export declare function useInterval(callback: () => void, ms: number | null): void;
|
package/dist/hooks/index.js
CHANGED
|
@@ -52,6 +52,36 @@ export function useDebounce(value, ms = 300) {
|
|
|
52
52
|
}, [value, ms]);
|
|
53
53
|
return debounced;
|
|
54
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
|
+
}
|
|
55
85
|
/** Run a callback every `ms` milliseconds. Pass `ms = null` to pause. */
|
|
56
86
|
export function useInterval(callback, ms) {
|
|
57
87
|
const saved = useRef(callback);
|
|
@@ -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,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,10 +2,16 @@ 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";
|
|
8
9
|
export * from "./token";
|
|
10
|
+
export * from "./nft";
|
|
11
|
+
export * from "./dataviz";
|
|
12
|
+
export * from "./user";
|
|
13
|
+
export * from "./hyperliquid";
|
|
14
|
+
export * from "./polymarket";
|
|
9
15
|
export * from "./account";
|
|
10
16
|
export * from "./ui";
|
|
11
17
|
export * from "./telegram";
|
package/dist/index.js
CHANGED
|
@@ -5,25 +5,39 @@
|
|
|
5
5
|
// wallet/ — ConnectWallet, WalletModal, useWalletConnectors
|
|
6
6
|
// provider/ — LivoWeb3Provider (wagmi + react-query)
|
|
7
7
|
// contracts/ — useContractValue, createLivoContracts (bound to Livo's generated bindings)
|
|
8
|
-
// tx/ — useTx, TxButton, decodeRevert
|
|
8
|
+
// tx/ — useTx, TxButton, TxProgress, decodeRevert
|
|
9
|
+
// auth/ — useSiwe, SignInWithEthereum (Sign-In With Ethereum vs a same-origin /api/auth)
|
|
9
10
|
// toast/ — ToastProvider, useToast
|
|
10
|
-
// data/ — useApi (
|
|
11
|
-
// web3/ — Address, Balance, NetworkGuard,
|
|
11
|
+
// data/ — useApi, useSubgraph, useCollection + DataTable (search/sort/paginate), useEventSource/useWebSocket (realtime)
|
|
12
|
+
// web3/ — Address, Balance, NetworkGuard, TokenAmount(Input)
|
|
12
13
|
// token/ — useToken, useAllowance, useApprove, ApproveButton (ERC-20 + approvals)
|
|
14
|
+
// nft/ — useNFT, NFTCard, NFTMedia, MintButton (metadata + mint, ipfs-resolved)
|
|
15
|
+
// dataviz/ — Sparkline (inline SVG trend), Stat + StatGroup (dashboard metrics)
|
|
16
|
+
// user/ — useUser, UserMenu (wallet + SIWE session + Telegram link, unified)
|
|
17
|
+
// hyperliquid/ — useHlPrice/Mids/Candles/OrderBook/Positions, PriceTicker (public market data, no key)
|
|
13
18
|
// account/ — Connected, Disconnected, RequireConnection, useIsConnected, Avatar, Identity
|
|
14
|
-
// ui/ — Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton
|
|
15
|
-
// telegram/ — useTelegramMiniApp, useTelegramLink, LinkTelegramButton
|
|
19
|
+
// ui/ — Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Countdown
|
|
20
|
+
// telegram/ — useTelegramMiniApp, useTelegramLink, LinkTelegramButton, useTelegramTheme,
|
|
21
|
+
// useTelegramMainButton/BackButton/Haptics (Mini App + wallet ↔ Telegram linking)
|
|
16
22
|
// theme/ — KitThemeProvider (restyle the whole kit from one theme object)
|
|
17
|
-
//
|
|
18
|
-
//
|
|
23
|
+
// polymarket/ — useMarkets/useMarket/useMarketTrades/useOrderbook/usePriceHistory/usePlaceOrder,
|
|
24
|
+
// MarketsList/MarketCard/OddsBar/OrderTicket (prediction markets via Livo's indexer)
|
|
25
|
+
// hooks/ — useCopyToClipboard, useLocalStorage, useDebounce, useInterval, useCountdown
|
|
26
|
+
// format — shortAddress, formatToken, parseToken, formatUsd, formatPrice, timeAgo, …
|
|
19
27
|
export * from "./wallet";
|
|
20
28
|
export * from "./provider";
|
|
21
29
|
export * from "./contracts";
|
|
22
30
|
export * from "./tx";
|
|
31
|
+
export * from "./auth";
|
|
23
32
|
export * from "./toast";
|
|
24
33
|
export * from "./data";
|
|
25
34
|
export * from "./web3";
|
|
26
35
|
export * from "./token";
|
|
36
|
+
export * from "./nft";
|
|
37
|
+
export * from "./dataviz";
|
|
38
|
+
export * from "./user";
|
|
39
|
+
export * from "./hyperliquid";
|
|
40
|
+
export * from "./polymarket";
|
|
27
41
|
export * from "./account";
|
|
28
42
|
export * from "./ui";
|
|
29
43
|
export * from "./telegram";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Abi } from "viem";
|
|
3
|
+
export interface MintButtonProps {
|
|
4
|
+
address: `0x${string}`;
|
|
5
|
+
abi: Abi;
|
|
6
|
+
/** Mint function name. Default "mint". */
|
|
7
|
+
functionName?: string;
|
|
8
|
+
/** Number to mint. Default 1. Passed as the first arg unless `args` is given. */
|
|
9
|
+
quantity?: number;
|
|
10
|
+
/** Price per unit in wei — total value sent = pricePerUnit * quantity. */
|
|
11
|
+
pricePerUnit?: bigint;
|
|
12
|
+
/** Explicit args (overrides the default `[quantity]`). */
|
|
13
|
+
args?: readonly unknown[];
|
|
14
|
+
className?: string;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
toast?: boolean;
|
|
17
|
+
onSuccess?: (hash: `0x${string}`) => void;
|
|
18
|
+
onError?: (error: Error) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function MintButton({ address, abi, functionName, quantity, pricePerUnit, args, className, children, toast, onSuccess, onError, }: MintButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import {} from "react";
|
|
3
|
+
import { TxButton } from "../tx/TxButton";
|
|
4
|
+
// A mint button: wraps TxButton with the common shape — value = pricePerUnit ×
|
|
5
|
+
// quantity, args default to [quantity]. Neutral styling (inherits .kit-btn).
|
|
6
|
+
export function MintButton({ address, abi, functionName = "mint", quantity = 1, pricePerUnit, args, className, children, toast, onSuccess, onError, }) {
|
|
7
|
+
const value = pricePerUnit !== undefined ? pricePerUnit * BigInt(quantity) : undefined;
|
|
8
|
+
return (_jsx(TxButton, { address: address, abi: abi, functionName: functionName, args: args ?? [BigInt(quantity)], value: value, className: className, toast: toast, onSuccess: onSuccess, onError: onError, children: children ?? `Mint${quantity > 1 ? ` ${quantity}` : ""}` }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "./nft.css";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type NftMetadata } from "./useNFT";
|
|
4
|
+
export interface NFTCardProps {
|
|
5
|
+
address?: `0x${string}`;
|
|
6
|
+
tokenId?: bigint | number;
|
|
7
|
+
standard?: "erc721" | "erc1155";
|
|
8
|
+
gateway?: string;
|
|
9
|
+
/** Show the trait/attribute chips. Default true. */
|
|
10
|
+
attributes?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Custom render given the loaded metadata (overrides the default body). */
|
|
13
|
+
children?: (meta: NftMetadata | undefined) => ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export declare function NFTCard({ address, tokenId, standard, gateway, attributes, className, children }: NFTCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "./nft.css";
|
|
3
|
+
import {} from "react";
|
|
4
|
+
import { useNFT } from "./useNFT";
|
|
5
|
+
import { NFTMedia } from "./NFTMedia";
|
|
6
|
+
import { cx } from "../util";
|
|
7
|
+
// A neutral NFT card: media + name + (optional) attribute chips, loaded from the
|
|
8
|
+
// contract's token metadata. Style via the .kit-nft* classes — see STYLING.md.
|
|
9
|
+
export function NFTCard({ address, tokenId, standard, gateway, attributes = true, className, children }) {
|
|
10
|
+
const { metadata, image, isLoading, error } = useNFT({ address, tokenId, standard, gateway });
|
|
11
|
+
if (children)
|
|
12
|
+
return _jsx("div", { className: cx("kit-nft-card", className), children: children(metadata) });
|
|
13
|
+
return (_jsxs("div", { className: cx("kit-nft-card", className), "data-state": error ? "error" : isLoading ? "loading" : "ready", children: [_jsx(NFTMedia, { src: image, alt: metadata?.name, gateway: gateway }), _jsxs("div", { className: "kit-nft-body", children: [_jsx("div", { className: "kit-nft-name", children: metadata?.name ?? (isLoading ? "Loading…" : error ? "Unavailable" : "Untitled") }), attributes && metadata?.attributes?.length ? (_jsx("div", { className: "kit-nft-traits", children: metadata.attributes.map((a, i) => (_jsxs("span", { className: "kit-nft-trait", children: [a.trait_type ? _jsx("span", { className: "kit-nft-trait-k", children: a.trait_type }) : null, _jsx("span", { className: "kit-nft-trait-v", children: String(a.value) })] }, i))) })) : null] })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "./nft.css";
|
|
2
|
+
export interface NFTMediaProps {
|
|
3
|
+
/** Media URL — ipfs://, ar://, http(s) or data: (resolved automatically). */
|
|
4
|
+
src?: string;
|
|
5
|
+
/** Treat as a <video> (autoplay/loop/muted). Default: infer from extension. */
|
|
6
|
+
video?: boolean;
|
|
7
|
+
alt?: string;
|
|
8
|
+
gateway?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function NFTMedia({ src, video, alt, gateway, className }: NFTMediaProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import "./nft.css";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { resolveUri } from "./resolveUri";
|
|
5
|
+
import { cx } from "../util";
|
|
6
|
+
const VIDEO_RE = /\.(mp4|webm|ogg|mov)(\?|$)/i;
|
|
7
|
+
// Render NFT media from a (possibly ipfs://) URL: <img> or <video>, with a neutral
|
|
8
|
+
// skeleton until it loads and a fallback box on error. Style via .kit-nft-media*.
|
|
9
|
+
export function NFTMedia({ src, video, alt = "", gateway, className }) {
|
|
10
|
+
const [state, setState] = useState("loading");
|
|
11
|
+
if (!src)
|
|
12
|
+
return _jsx("div", { className: cx("kit-nft-media", "kit-nft-media-empty", className) });
|
|
13
|
+
const url = resolveUri(src, gateway);
|
|
14
|
+
const isVideo = video ?? VIDEO_RE.test(src);
|
|
15
|
+
return (_jsx("div", { className: cx("kit-nft-media", className), "data-state": state, children: isVideo ? (_jsx("video", { src: url, autoPlay: true, loop: true, muted: true, playsInline: true, onCanPlay: () => setState("ready"), onError: () => setState("error") })) : (_jsx("img", { src: url, alt: alt, loading: "lazy", onLoad: () => setState("ready"), onError: () => setState("error") })) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { useNFT, type UseNFTParams, type NftResult, type NftMetadata, type NftAttribute } from "./useNFT";
|
|
2
|
+
export { NFTMedia, type NFTMediaProps } from "./NFTMedia";
|
|
3
|
+
export { NFTCard, type NFTCardProps } from "./NFTCard";
|
|
4
|
+
export { MintButton, type MintButtonProps } from "./MintButton";
|
|
5
|
+
export { resolveUri } from "./resolveUri";
|
package/dist/nft/nft.css
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* NFT card + media. Neutral; restyle via these classes or --kit-* vars. */
|
|
2
|
+
.kit-nft-card{display:flex;flex-direction:column;gap:0;border:1px solid var(--kit-border,#e5e7eb);border-radius:var(--kit-radius,14px);overflow:hidden;background:var(--kit-bg,#fff);font:inherit}
|
|
3
|
+
.kit-nft-media{position:relative;aspect-ratio:1/1;background:var(--kit-skeleton,#f1f3f5);overflow:hidden}
|
|
4
|
+
.kit-nft-media img,.kit-nft-media video{width:100%;height:100%;object-fit:cover;display:block}
|
|
5
|
+
.kit-nft-media[data-state="loading"]{animation:kit-nft-pulse 1.2s ease-in-out infinite}
|
|
6
|
+
.kit-nft-media-empty{aspect-ratio:1/1;background:var(--kit-skeleton,#f1f3f5)}
|
|
7
|
+
@keyframes kit-nft-pulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
8
|
+
.kit-nft-body{display:flex;flex-direction:column;gap:8px;padding:12px}
|
|
9
|
+
.kit-nft-name{font-weight:600;font-size:15px;color:var(--kit-fg,#111827)}
|
|
10
|
+
.kit-nft-traits{display:flex;flex-wrap:wrap;gap:6px}
|
|
11
|
+
.kit-nft-trait{display:inline-flex;gap:4px;align-items:baseline;padding:3px 8px;border-radius:999px;background:var(--kit-skeleton,#f1f3f5);font-size:12px}
|
|
12
|
+
.kit-nft-trait-k{color:var(--kit-muted,#6b7280)}
|
|
13
|
+
.kit-nft-trait-v{font-weight:600;color:var(--kit-fg,#111827)}
|
|
14
|
+
@media (prefers-color-scheme:dark){
|
|
15
|
+
.kit-nft-card{--kit-border:#2c2e36;--kit-bg:#15171c;--kit-fg:#f5f5f7;--kit-skeleton:#23262e}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveUri(uri: string, gateway?: string): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Resolve an ipfs:// / ar:// URI to an HTTP(S) URL. http(s)/data URIs pass through.
|
|
2
|
+
// (Mirrors @livo-build/runtime's resolveUri so the kit needs no runtime dependency.)
|
|
3
|
+
export function resolveUri(uri, gateway = "https://ipfs.io/ipfs/") {
|
|
4
|
+
if (!uri)
|
|
5
|
+
return uri;
|
|
6
|
+
if (uri.startsWith("ipfs://")) {
|
|
7
|
+
let rest = uri.slice("ipfs://".length);
|
|
8
|
+
if (rest.startsWith("ipfs/"))
|
|
9
|
+
rest = rest.slice("ipfs/".length);
|
|
10
|
+
return gateway + rest;
|
|
11
|
+
}
|
|
12
|
+
if (uri.startsWith("ar://"))
|
|
13
|
+
return "https://arweave.net/" + uri.slice("ar://".length);
|
|
14
|
+
return uri;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveUri } from "./resolveUri";
|
|
3
|
+
describe("resolveUri (kit)", () => {
|
|
4
|
+
it("resolves ipfs:// (and ipfs://ipfs/), ar://, and passes http/data through", () => {
|
|
5
|
+
expect(resolveUri("ipfs://bafyCID/1.png")).toBe("https://ipfs.io/ipfs/bafyCID/1.png");
|
|
6
|
+
expect(resolveUri("ipfs://ipfs/bafyCID")).toBe("https://ipfs.io/ipfs/bafyCID");
|
|
7
|
+
expect(resolveUri("ipfs://CID", "https://x.mypinata.cloud/ipfs/")).toBe("https://x.mypinata.cloud/ipfs/CID");
|
|
8
|
+
expect(resolveUri("ar://TXID")).toBe("https://arweave.net/TXID");
|
|
9
|
+
expect(resolveUri("https://cdn/x.png")).toBe("https://cdn/x.png");
|
|
10
|
+
expect(resolveUri("data:image/svg+xml,<svg/>")).toBe("data:image/svg+xml,<svg/>");
|
|
11
|
+
expect(resolveUri("")).toBe("");
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface NftAttribute {
|
|
2
|
+
trait_type?: string;
|
|
3
|
+
value?: unknown;
|
|
4
|
+
[k: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface NftMetadata {
|
|
7
|
+
name?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Image URL resolved through the gateway (ipfs:// → https). */
|
|
10
|
+
image?: string;
|
|
11
|
+
/** animation_url resolved through the gateway. */
|
|
12
|
+
animationUrl?: string;
|
|
13
|
+
attributes?: NftAttribute[];
|
|
14
|
+
raw: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface UseNFTParams {
|
|
17
|
+
/** Collection contract address. */
|
|
18
|
+
address?: `0x${string}`;
|
|
19
|
+
tokenId?: bigint | number;
|
|
20
|
+
/** ERC-1155 reads uri(id); default ERC-721 tokenURI(id). */
|
|
21
|
+
standard?: "erc721" | "erc1155";
|
|
22
|
+
/** IPFS gateway base (trailing slash). Default "https://ipfs.io/ipfs/". */
|
|
23
|
+
gateway?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface NftResult {
|
|
26
|
+
metadata: NftMetadata | undefined;
|
|
27
|
+
/** Convenience: the resolved image URL from metadata. */
|
|
28
|
+
image: string | undefined;
|
|
29
|
+
/** The on-chain token URI (pre-fetch). */
|
|
30
|
+
uri: string | undefined;
|
|
31
|
+
isLoading: boolean;
|
|
32
|
+
error: Error | null;
|
|
33
|
+
}
|
|
34
|
+
export declare function useNFT(params: UseNFTParams): NftResult;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useReadContract } from "wagmi";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { resolveUri } from "./resolveUri";
|
|
4
|
+
const TOKEN_URI_ABI = [
|
|
5
|
+
{ type: "function", name: "tokenURI", stateMutability: "view", inputs: [{ name: "id", type: "uint256" }], outputs: [{ type: "string" }] },
|
|
6
|
+
{ type: "function", name: "uri", stateMutability: "view", inputs: [{ name: "id", type: "uint256" }], outputs: [{ type: "string" }] },
|
|
7
|
+
];
|
|
8
|
+
function idHex(id) {
|
|
9
|
+
return id.toString(16).padStart(64, "0");
|
|
10
|
+
}
|
|
11
|
+
async function loadMetadata(uri, gateway, id, standard) {
|
|
12
|
+
const sub = standard === "erc1155" ? uri.replace(/\{id\}/g, idHex(id)) : uri;
|
|
13
|
+
const resolved = resolveUri(sub, gateway);
|
|
14
|
+
let doc;
|
|
15
|
+
if (resolved.startsWith("data:")) {
|
|
16
|
+
const comma = resolved.indexOf(",");
|
|
17
|
+
const header = resolved.slice(5, comma);
|
|
18
|
+
const body = resolved.slice(comma + 1);
|
|
19
|
+
doc = JSON.parse(header.includes("base64") ? atob(body) : decodeURIComponent(body));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const res = await fetch(resolved);
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
throw new Error(`metadata fetch ${res.status}`);
|
|
25
|
+
doc = (await res.json());
|
|
26
|
+
}
|
|
27
|
+
const imageRaw = (doc.image ?? doc.image_url ?? doc.imageUrl);
|
|
28
|
+
const animRaw = (doc.animation_url ?? doc.animationUrl);
|
|
29
|
+
return {
|
|
30
|
+
name: doc.name,
|
|
31
|
+
description: doc.description,
|
|
32
|
+
image: imageRaw ? resolveUri(imageRaw, gateway) : undefined,
|
|
33
|
+
animationUrl: animRaw ? resolveUri(animRaw, gateway) : undefined,
|
|
34
|
+
attributes: doc.attributes,
|
|
35
|
+
raw: doc,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Read an NFT's on-chain metadata URI, then fetch + parse it (resolving ipfs:// →
|
|
39
|
+
// gateway for the doc and its image). Works for ERC-721 (tokenURI) and ERC-1155
|
|
40
|
+
// (uri(id) with {id} substitution). Caches via react-query.
|
|
41
|
+
export function useNFT(params) {
|
|
42
|
+
const { address, tokenId, standard = "erc721", gateway = "https://ipfs.io/ipfs/" } = params;
|
|
43
|
+
const id = tokenId === undefined ? undefined : BigInt(tokenId);
|
|
44
|
+
const fn = standard === "erc1155" ? "uri" : "tokenURI";
|
|
45
|
+
const uriRead = useReadContract({
|
|
46
|
+
address,
|
|
47
|
+
abi: TOKEN_URI_ABI,
|
|
48
|
+
functionName: fn,
|
|
49
|
+
args: id === undefined ? undefined : [id],
|
|
50
|
+
query: { enabled: Boolean(address && id !== undefined) },
|
|
51
|
+
});
|
|
52
|
+
const uri = uriRead.data;
|
|
53
|
+
const meta = useQuery({
|
|
54
|
+
queryKey: ["livo-nft", address, id?.toString(), uri, gateway],
|
|
55
|
+
queryFn: () => loadMetadata(uri, gateway, id, standard),
|
|
56
|
+
enabled: Boolean(uri),
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
metadata: meta.data,
|
|
60
|
+
image: meta.data?.image,
|
|
61
|
+
uri,
|
|
62
|
+
isLoading: uriRead.isLoading || meta.isLoading,
|
|
63
|
+
error: uriRead.error ?? meta.error ?? null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "./polymarket.css";
|
|
2
|
+
import type { PmMarket } from "./client";
|
|
3
|
+
export interface MarketCardProps {
|
|
4
|
+
market: PmMarket;
|
|
5
|
+
/** Called when the card is clicked (e.g. to open a detail view). */
|
|
6
|
+
onSelect?: (market: PmMarket) => void;
|
|
7
|
+
/** Hide the 24h volume footer. */
|
|
8
|
+
hideStats?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function MarketCard({ market, onSelect, hideStats, className }: MarketCardProps): import("react").JSX.Element;
|