@livo-build/kit 0.1.6 → 0.2.1

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/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export * from "./nft";
11
11
  export * from "./dataviz";
12
12
  export * from "./user";
13
13
  export * from "./hyperliquid";
14
+ export * from "./polymarket";
14
15
  export * from "./account";
15
16
  export * from "./ui";
16
17
  export * from "./telegram";
package/dist/index.js CHANGED
@@ -20,6 +20,8 @@
20
20
  // telegram/ — useTelegramMiniApp, useTelegramLink, LinkTelegramButton, useTelegramTheme,
21
21
  // useTelegramMainButton/BackButton/Haptics (Mini App + wallet ↔ Telegram linking)
22
22
  // theme/ — KitThemeProvider (restyle the whole kit from one theme object)
23
+ // polymarket/ — useMarkets/useMarket/useMarketTrades/useOrderbook/usePriceHistory/usePlaceOrder,
24
+ // MarketsList/MarketCard/OddsBar/OrderTicket (prediction markets via Livo's indexer)
23
25
  // hooks/ — useCopyToClipboard, useLocalStorage, useDebounce, useInterval, useCountdown
24
26
  // format — shortAddress, formatToken, parseToken, formatUsd, formatPrice, timeAgo, …
25
27
  export * from "./wallet";
@@ -35,6 +37,7 @@ export * from "./nft";
35
37
  export * from "./dataviz";
36
38
  export * from "./user";
37
39
  export * from "./hyperliquid";
40
+ export * from "./polymarket";
38
41
  export * from "./account";
39
42
  export * from "./ui";
40
43
  export * from "./telegram";
@@ -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;
@@ -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,78 @@
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
+ /**
70
+ * Price history points for charting an outcome token. Prefers Livo's indexer (`/candles`,
71
+ * server-side CLOB egress on a trusted domain — works where the browser can't reach
72
+ * `clob.polymarket.com` directly), falling back to the public CLOB. Returns ascending
73
+ * `{ t, p }` points (`p` = the bucket close).
74
+ */
75
+ export declare function pmPriceHistory(tokenId: string, params?: {
76
+ interval?: "1h" | "6h" | "1d" | "1w" | "max";
77
+ fidelity?: number;
78
+ }, opts?: PmFeedOptions): Promise<PmHistoryPoint[]>;
@@ -0,0 +1,55 @@
1
+ // Frontend-safe Polymarket client. Reads Livo's Polymarket indexer
2
+ // (polymarket.livo.build) for the normalized markets + odds + trades feed AND for price
3
+ // history (`/candles`, fetched server-side from a Polymarket-permitted region) — so the
4
+ // browser never calls clob.polymarket.com directly, which fails on CORS / geoblocks /
5
+ // TLS-cert errors in some networks/regions. Order-book DEPTH (`pmBook`) still reads the
6
+ // public CLOB directly (the indexer exposes no per-token book), so it inherits those
7
+ // limits. For TRADING, route orders through your api/ Worker (usePlaceOrder), which signs
8
+ // server-side with @livo-build/runtime — never ship a trading key to the browser.
9
+ export const POLYMARKET_FEED = "https://polymarket.livo.build";
10
+ export const CLOB_HOST = "https://clob.polymarket.com";
11
+ const feedBase = (o) => (o?.feedUrl ?? POLYMARKET_FEED).replace(/\/$/, "");
12
+ const clobBase = (o) => (o?.clobHost ?? CLOB_HOST).replace(/\/$/, "");
13
+ async function getJson(url) {
14
+ const res = await fetch(url, { headers: { accept: "application/json" } });
15
+ if (!res.ok)
16
+ throw new Error(`Polymarket ${res.status} for ${url}`);
17
+ return (await res.json());
18
+ }
19
+ /** The full indexer snapshot. */
20
+ export function pmSnapshot(opts) {
21
+ return getJson(`${feedBase(opts)}/data`);
22
+ }
23
+ /** Order-book depth for an outcome token (straight from the public CLOB). */
24
+ export function pmBook(tokenId, opts) {
25
+ return getJson(`${clobBase(opts)}/book?token_id=${encodeURIComponent(tokenId)}`);
26
+ }
27
+ /** Span (seconds) of each `interval` preset — used to size the indexer `/candles` request. */
28
+ const INTERVAL_SPAN_S = { "1h": 3600, "6h": 21600, "1d": 86400, "1w": 604800, max: 0 };
29
+ /**
30
+ * Price history points for charting an outcome token. Prefers Livo's indexer (`/candles`,
31
+ * server-side CLOB egress on a trusted domain — works where the browser can't reach
32
+ * `clob.polymarket.com` directly), falling back to the public CLOB. Returns ascending
33
+ * `{ t, p }` points (`p` = the bucket close).
34
+ */
35
+ export async function pmPriceHistory(tokenId, params = {}, opts) {
36
+ const bucket = Math.max(60, (params.fidelity ?? 60) * 60); // fidelity is in MINUTES
37
+ const span = INTERVAL_SPAN_S[params.interval ?? "1w"] ?? 604800;
38
+ const limit = span > 0 ? Math.max(1, Math.min(1000, Math.round(span / bucket))) : 1000; // "max" → cap at 1000
39
+ // 1) Livo indexer (trusted domain, server-side CLOB egress).
40
+ try {
41
+ const j = await getJson(`${feedBase(opts)}/candles?token_id=${encodeURIComponent(tokenId)}&interval=${bucket}&limit=${limit}`);
42
+ const candles = j.candles ?? [];
43
+ if (candles.length)
44
+ return candles.map((c) => ({ t: c.time, p: c.c }));
45
+ }
46
+ catch {
47
+ /* indexer miss — fall back to the public CLOB below */
48
+ }
49
+ // 2) Public CLOB fallback (direct — may fail in blocked networks/regions).
50
+ const qs = new URLSearchParams({ market: tokenId, interval: params.interval ?? "1w" });
51
+ if (params.fidelity)
52
+ qs.set("fidelity", String(params.fidelity));
53
+ const j = await getJson(`${clobBase(opts)}/prices-history?${qs.toString()}`);
54
+ return j.history ?? [];
55
+ }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livo-build/kit",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Livo frontend kit — reusable React + wagmi v3 building blocks (wallet connect modal, providers, hooks) for web3 apps built on Livo.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",