diff --git a/PR_ADVANCED_VIEW.md b/PR_ADVANCED_VIEW.md new file mode 100644 index 00000000..91da62e2 --- /dev/null +++ b/PR_ADVANCED_VIEW.md @@ -0,0 +1,14 @@ +# Advanced View (PR notes) + +## Liquidity depth (how we visualize it) + +- **Depth bar:** Each segment’s width is that outcome’s **share of XLM staked in the last 24 hours**. A wider segment means more recent volume and more “room” to trade on that side before the split shifts. +- **Order book:** **Price** = implied probability from that 24h volume split. **Size** = XLM staked on that outcome in the window (shown as shares at that level). **Total** = same as size here (single level per outcome). Binary markets use green **Bid** / red **Ask** rows; 3+ outcomes list all rows sorted by price. + +## Figma + +Replace with your file: **https://www.figma.com/design/REPLACE_WITH_YOUR_FILE/advanced-view** + +## Screenshot (for reviewers) + +Capture **Advanced** toggled **on** on a market with real 24h volume and attach to the PR. diff --git a/frontend/src/app/market/[id]/MarketDetailPage.tsx b/frontend/src/app/market/[id]/MarketDetailPage.tsx index 4af6d1c3..015c7030 100644 --- a/frontend/src/app/market/[id]/MarketDetailPage.tsx +++ b/frontend/src/app/market/[id]/MarketDetailPage.tsx @@ -13,8 +13,8 @@ import { useMarket } from "../../../hooks/useMarket"; import { usePlaceBet } from "../../../hooks/usePlaceBet"; import EmptyState from "../../../components/EmptyState"; import { NoActivityIllustration, NoPositionsIllustration } from "../../../assets/emptyStates"; -import StakePresets from "../../../components/StakePresets"; import { useToast } from "../../../components/ToastProvider"; +import AdvancedLiquidityView from "../../../components/market/AdvancedLiquidityView"; // ============================================================================= // Types @@ -173,6 +173,44 @@ function Tab({ active, onClick, label }: TabProps) { ); } +function ViewModeToggle({ + advanced, + onToggle, +}: { + advanced: boolean; + onToggle: () => void; +}) { + return ( +
+ + Simple + + + + Advanced + +
+ ); +} + interface AboutTabProps { market: Market; poolSize: string; @@ -597,6 +635,7 @@ interface MarketDetailPageProps { export default function MarketDetailPage({ marketId }: MarketDetailPageProps) { const [activeTab, setActiveTab] = useState("about"); + const [advancedView, setAdvancedView] = useState(false); const { publicKey, disconnect } = useWalletContext(); // Fetch market detail via shared hook @@ -706,9 +745,12 @@ export default function MarketDetailPage({ marketId }: MarketDetailPageProps) { Ends {new Date(market.end_date).toLocaleDateString()} -

- {market.question} -

+
+

+ {market.question} +

+ setAdvancedView((v) => !v)} /> +
{/* Pool Size Banner */} @@ -736,6 +778,12 @@ export default function MarketDetailPage({ marketId }: MarketDetailPageProps) { + {advancedView && ( +
+ +
+ )} + {/* Tabs */}
diff --git a/frontend/src/components/market/AdvancedLiquidityView.tsx b/frontend/src/components/market/AdvancedLiquidityView.tsx new file mode 100644 index 00000000..98f7a93b --- /dev/null +++ b/frontend/src/components/market/AdvancedLiquidityView.tsx @@ -0,0 +1,190 @@ +"use client"; + +import { useMemo } from "react"; + +export interface DepthBet { + amount: string; + outcome_index: number; + created_at: string; +} + +const H24_MS = 24 * 60 * 60 * 1000; + +const BAR_BG = ["bg-emerald-700", "bg-rose-700", "bg-violet-700", "bg-amber-700"] as const; +const BAR_TEXT = ["text-emerald-400", "text-rose-400", "text-violet-400", "text-amber-400"] as const; + +function filterBets24h(bets: DepthBet[]): DepthBet[] { + const cutoff = Date.now() - H24_MS; + return bets.filter((b) => new Date(b.created_at).getTime() >= cutoff); +} + +function sumOutcome(bets: DepthBet[], idx: number): number { + return bets + .filter((b) => b.outcome_index === idx) + .reduce((s, b) => s + parseFloat(b.amount || "0"), 0); +} + +/** + * 24h pool depth: implied “price” = share of 24h volume on that outcome; + * “size” = XLM staked on that outcome in the window (treated like shares at that level). + */ +export default function AdvancedLiquidityView({ + bets, + outcomes, +}: { + bets: DepthBet[]; + outcomes: string[]; +}) { + const bets24 = useMemo(() => filterBets24h(bets), [bets]); + + const rows = useMemo(() => { + const vol = outcomes.map((_, i) => sumOutcome(bets24, i)); + const total = vol.reduce((a, b) => a + b, 0); + return outcomes.map((label, i) => { + const v = vol[i] ?? 0; + const price = total > 0 ? v / total : outcomes.length > 0 ? 1 / outcomes.length : 0; + return { label, outcomeIndex: i, price, size: v, totalXlm: v }; + }); + }, [bets24, outcomes]); + + const sortedHighToLow = useMemo(() => [...rows].sort((a, b) => b.price - a.price), [rows]); + const totalVol = useMemo(() => rows.reduce((s, r) => s + r.size, 0), [rows]); + + const askRow = sortedHighToLow[0]; + const bidRow = sortedHighToLow[1]; + const hasVol = totalVol > 0; + + return ( +
+
+

Liquidity depth

+ Last 24h +
+ + {/* Depth chart: buy vs sell pressure = share of 24h volume per outcome */} +
+

+ Bar width shows each outcome’s fraction of 24h volume (deeper = more liquidity at that + side). +

+
+ {rows.map((r) => { + const pct = hasVol ? (r.size / totalVol) * 100 : 100 / Math.max(rows.length, 1); + const bg = BAR_BG[r.outcomeIndex % BAR_BG.length]; + return ( +
+ {pct >= 12 ? `${pct.toFixed(0)}%` : ""} +
+ ); + })} +
+
+ {rows.map((r) => ( + + {r.label} + : {r.size.toFixed(2)} XLM + + ))} +
+
+ + {/* Order book table */} +
+
+ Order book + Implied price · 24h +
+
+ + + + + + + + + + + {!hasVol ? ( + + + + ) : outcomes.length > 2 ? ( + sortedHighToLow.map((r) => ( + + + + + + + )) + ) : ( + <> + {askRow && ( + + + + + + + )} + + + + {bidRow && ( + + + + + + + )} + + )} + +
SidePriceSize (shares)Total (XLM)
+ No fills in the last 24h — depth chart shows an even split until there is + volume. +
{r.label} + {r.price.toFixed(4)} + + {r.size.toFixed(2)} + + {r.totalXlm.toFixed(2)} +
+ Ask · {askRow.label} + + {askRow.price.toFixed(4)} + + {askRow.size.toFixed(2)} + + {askRow.totalXlm.toFixed(2)} +
+ Spread (mid) ·{" "} + {askRow && bidRow + ? ((askRow.price + bidRow.price) / 2).toFixed(4) + : "—"} +
+ Bid · {bidRow.label} + + {bidRow.price.toFixed(4)} + + {bidRow.size.toFixed(2)} + + {bidRow.totalXlm.toFixed(2)} +
+
+
+
+ ); +}