@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.
Files changed (102) hide show
  1. package/dist/auth/SignInWithEthereum.d.ts +8 -0
  2. package/dist/auth/SignInWithEthereum.js +18 -0
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.js +2 -0
  5. package/dist/auth/useSiwe.d.ts +18 -0
  6. package/dist/auth/useSiwe.js +72 -0
  7. package/dist/data/DataTable.d.ts +26 -0
  8. package/dist/data/DataTable.js +15 -0
  9. package/dist/data/data.css +22 -0
  10. package/dist/data/index.d.ts +4 -0
  11. package/dist/data/index.js +4 -0
  12. package/dist/data/useCollection.d.ts +42 -0
  13. package/dist/data/useCollection.js +67 -0
  14. package/dist/data/useCollection.test.d.ts +1 -0
  15. package/dist/data/useCollection.test.js +27 -0
  16. package/dist/data/useEventSource.d.ts +15 -0
  17. package/dist/data/useEventSource.js +37 -0
  18. package/dist/data/useWebSocket.d.ts +17 -0
  19. package/dist/data/useWebSocket.js +61 -0
  20. package/dist/dataviz/Stat.d.ts +18 -0
  21. package/dist/dataviz/Stat.js +14 -0
  22. package/dist/dataviz/dataviz.css +15 -0
  23. package/dist/dataviz/index.d.ts +2 -0
  24. package/dist/dataviz/index.js +2 -0
  25. package/dist/dataviz/sparkline.d.ts +15 -0
  26. package/dist/dataviz/sparkline.js +31 -0
  27. package/dist/dataviz/sparkline.test.d.ts +1 -0
  28. package/dist/dataviz/sparkline.test.js +14 -0
  29. package/dist/format.d.ts +3 -0
  30. package/dist/format.js +9 -0
  31. package/dist/hooks/index.d.ts +12 -0
  32. package/dist/hooks/index.js +30 -0
  33. package/dist/hyperliquid/PriceTicker.d.ts +11 -0
  34. package/dist/hyperliquid/PriceTicker.js +24 -0
  35. package/dist/hyperliquid/client.d.ts +9 -0
  36. package/dist/hyperliquid/client.js +17 -0
  37. package/dist/hyperliquid/hl.css +10 -0
  38. package/dist/hyperliquid/index.d.ts +3 -0
  39. package/dist/hyperliquid/index.js +3 -0
  40. package/dist/hyperliquid/useHyperliquid.d.ts +76 -0
  41. package/dist/hyperliquid/useHyperliquid.js +52 -0
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +21 -7
  44. package/dist/nft/MintButton.d.ts +20 -0
  45. package/dist/nft/MintButton.js +9 -0
  46. package/dist/nft/NFTCard.d.ts +15 -0
  47. package/dist/nft/NFTCard.js +14 -0
  48. package/dist/nft/NFTMedia.d.ts +11 -0
  49. package/dist/nft/NFTMedia.js +16 -0
  50. package/dist/nft/index.d.ts +5 -0
  51. package/dist/nft/index.js +5 -0
  52. package/dist/nft/nft.css +16 -0
  53. package/dist/nft/resolveUri.d.ts +1 -0
  54. package/dist/nft/resolveUri.js +15 -0
  55. package/dist/nft/resolveUri.test.d.ts +1 -0
  56. package/dist/nft/resolveUri.test.js +13 -0
  57. package/dist/nft/useNFT.d.ts +34 -0
  58. package/dist/nft/useNFT.js +65 -0
  59. package/dist/polymarket/MarketCard.d.ts +11 -0
  60. package/dist/polymarket/MarketCard.js +16 -0
  61. package/dist/polymarket/MarketsList.d.ts +13 -0
  62. package/dist/polymarket/MarketsList.js +13 -0
  63. package/dist/polymarket/OddsBar.d.ts +9 -0
  64. package/dist/polymarket/OddsBar.js +13 -0
  65. package/dist/polymarket/OrderTicket.d.ts +12 -0
  66. package/dist/polymarket/OrderTicket.js +28 -0
  67. package/dist/polymarket/client.d.ts +73 -0
  68. package/dist/polymarket/client.js +31 -0
  69. package/dist/polymarket/index.d.ts +6 -0
  70. package/dist/polymarket/index.js +10 -0
  71. package/dist/polymarket/polymarket.css +49 -0
  72. package/dist/polymarket/usePolymarket.d.ts +59 -0
  73. package/dist/polymarket/usePolymarket.js +97 -0
  74. package/dist/telegram/index.d.ts +2 -0
  75. package/dist/telegram/index.js +2 -0
  76. package/dist/telegram/miniapp.d.ts +15 -0
  77. package/dist/telegram/miniapp.js +63 -0
  78. package/dist/telegram/useTelegramTheme.d.ts +2 -0
  79. package/dist/telegram/useTelegramTheme.js +20 -0
  80. package/dist/tx/TxProgress.d.ts +13 -0
  81. package/dist/tx/TxProgress.js +22 -0
  82. package/dist/tx/index.d.ts +1 -0
  83. package/dist/tx/index.js +1 -0
  84. package/dist/tx/tx.css +9 -0
  85. package/dist/ui/index.d.ts +1 -1
  86. package/dist/ui/index.js +1 -1
  87. package/dist/ui/ui.css +7 -0
  88. package/dist/ui/ui.d.ts +13 -0
  89. package/dist/ui/ui.js +24 -0
  90. package/dist/user/UserMenu.d.ts +10 -0
  91. package/dist/user/UserMenu.js +18 -0
  92. package/dist/user/index.d.ts +2 -0
  93. package/dist/user/index.js +2 -0
  94. package/dist/user/useUser.d.ts +32 -0
  95. package/dist/user/useUser.js +36 -0
  96. package/dist/user/user.css +15 -0
  97. package/dist/web3/TokenAmount.d.ts +16 -0
  98. package/dist/web3/TokenAmount.js +17 -0
  99. package/dist/web3/index.d.ts +1 -0
  100. package/dist/web3/index.js +1 -0
  101. package/dist/web3/web3.css +4 -0
  102. package/package.json +1 -1
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./polymarket.css";
3
+ import { OddsBar } from "./OddsBar";
4
+ import { formatUsd } from "../format";
5
+ import { cx } from "../util";
6
+ // A compact prediction-market card: icon, question, an odds bar, and 24h volume.
7
+ // Neutral; restyle via .kit-pm-card.
8
+ export function MarketCard({ market, onSelect, hideStats, className }) {
9
+ const interactive = Boolean(onSelect);
10
+ return (_jsxs("div", { className: cx("kit-pm-card", interactive && "kit-pm-card--interactive", className), onClick: onSelect ? () => onSelect(market) : undefined, role: interactive ? "button" : undefined, tabIndex: interactive ? 0 : undefined, onKeyDown: interactive
11
+ ? (e) => {
12
+ if (e.key === "Enter" || e.key === " ")
13
+ onSelect(market);
14
+ }
15
+ : undefined, children: [_jsxs("div", { className: "kit-pm-card-head", children: [market.icon ? _jsx("img", { className: "kit-pm-card-icon", src: market.icon, alt: "" }) : null, _jsx("span", { className: "kit-pm-card-q", children: market.question })] }), _jsx(OddsBar, { market: market }), !hideStats && (_jsxs("div", { className: "kit-pm-card-stats", children: [_jsxs("span", { children: [formatUsd(market.vol24h), " 24h"] }), market.category ? _jsx("span", { className: "kit-pm-card-cat", children: market.category }) : null] }))] }));
16
+ }
@@ -0,0 +1,13 @@
1
+ import "./polymarket.css";
2
+ import { type PmQueryOptions, type UseMarketsParams } from "./usePolymarket";
3
+ import type { PmMarket } from "./client";
4
+ export interface MarketsListProps extends UseMarketsParams {
5
+ /** Called when a market card is clicked. */
6
+ onSelect?: (market: PmMarket) => void;
7
+ /** Show a search box that filters by question/slug. Default true. */
8
+ search?: boolean;
9
+ /** Poll/query options (feedUrl, refetchInterval, …). */
10
+ queryOptions?: PmQueryOptions;
11
+ className?: string;
12
+ }
13
+ export declare function MarketsList({ onSelect, search, queryOptions, className, ...filter }: MarketsListProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./polymarket.css";
3
+ import { useState } from "react";
4
+ import { useMarkets } from "./usePolymarket";
5
+ import { MarketCard } from "./MarketCard";
6
+ import { cx } from "../util";
7
+ // A live, searchable list of prediction markets from Livo's Polymarket indexer.
8
+ // Neutral; restyle via .kit-pm-list.
9
+ export function MarketsList({ onSelect, search = true, queryOptions, className, ...filter }) {
10
+ const [q, setQ] = useState("");
11
+ const { data, isLoading, error } = useMarkets({ ...filter, q: q || filter.q }, queryOptions);
12
+ return (_jsxs("div", { className: cx("kit-pm-list", className), children: [search && (_jsx("input", { className: "kit-pm-search", placeholder: "Search markets\u2026", value: q, onChange: (e) => setQ(e.target.value) })), error ? (_jsx("div", { className: "kit-pm-empty", children: "Couldn\u2019t load markets." })) : isLoading && !data ? (_jsx("div", { className: "kit-pm-empty", children: "Loading markets\u2026" })) : !data?.length ? (_jsx("div", { className: "kit-pm-empty", children: "No markets." })) : (_jsx("div", { className: "kit-pm-grid", children: data.map((m) => (_jsx(MarketCard, { market: m, onSelect: onSelect }, m.conditionId))) }))] }));
13
+ }
@@ -0,0 +1,9 @@
1
+ import "./polymarket.css";
2
+ import type { PmMarket } from "./client";
3
+ export interface OddsBarProps {
4
+ market: Pick<PmMarket, "tokens">;
5
+ /** Show a numeric % label on each segment. Default true. */
6
+ showLabels?: boolean;
7
+ className?: string;
8
+ }
9
+ export declare function OddsBar({ market, showLabels, className }: OddsBarProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import "./polymarket.css";
3
+ import { cx } from "../util";
4
+ // A horizontal probability bar — one segment per outcome, width = implied
5
+ // probability (the token mid, 0–1). Neutral; restyle via .kit-pm-odds[data-i].
6
+ export function OddsBar({ market, showLabels = true, className }) {
7
+ const tokens = market.tokens ?? [];
8
+ const total = tokens.reduce((s, t) => s + (t.mid || 0), 0) || 1;
9
+ return (_jsx("div", { className: cx("kit-pm-odds", className), role: "img", "aria-label": "market odds", children: tokens.map((t, i) => {
10
+ const pct = ((t.mid || 0) / total) * 100;
11
+ return (_jsx("span", { className: "kit-pm-odds-seg", "data-i": i, style: { width: `${pct}%` }, children: showLabels && pct > 12 && (_jsxs("span", { className: "kit-pm-odds-lbl", children: [t.outcome, " ", Math.round((t.mid || 0) * 100), "%"] })) }, t.tokenId));
12
+ }) }));
13
+ }
@@ -0,0 +1,12 @@
1
+ import "./polymarket.css";
2
+ import { type UsePlaceOrderOptions } from "./usePolymarket";
3
+ import type { PmMarket } from "./client";
4
+ export interface OrderTicketProps {
5
+ market: PmMarket;
6
+ /** Where to POST the signed order (your api/ Worker). See usePlaceOrder. */
7
+ order?: UsePlaceOrderOptions;
8
+ /** Called with the backend response after a successful placement. */
9
+ onPlaced?: (result: unknown) => void;
10
+ className?: string;
11
+ }
12
+ export declare function OrderTicket({ market, order, onPlaced, className }: OrderTicketProps): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./polymarket.css";
3
+ import { useState } from "react";
4
+ import { usePlaceOrder } from "./usePolymarket";
5
+ import { cx } from "../util";
6
+ // A minimal buy/sell ticket for one market. Picks an outcome + side + price + size,
7
+ // then POSTs to YOUR backend (which signs server-side — no key in the browser).
8
+ // Neutral; restyle via .kit-pm-ticket.
9
+ export function OrderTicket({ market, order, onPlaced, className }) {
10
+ const tokens = market.tokens ?? [];
11
+ const [tokenId, setTokenId] = useState(tokens[0]?.tokenId ?? "");
12
+ const [side, setSide] = useState("BUY");
13
+ const [price, setPrice] = useState(() => round2(tokens[0]?.mid || 0.5));
14
+ const [size, setSize] = useState(10);
15
+ const place = usePlaceOrder(order);
16
+ const selected = tokens.find((t) => t.tokenId === tokenId);
17
+ const cost = side === "BUY" ? price * size : 0;
18
+ const submit = async () => {
19
+ const res = await place.mutateAsync({ tokenId, side, price, size });
20
+ onPlaced?.(res);
21
+ };
22
+ return (_jsxs("div", { className: cx("kit-pm-ticket", className), children: [_jsx("div", { className: "kit-pm-ticket-q", children: market.question }), _jsx("div", { className: "kit-pm-ticket-outcomes", children: tokens.map((t) => (_jsxs("button", { type: "button", className: cx("kit-pm-outcome", t.tokenId === tokenId && "kit-pm-outcome--on"), onClick: () => {
23
+ setTokenId(t.tokenId);
24
+ setPrice(round2(t.mid || 0.5));
25
+ }, children: [_jsx("span", { children: t.outcome }), _jsxs("span", { className: "kit-pm-outcome-px", children: [Math.round((t.mid || 0) * 100), "\u00A2"] })] }, t.tokenId))) }), _jsx("div", { className: "kit-pm-ticket-side", children: ["BUY", "SELL"].map((s) => (_jsx("button", { type: "button", className: cx("kit-pm-sidebtn", side === s && `kit-pm-sidebtn--${s.toLowerCase()}`), onClick: () => setSide(s), children: s }, s))) }), _jsxs("label", { className: "kit-pm-field", children: [_jsx("span", { children: "Price (\u00A2)" }), _jsx("input", { type: "number", min: 0.01, max: 0.99, step: 0.01, value: price, onChange: (e) => setPrice(clamp(Number(e.target.value), 0.01, 0.99)) })] }), _jsxs("label", { className: "kit-pm-field", children: [_jsx("span", { children: "Shares" }), _jsx("input", { type: "number", min: 1, step: 1, value: size, onChange: (e) => setSize(Math.max(1, Number(e.target.value))) })] }), _jsxs("button", { type: "button", className: "kit-pm-submit", disabled: !selected || place.isPending, onClick: submit, children: [place.isPending ? "Placing…" : `${side} ${size} ${selected?.outcome ?? ""}`, side === "BUY" ? ` · ~$${cost.toFixed(2)}` : ""] }), place.error ? _jsx("div", { className: "kit-pm-ticket-err", children: place.error.message }) : null] }));
26
+ }
27
+ const round2 = (n) => Math.round(n * 100) / 100;
28
+ const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
@@ -0,0 +1,73 @@
1
+ export declare const POLYMARKET_FEED = "https://polymarket.livo.build";
2
+ export declare const CLOB_HOST = "https://clob.polymarket.com";
3
+ export interface PmFeedOptions {
4
+ /** Override the indexer base URL. */
5
+ feedUrl?: string;
6
+ /** Override the CLOB host (depth + price history). */
7
+ clobHost?: string;
8
+ }
9
+ export interface PmTokenQuote {
10
+ outcome: string;
11
+ tokenId: string;
12
+ bid: number;
13
+ ask: number;
14
+ mid: number;
15
+ last: number;
16
+ }
17
+ export interface PmMarket {
18
+ conditionId: string;
19
+ slug: string;
20
+ question: string;
21
+ icon: string;
22
+ category: string;
23
+ endDate: string;
24
+ negRisk: boolean;
25
+ active: boolean;
26
+ vol24h: number;
27
+ liquidity: number;
28
+ tokens: PmTokenQuote[];
29
+ }
30
+ export interface PmTrade {
31
+ id: string;
32
+ conditionId: string;
33
+ slug: string;
34
+ question: string;
35
+ tokenId: string;
36
+ outcome: string;
37
+ side: string;
38
+ price: number;
39
+ size: number;
40
+ usd: number;
41
+ ago: number;
42
+ }
43
+ export interface PmSnapshot {
44
+ caught_up: boolean;
45
+ markets_tracked: number;
46
+ markets: PmMarket[];
47
+ trades: PmTrade[];
48
+ [k: string]: unknown;
49
+ }
50
+ export interface PmBookLevel {
51
+ price: string;
52
+ size: string;
53
+ }
54
+ export interface PmBook {
55
+ market?: string;
56
+ asset_id?: string;
57
+ bids: PmBookLevel[];
58
+ asks: PmBookLevel[];
59
+ [k: string]: unknown;
60
+ }
61
+ export interface PmHistoryPoint {
62
+ t: number;
63
+ p: number;
64
+ }
65
+ /** The full indexer snapshot. */
66
+ export declare function pmSnapshot(opts?: PmFeedOptions): Promise<PmSnapshot>;
67
+ /** Order-book depth for an outcome token (straight from the public CLOB). */
68
+ export declare function pmBook(tokenId: string, opts?: PmFeedOptions): Promise<PmBook>;
69
+ /** Price history points for charting an outcome token (public CLOB). */
70
+ export declare function pmPriceHistory(tokenId: string, params?: {
71
+ interval?: "1h" | "6h" | "1d" | "1w" | "max";
72
+ fidelity?: number;
73
+ }, opts?: PmFeedOptions): Promise<PmHistoryPoint[]>;
@@ -0,0 +1,31 @@
1
+ // Frontend-safe Polymarket client. Reads Livo's Polymarket indexer
2
+ // (polymarket.livo.build) for the normalized markets + odds + trades feed, and the
3
+ // public Polymarket CLOB for order-book depth + price history (both CORS-open, no
4
+ // key). For TRADING, route orders through your api/ Worker (usePlaceOrder), which
5
+ // signs server-side with @livo-build/runtime — never ship a trading key to the browser.
6
+ export const POLYMARKET_FEED = "https://polymarket.livo.build";
7
+ export const CLOB_HOST = "https://clob.polymarket.com";
8
+ const feedBase = (o) => (o?.feedUrl ?? POLYMARKET_FEED).replace(/\/$/, "");
9
+ const clobBase = (o) => (o?.clobHost ?? CLOB_HOST).replace(/\/$/, "");
10
+ async function getJson(url) {
11
+ const res = await fetch(url, { headers: { accept: "application/json" } });
12
+ if (!res.ok)
13
+ throw new Error(`Polymarket ${res.status} for ${url}`);
14
+ return (await res.json());
15
+ }
16
+ /** The full indexer snapshot. */
17
+ export function pmSnapshot(opts) {
18
+ return getJson(`${feedBase(opts)}/data`);
19
+ }
20
+ /** Order-book depth for an outcome token (straight from the public CLOB). */
21
+ export function pmBook(tokenId, opts) {
22
+ return getJson(`${clobBase(opts)}/book?token_id=${encodeURIComponent(tokenId)}`);
23
+ }
24
+ /** Price history points for charting an outcome token (public CLOB). */
25
+ export async function pmPriceHistory(tokenId, params = {}, opts) {
26
+ const qs = new URLSearchParams({ market: tokenId, interval: params.interval ?? "1w" });
27
+ if (params.fidelity)
28
+ qs.set("fidelity", String(params.fidelity));
29
+ const j = await getJson(`${clobBase(opts)}/prices-history?${qs.toString()}`);
30
+ return j.history ?? [];
31
+ }
@@ -0,0 +1,6 @@
1
+ export { POLYMARKET_FEED, CLOB_HOST, pmSnapshot, pmBook, pmPriceHistory, type PmFeedOptions, type PmMarket, type PmTokenQuote, type PmTrade, type PmSnapshot, type PmBook, type PmBookLevel, type PmHistoryPoint, } from "./client";
2
+ export { useMarkets, useMarket, useMarketTrades, useOrderbook, usePriceHistory, usePlaceOrder, type PmQueryOptions, type PmResult, type UseMarketsParams, type UsePriceHistoryParams, type PlaceOrderInput, type UsePlaceOrderOptions, } from "./usePolymarket";
3
+ export { OddsBar, type OddsBarProps } from "./OddsBar";
4
+ export { MarketCard, type MarketCardProps } from "./MarketCard";
5
+ export { MarketsList, type MarketsListProps } from "./MarketsList";
6
+ export { OrderTicket, type OrderTicketProps } from "./OrderTicket";
@@ -0,0 +1,10 @@
1
+ // Polymarket — prediction-market building blocks. Reads Livo's Polymarket indexer
2
+ // (polymarket.livo.build) for live markets/odds/trades + the public CLOB for depth +
3
+ // history. Trading routes through YOUR api/ Worker (usePlaceOrder) so the key stays
4
+ // server-side. Pairs with @livo-build/runtime's `polymarket(env)` + `Polymarket`.
5
+ export { POLYMARKET_FEED, CLOB_HOST, pmSnapshot, pmBook, pmPriceHistory, } from "./client";
6
+ export { useMarkets, useMarket, useMarketTrades, useOrderbook, usePriceHistory, usePlaceOrder, } from "./usePolymarket";
7
+ export { OddsBar } from "./OddsBar";
8
+ export { MarketCard } from "./MarketCard";
9
+ export { MarketsList } from "./MarketsList";
10
+ export { OrderTicket } from "./OrderTicket";
@@ -0,0 +1,49 @@
1
+ /* Polymarket components. Neutral; restyle via --kit-* vars or the .kit-pm-* classes. */
2
+
3
+ /* Odds bar */
4
+ .kit-pm-odds{display:flex;width:100%;height:22px;border-radius:6px;overflow:hidden;background:var(--kit-surface,#f1f5f9)}
5
+ .kit-pm-odds-seg{display:flex;align-items:center;min-width:0;padding:0 6px;font-size:.75rem;font-weight:600;color:#fff;white-space:nowrap;overflow:hidden}
6
+ .kit-pm-odds-seg[data-i="0"]{background:var(--kit-accent,#2563eb)}
7
+ .kit-pm-odds-seg[data-i="1"]{background:var(--kit-muted,#64748b)}
8
+ .kit-pm-odds-seg[data-i="2"]{background:#a855f7}
9
+ .kit-pm-odds-seg[data-i="3"]{background:#f59e0b}
10
+ .kit-pm-odds-lbl{overflow:hidden;text-overflow:ellipsis}
11
+
12
+ /* Market card */
13
+ .kit-pm-card{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid var(--kit-border,#e2e8f0);border-radius:12px;background:var(--kit-bg,#fff)}
14
+ .kit-pm-card--interactive{cursor:pointer;transition:border-color .15s,box-shadow .15s}
15
+ .kit-pm-card--interactive:hover{border-color:var(--kit-accent,#2563eb);box-shadow:0 1px 8px rgba(0,0,0,.06)}
16
+ .kit-pm-card-head{display:flex;align-items:center;gap:10px}
17
+ .kit-pm-card-icon{width:28px;height:28px;border-radius:6px;object-fit:cover;flex:none}
18
+ .kit-pm-card-q{font-weight:600;line-height:1.3}
19
+ .kit-pm-card-stats{display:flex;justify-content:space-between;font-size:.8rem;color:var(--kit-muted,#64748b)}
20
+ .kit-pm-card-cat{text-transform:uppercase;letter-spacing:.03em;font-size:.7rem}
21
+
22
+ /* Markets list */
23
+ .kit-pm-list{display:flex;flex-direction:column;gap:12px}
24
+ .kit-pm-search{padding:10px 12px;border:1px solid var(--kit-border,#e2e8f0);border-radius:10px;font:inherit;background:var(--kit-bg,#fff);color:inherit}
25
+ .kit-pm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}
26
+ .kit-pm-empty{padding:24px;text-align:center;color:var(--kit-muted,#64748b)}
27
+
28
+ /* Order ticket */
29
+ .kit-pm-ticket{display:flex;flex-direction:column;gap:10px;padding:16px;border:1px solid var(--kit-border,#e2e8f0);border-radius:12px;background:var(--kit-bg,#fff);max-width:360px}
30
+ .kit-pm-ticket-q{font-weight:600;line-height:1.3}
31
+ .kit-pm-ticket-outcomes{display:flex;gap:8px;flex-wrap:wrap}
32
+ .kit-pm-outcome{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:8px 12px;border:1px solid var(--kit-border,#e2e8f0);border-radius:8px;background:transparent;cursor:pointer;font:inherit;color:inherit}
33
+ .kit-pm-outcome--on{border-color:var(--kit-accent,#2563eb);background:var(--kit-surface,#eff6ff)}
34
+ .kit-pm-outcome-px{font-size:.8rem;color:var(--kit-muted,#64748b);font-weight:700}
35
+ .kit-pm-ticket-side{display:flex;gap:8px}
36
+ .kit-pm-sidebtn{flex:1;padding:8px;border:1px solid var(--kit-border,#e2e8f0);border-radius:8px;background:transparent;cursor:pointer;font:inherit;font-weight:600;color:inherit}
37
+ .kit-pm-sidebtn--buy{border-color:var(--kit-up,#15803d);color:var(--kit-up,#15803d)}
38
+ .kit-pm-sidebtn--sell{border-color:var(--kit-down,#b91c1c);color:var(--kit-down,#b91c1c)}
39
+ .kit-pm-field{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:.85rem;color:var(--kit-muted,#64748b)}
40
+ .kit-pm-field input{width:120px;padding:6px 8px;border:1px solid var(--kit-border,#e2e8f0);border-radius:8px;font:inherit;background:var(--kit-bg,#fff);color:inherit;text-align:right}
41
+ .kit-pm-submit{padding:10px;border:none;border-radius:10px;background:var(--kit-accent,#2563eb);color:#fff;font:inherit;font-weight:700;cursor:pointer}
42
+ .kit-pm-submit:disabled{opacity:.55;cursor:default}
43
+ .kit-pm-ticket-err{color:var(--kit-down,#b91c1c);font-size:.8rem}
44
+
45
+ @media (prefers-color-scheme:dark){
46
+ .kit-pm-odds{background:var(--kit-surface,#1e293b)}
47
+ .kit-pm-card,.kit-pm-ticket,.kit-pm-search,.kit-pm-field input{background:var(--kit-bg,#0f172a);border-color:var(--kit-border,#1e293b)}
48
+ .kit-pm-outcome--on{background:var(--kit-surface,#1e293b)}
49
+ }
@@ -0,0 +1,59 @@
1
+ import { type PmFeedOptions, type PmMarket, type PmTrade, type PmBook, type PmHistoryPoint } from "./client";
2
+ export interface PmQueryOptions extends PmFeedOptions {
3
+ /** Poll interval in ms (default 6000). Pass false to fetch once. */
4
+ refetchInterval?: number | false;
5
+ enabled?: boolean;
6
+ }
7
+ export interface PmResult<T> {
8
+ data: T | undefined;
9
+ error: Error | null;
10
+ isLoading: boolean;
11
+ refetch: () => void;
12
+ }
13
+ export interface UseMarketsParams {
14
+ /** Min 24h volume (USD). */
15
+ minVol24h?: number;
16
+ /** Substring match on question/slug. */
17
+ q?: string;
18
+ category?: string;
19
+ limit?: number;
20
+ }
21
+ /** Live markets from the indexer, hottest first, filtered client-side. Polls. */
22
+ export declare function useMarkets(params?: UseMarketsParams, opts?: PmQueryOptions): PmResult<PmMarket[]>;
23
+ /** One market by slug or conditionId. */
24
+ export declare function useMarket(slugOrConditionId?: string, opts?: PmQueryOptions): PmResult<PmMarket | undefined>;
25
+ /** Recent trades firehose from the indexer. */
26
+ export declare function useMarketTrades(params?: {
27
+ slug?: string;
28
+ minUsd?: number;
29
+ limit?: number;
30
+ }, opts?: PmQueryOptions): PmResult<PmTrade[]>;
31
+ /** Order-book depth for an outcome token (public CLOB). */
32
+ export declare function useOrderbook(tokenId?: string, opts?: PmQueryOptions): PmResult<PmBook>;
33
+ export interface UsePriceHistoryParams {
34
+ tokenId: string;
35
+ interval?: "1h" | "6h" | "1d" | "1w" | "max";
36
+ fidelity?: number;
37
+ }
38
+ /** Price history points (0–1) for charting an outcome token. */
39
+ export declare function usePriceHistory(params: UsePriceHistoryParams, opts?: PmQueryOptions): PmResult<PmHistoryPoint[]>;
40
+ export interface PlaceOrderInput {
41
+ tokenId: string;
42
+ side: "BUY" | "SELL";
43
+ price: number;
44
+ size: number;
45
+ orderType?: "GTC" | "FOK" | "GTD" | "FAK";
46
+ }
47
+ export interface UsePlaceOrderOptions {
48
+ /** Your api/ Worker endpoint that signs + relays the order server-side.
49
+ * Default "/api/polymarket/order". */
50
+ endpoint?: string;
51
+ /** Extra headers (e.g. an auth/session token). */
52
+ headers?: Record<string, string>;
53
+ }
54
+ /**
55
+ * Place a Polymarket order by POSTing to YOUR backend (an api/ Worker that signs with
56
+ * @livo-build/runtime's `Polymarket` and routes through Livo's builder-relay). The
57
+ * trading key NEVER touches the browser. Returns the backend's JSON response.
58
+ */
59
+ export declare function usePlaceOrder(opts?: UsePlaceOrderOptions): import("@tanstack/react-query").UseMutationResult<unknown, Error, PlaceOrderInput, unknown>;
@@ -0,0 +1,97 @@
1
+ import { useQuery, useMutation } from "@tanstack/react-query";
2
+ import { pmSnapshot, pmBook, pmPriceHistory, } 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
+ /** Live markets from the indexer, hottest first, filtered client-side. Polls. */
8
+ export function useMarkets(params = {}, opts = {}) {
9
+ const q = wrap(useQuery({
10
+ queryKey: ["pm-markets", opts.feedUrl],
11
+ queryFn: async () => (await pmSnapshot(opts)).markets ?? [],
12
+ refetchInterval: opts.refetchInterval ?? DEFAULT_POLL,
13
+ enabled: opts.enabled ?? true,
14
+ }));
15
+ let rows = q.data;
16
+ if (rows) {
17
+ if (params.minVol24h != null)
18
+ rows = rows.filter((m) => m.vol24h >= params.minVol24h);
19
+ if (params.category)
20
+ rows = rows.filter((m) => m.category.toLowerCase() === params.category.toLowerCase());
21
+ if (params.q) {
22
+ const needle = params.q.toLowerCase();
23
+ rows = rows.filter((m) => m.question.toLowerCase().includes(needle) || m.slug.toLowerCase().includes(needle));
24
+ }
25
+ if (params.limit != null)
26
+ rows = rows.slice(0, params.limit);
27
+ }
28
+ return { ...q, data: rows };
29
+ }
30
+ /** One market by slug or conditionId. */
31
+ export function useMarket(slugOrConditionId, opts = {}) {
32
+ const q = useMarkets({}, { ...opts, enabled: opts.enabled ?? Boolean(slugOrConditionId) });
33
+ const norm = (s) => s.replace(/^0x/, "").toLowerCase();
34
+ const market = slugOrConditionId
35
+ ? q.data?.find((m) => m.slug.toLowerCase() === slugOrConditionId.toLowerCase() || norm(m.conditionId) === norm(slugOrConditionId))
36
+ : undefined;
37
+ return { data: market, error: q.error, isLoading: q.isLoading, refetch: q.refetch };
38
+ }
39
+ /** Recent trades firehose from the indexer. */
40
+ export function useMarketTrades(params = {}, opts = {}) {
41
+ const q = wrap(useQuery({
42
+ queryKey: ["pm-trades", opts.feedUrl],
43
+ queryFn: async () => (await pmSnapshot(opts)).trades ?? [],
44
+ refetchInterval: opts.refetchInterval ?? DEFAULT_POLL,
45
+ enabled: opts.enabled ?? true,
46
+ }));
47
+ let rows = q.data;
48
+ if (rows) {
49
+ if (params.slug)
50
+ rows = rows.filter((t) => t.slug === params.slug);
51
+ if (params.minUsd != null)
52
+ rows = rows.filter((t) => t.usd >= params.minUsd);
53
+ if (params.limit != null)
54
+ rows = rows.slice(0, params.limit);
55
+ }
56
+ return { ...q, data: rows };
57
+ }
58
+ /** Order-book depth for an outcome token (public CLOB). */
59
+ export function useOrderbook(tokenId, opts = {}) {
60
+ return wrap(useQuery({
61
+ queryKey: ["pm-book", tokenId, opts.clobHost],
62
+ queryFn: () => pmBook(tokenId, opts),
63
+ refetchInterval: opts.refetchInterval ?? 3000,
64
+ enabled: (opts.enabled ?? true) && Boolean(tokenId),
65
+ }));
66
+ }
67
+ /** Price history points (0–1) for charting an outcome token. */
68
+ export function usePriceHistory(params, opts = {}) {
69
+ const { tokenId, interval = "1w", fidelity } = params;
70
+ return wrap(useQuery({
71
+ queryKey: ["pm-history", tokenId, interval, fidelity, opts.clobHost],
72
+ queryFn: () => pmPriceHistory(tokenId, { interval, fidelity }, opts),
73
+ refetchInterval: opts.refetchInterval ?? 30000,
74
+ enabled: (opts.enabled ?? true) && Boolean(tokenId),
75
+ }));
76
+ }
77
+ /**
78
+ * Place a Polymarket order by POSTing to YOUR backend (an api/ Worker that signs with
79
+ * @livo-build/runtime's `Polymarket` and routes through Livo's builder-relay). The
80
+ * trading key NEVER touches the browser. Returns the backend's JSON response.
81
+ */
82
+ export function usePlaceOrder(opts = {}) {
83
+ const endpoint = opts.endpoint ?? "/api/polymarket/order";
84
+ return useMutation({
85
+ mutationFn: async (order) => {
86
+ const res = await fetch(endpoint, {
87
+ method: "POST",
88
+ headers: { "content-type": "application/json", ...opts.headers },
89
+ body: JSON.stringify(order),
90
+ });
91
+ const body = await res.json().catch(() => ({}));
92
+ if (!res.ok)
93
+ throw new Error(body.error || `order failed: HTTP ${res.status}`);
94
+ return body;
95
+ },
96
+ });
97
+ }
@@ -1,3 +1,5 @@
1
1
  export { useTelegramMiniApp, type TelegramMiniApp } from "./useTelegramMiniApp";
2
2
  export { useTelegramLink, type TelegramLinkState, type LinkStatus, type LinkedTelegram, type UseTelegramLinkOptions, } from "./useTelegramLink";
3
3
  export { LinkTelegramButton, type LinkTelegramButtonProps } from "./LinkTelegramButton";
4
+ export { useTelegramTheme } from "./useTelegramTheme";
5
+ export { useTelegramMainButton, useTelegramBackButton, useTelegramHaptics, type MainButtonOptions, type Haptics, } from "./miniapp";
@@ -1,3 +1,5 @@
1
1
  export { useTelegramMiniApp } from "./useTelegramMiniApp";
2
2
  export { useTelegramLink, } from "./useTelegramLink";
3
3
  export { LinkTelegramButton } from "./LinkTelegramButton";
4
+ export { useTelegramTheme } from "./useTelegramTheme";
5
+ export { useTelegramMainButton, useTelegramBackButton, useTelegramHaptics, } from "./miniapp";
@@ -0,0 +1,15 @@
1
+ export interface MainButtonOptions {
2
+ text: string;
3
+ onClick: () => void;
4
+ show?: boolean;
5
+ enabled?: boolean;
6
+ progress?: boolean;
7
+ }
8
+ export declare function useTelegramMainButton(opts: MainButtonOptions): void;
9
+ export declare function useTelegramBackButton(onClick: () => void, show?: boolean): void;
10
+ export interface Haptics {
11
+ impact: (style?: "light" | "medium" | "heavy" | "rigid" | "soft") => void;
12
+ notification: (type?: "error" | "success" | "warning") => void;
13
+ selection: () => void;
14
+ }
15
+ export declare function useTelegramHaptics(): Haptics;
@@ -0,0 +1,63 @@
1
+ import { useEffect, useRef } from "react";
2
+ function webApp() {
3
+ if (typeof window === "undefined")
4
+ return undefined;
5
+ return window.Telegram?.WebApp;
6
+ }
7
+ // Drive the Telegram Mini App MainButton from React. No-op outside Telegram.
8
+ export function useTelegramMainButton(opts) {
9
+ const cbRef = useRef(opts.onClick);
10
+ cbRef.current = opts.onClick;
11
+ useEffect(() => {
12
+ const mb = webApp()?.MainButton;
13
+ if (!mb)
14
+ return;
15
+ const handler = () => cbRef.current();
16
+ mb.setText(opts.text);
17
+ if (opts.enabled === false)
18
+ mb.disable();
19
+ else
20
+ mb.enable();
21
+ if (opts.progress)
22
+ mb.showProgress?.();
23
+ else
24
+ mb.hideProgress?.();
25
+ if (opts.show === false)
26
+ mb.hide();
27
+ else
28
+ mb.show();
29
+ mb.onClick(handler);
30
+ return () => {
31
+ mb.offClick(handler);
32
+ mb.hide();
33
+ };
34
+ }, [opts.text, opts.show, opts.enabled, opts.progress]);
35
+ }
36
+ // Drive the Telegram Mini App BackButton. No-op outside Telegram.
37
+ export function useTelegramBackButton(onClick, show = true) {
38
+ const cbRef = useRef(onClick);
39
+ cbRef.current = onClick;
40
+ useEffect(() => {
41
+ const bb = webApp()?.BackButton;
42
+ if (!bb)
43
+ return;
44
+ const handler = () => cbRef.current();
45
+ bb.onClick(handler);
46
+ if (show)
47
+ bb.show();
48
+ else
49
+ bb.hide();
50
+ return () => {
51
+ bb.offClick(handler);
52
+ bb.hide();
53
+ };
54
+ }, [show]);
55
+ }
56
+ // Telegram haptic feedback helpers. Safely no-op outside Telegram.
57
+ export function useTelegramHaptics() {
58
+ return {
59
+ impact: (style = "medium") => webApp()?.HapticFeedback?.impactOccurred(style),
60
+ notification: (type = "success") => webApp()?.HapticFeedback?.notificationOccurred(type),
61
+ selection: () => webApp()?.HapticFeedback?.selectionChanged(),
62
+ };
63
+ }
@@ -0,0 +1,2 @@
1
+ import { type KitTheme } from "../theme";
2
+ export declare function useTelegramTheme(): KitTheme | undefined;
@@ -0,0 +1,20 @@
1
+ import {} from "../theme";
2
+ // A KitTheme derived from the host Telegram Mini App's theme, or undefined outside
3
+ // Telegram. Pass it to <LivoApp theme={…}> / <KitThemeProvider> so the kit matches
4
+ // the user's Telegram colours automatically.
5
+ export function useTelegramTheme() {
6
+ if (typeof window === "undefined")
7
+ return undefined;
8
+ const wa = window.Telegram?.WebApp;
9
+ const tp = wa?.themeParams;
10
+ if (!tp)
11
+ return undefined;
12
+ return {
13
+ accent: tp.button_color,
14
+ accentFg: tp.button_text_color,
15
+ bg: tp.bg_color,
16
+ fg: tp.text_color,
17
+ muted: tp.hint_color,
18
+ border: tp.secondary_bg_color,
19
+ };
20
+ }
@@ -0,0 +1,13 @@
1
+ import "./tx.css";
2
+ import { type TxStatus } from "./useTx";
3
+ export interface TxProgressProps {
4
+ status: TxStatus;
5
+ hash?: `0x${string}`;
6
+ error?: Error | null;
7
+ /** Block explorer base, e.g. "https://sepolia.etherscan.io". Links the hash when set. */
8
+ explorer?: string;
9
+ className?: string;
10
+ /** Hide when idle (default true). */
11
+ hideIdle?: boolean;
12
+ }
13
+ export declare function TxProgress({ status, hash, error, explorer, className, hideIdle }: TxProgressProps): import("react").JSX.Element | null;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "./tx.css";
3
+ import {} from "./useTx";
4
+ import { shortHash } from "../format";
5
+ import { cx } from "../util";
6
+ const LABELS = {
7
+ idle: "",
8
+ pending: "Confirm in your wallet…",
9
+ confirming: "Transaction submitted, confirming…",
10
+ success: "Confirmed",
11
+ error: "Failed",
12
+ };
13
+ // A neutral inline status line for a transaction lifecycle (pairs with useTx). Style
14
+ // it via the .kit-txp* classes — see STYLING.md.
15
+ export function TxProgress({ status, hash, error, explorer, className, hideIdle = true }) {
16
+ if (status === "idle" && hideIdle)
17
+ return null;
18
+ const label = status === "error" ? error?.message ?? LABELS.error : LABELS[status];
19
+ const busy = status === "pending" || status === "confirming";
20
+ return (_jsxs("div", { className: cx("kit-txp", `kit-txp-${status}`, className), role: "status", "aria-live": "polite", children: [busy && _jsx("span", { className: "kit-txp-spinner", "aria-hidden": "true" }), _jsx("span", { className: "kit-txp-label", children: label }), hash &&
21
+ (explorer ? (_jsx("a", { className: "kit-txp-hash", href: `${explorer}/tx/${hash}`, target: "_blank", rel: "noreferrer", children: shortHash(hash) })) : (_jsx("span", { className: "kit-txp-hash", children: shortHash(hash) })))] }));
22
+ }
@@ -1,3 +1,4 @@
1
1
  export { useTx, type UseTxOptions, type UseTxResult, type TxStatus } from "./useTx";
2
2
  export { TxButton, type TxButtonProps } from "./TxButton";
3
3
  export { decodeRevert } from "./revert";
4
+ export { TxProgress, type TxProgressProps } from "./TxProgress";
package/dist/tx/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { useTx } from "./useTx";
2
2
  export { TxButton } from "./TxButton";
3
3
  export { decodeRevert } from "./revert";
4
+ export { TxProgress } from "./TxProgress";
package/dist/tx/tx.css CHANGED
@@ -11,3 +11,12 @@
11
11
  .kit-btn:hover:not(:disabled){opacity:.9}
12
12
  .kit-btn:active:not(:disabled){transform:translateY(1px)}
13
13
  @media (prefers-color-scheme:dark){.kit-btn{background:var(--kit-accent,#f5f5f7);color:var(--kit-accent-fg,#111827)}}
14
+
15
+ /* Transaction status line (TxProgress). Neutral; restyle via these classes. */
16
+ .kit-txp{display:inline-flex;align-items:center;gap:8px;font:inherit;font-size:13px;color:var(--kit-muted,#6b7280)}
17
+ .kit-txp-success{color:var(--kit-success,#15803d)}
18
+ .kit-txp-error{color:var(--kit-danger,#b91c1c)}
19
+ .kit-txp-hash{color:var(--kit-accent,#2563eb);text-decoration:none;font-variant-numeric:tabular-nums}
20
+ .kit-txp-hash:hover{text-decoration:underline}
21
+ .kit-txp-spinner{width:12px;height:12px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;animation:kit-txp-spin .7s linear infinite}
22
+ @keyframes kit-txp-spin{to{transform:rotate(360deg)}}
@@ -1 +1 @@
1
- export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Button, Badge, type DialogProps, type SkeletonProps, type EmptyStateProps, type CopyButtonProps, type ButtonProps, } from "./ui";
1
+ export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Button, Badge, Countdown, type DialogProps, type SkeletonProps, type EmptyStateProps, type CopyButtonProps, type ButtonProps, type CountdownProps, } from "./ui";
package/dist/ui/index.js CHANGED
@@ -1 +1 @@
1
- export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Button, Badge, } from "./ui";
1
+ export { Dialog, Card, Skeleton, Spinner, EmptyState, CopyButton, Button, Badge, Countdown, } from "./ui";