# FunctionSpace Trading SDK -- Agent Implementation Guide This document is a comprehensive implementation guide for AI coding assistants integrating the FunctionSpace Trading SDK into web applications. It covers all integration modes -- from drop-in widget embedding to headless custom UI to core-only non-React usage -- with complete, runnable code examples, type references, and explicit constraint rules. Use the decision tree in Section 3 to identify your integration mode, then follow the corresponding section. > **Note:** These packages are not yet published to npm. To use the SDK, > clone the repository and use npm workspace linking. The `npm install` > commands below are forward-looking and will work once packages are published. ## Table of Contents - [1. Overview](#1-overview) - [1.1 Machine-Readable Preamble](#11-machine-readable-preamble) - [1.2 What This SDK Does](#12-what-this-sdk-does) - [1.3 Package Dependency Chain](#13-package-dependency-chain) - [1.4 Authentication Modes Summary](#14-authentication-modes-summary) - [2. Known Limitations](#2-known-limitations) - [2.1 Limitations Table](#21-limitations-table) - [2.2 What IS Supported](#22-what-is-supported) - [3. Decision Tree: Which Integration Mode?](#3-decision-tree-which-integration-mode) - [3.1 Decision Tree](#31-decision-tree) - [3.2 Integration Mode Summary Table](#32-integration-mode-summary-table) - [4. Embedding Widgets](#4-embedding-widgets) - [4.1 Setup](#41-setup) - [4.2 Theming](#42-theming) - [4.3 Chart Widgets](#43-chart-widgets) - [4.4 Trading Widgets](#44-trading-widgets) - [4.5 Data Display Widgets](#45-data-display-widgets) - [4.6 Composition Patterns](#46-composition-patterns) - [4.7 NEVER Rules (Embedding)](#47-never-rules-embedding) - [5. Headless Custom UI](#5-headless-custom-ui) - [5.1 Setup](#51-setup) - [5.2 Data Hooks Reference](#52-data-hooks-reference) - [5.3 State & Action Hooks](#53-state--action-hooks) - [5.4 Building Beliefs](#54-building-beliefs) - [5.5 Executing Trades (Headless)](#55-executing-trades-headless) - [5.6 Previews](#56-previews) - [5.7 Context API Quick Reference](#57-context-api-quick-reference) - [5.8 NEVER Rules (Headless)](#58-never-rules-headless) - [6. Core-Only Non-React](#6-core-only-non-react) - [6.1 Setup](#61-setup) - [6.2 Reading Market Data](#62-reading-market-data) - [6.3 Building Beliefs and Trading](#63-building-beliefs-and-trading) - [6.4 Pure Math Functions (No Client Required)](#64-pure-math-functions-no-client-required) - [6.5 NEVER Rules (Core-Only)](#65-never-rules-core-only) - [7. Full App Generation](#7-full-app-generation) - [7.1 Project Scaffolding](#71-project-scaffolding) - [7.2 App Entry Point](#72-app-entry-point) - [7.3 Starter Kit: Market Dashboard](#73-starter-kit-market-dashboard) - [7.4 Starter Kit: Minimal Embed](#74-starter-kit-minimal-embed) - [7.5 Starter Kit: Headless Custom](#75-starter-kit-headless-custom) - [7.6 NEVER Rules (Full App)](#76-never-rules-full-app) - [8. Type Reference](#8-type-reference) - [8.1 Core Types](#81-core-types) - [8.2 React/UI Types](#82-reactui-types) - [8.3 Enum/Literal Types](#83-enumliteral-types) --- ## 1. Overview ### 1.1 Machine-Readable Preamble ``` SDK: FunctionSpace Trading SDK Version: 1.x (pre-release) Packages: @functionspace/core -- Pure TypeScript. API client, probability math, belief construction, trade execution. Zero framework dependencies. @functionspace/react -- React integration. Provider, hooks, theme system. Depends on @functionspace/core. @functionspace/ui -- Pre-built React widgets. Charts, trade panels, position tables. Depends on @functionspace/core and @functionspace/react. Install (full widget integration): npm install @functionspace/core @functionspace/react @functionspace/ui Install (headless React -- hooks only, no pre-built components): npm install @functionspace/core @functionspace/react Install (core only -- Node.js, non-React, server-side): npm install @functionspace/core Description: A TypeScript SDK for embedding prediction market trading widgets into web applications. Three packages form a strict dependency chain: core (pure TS math and API client) -> react (hooks, Provider, theming) -> ui (pre-built interactive components). ``` ### 1.2 What This SDK Does The FunctionSpace Trading SDK enables developers to embed prediction market trading widgets into web applications. Three npm packages provide everything from low-level probability math and API communication to fully themed, interactive React components for market visualization, belief construction, and trade execution. ### 1.3 Package Dependency Chain ``` @functionspace/core Pure TypeScript. API client, probability math, | belief construction, trade execution. No framework | dependencies. Works in Node.js, browsers, or any | JavaScript runtime. v @functionspace/react React integration layer. FunctionSpaceProvider | manages auth, theming, and shared state. Hooks | wrap core functions for React consumption. v @functionspace/ui Pre-built React widgets. ConsensusChart, TradePanel, PositionTable, ShapeCutter, and more. Fully themed and self-contained. ``` Higher layers depend on lower layers. Lower layers never import from higher layers. Install all packages that your chosen integration mode requires. See Section 3 for guidance on which packages to install. ### 1.4 Authentication Modes Summary | Mode | Config | Description | |------|--------|-------------| | **Auto-auth** | Pass `username` and `password` in the `config` object | The Provider authenticates automatically on mount; no login UI or manual token management required. | | **Interactive** | Omit `username` and `password` from `config` | Use the `AuthWidget` component or the `useAuth()` hook to present login/signup forms; the user authenticates at runtime. | | **Guest** | No credentials anywhere | Read-only access to charts and market data; trading operations (`buy`, `sell`) are blocked server-side. | **Auth model by integration mode:** React-based integrations (Sections 4, 5, 7) use Provider-managed auth -- credentials go in the `config` prop or the developer uses the `useAuth()` hook. Core-only integrations (Section 6) use manual auth -- the developer calls `loginUser()` directly and sets the token on the client with `client.setToken(token)`. --- ## 2. Known Limitations ### 2.1 Limitations Table | # | Limitation | Impact | Workaround | |---|-----------|--------|------------| | 1 | **No query caching or request deduplication.** Each hook instance fires its own independent API request. Mounting `useMarket` and `useConsensus` for the same market produces two separate network calls to the same endpoint. No `staleTime` or `gcTime` behavior exists. | Redundant API traffic scales linearly with the number of mounted hooks. Pages with multiple widgets for one market generate multiple identical requests per invalidation cycle. | Accept the redundant requests. Do not attempt to add TanStack Query, SWR, or any external caching wrapper around the SDK hooks. The internal hook API is not designed to be wrapped by external cache layers, and doing so will break invalidation coordination. | | 2 | **No real-time transport (WebSocket/SSE).** The SDK has no push-based data delivery. All data freshness depends on explicit refetch calls or polling. Changes made by other users are not automatically visible. | Market state, consensus curves, and position data go stale between manual refetches. In multi-user scenarios, the local view drifts from server state until the next refetch. | Use `useTradeHistory` with the `pollInterval` option to create a live trade feed (e.g., `useTradeHistory(marketId, { pollInterval: 3000 })`). For other data types, call `ctx.invalidate(marketId)` after local trades to trigger a refetch of all hooks. There is no polling support for hooks other than `useTradeHistory`. | | 3 | **No mutation hooks.** `buy()` and `sell()` are imported directly from `@functionspace/core`, not from `@functionspace/react`. No `useBuy` or `useSell` hooks exist. Preview functions (`previewPayoutCurve`, `previewSell`) are also core imports. | Developers building custom trade UIs must import from two packages (`@functionspace/core` for mutations and `@functionspace/react` for data hooks), manage their own `submitting`/`error` state, and manually call `ctx.invalidate(marketId)` and `ctx.refreshUser()` after every trade. | Import `buy` and `sell` from `@functionspace/core`. After a successful trade, call `ctx.invalidate(marketId)` to refresh all data hooks, then call `ctx.refreshUser()` to update the wallet balance. Pre-built UI trading components (`TradePanel`, `ShapeCutter`, `BinaryPanel`, etc.) handle mutation lifecycle internally. | | 4 | **No market search or filtering.** `discoverMarkets()` returns all available markets as a flat `MarketState[]` array. The function accepts no search, filter, sort, or pagination parameters. | Applications that need to display a subset of markets must fetch the entire market list on every call. There is no server-side filtering. | Call `discoverMarkets(client)` and filter the returned array client-side using standard `Array.filter()`, `Array.sort()`, or `Array.find()` methods. | | 5 | **Global invalidation only.** `ctx.invalidate(marketId)` accepts a `marketId` parameter, but the parameter is currently ignored. Calling `invalidate` increments a global counter that causes every mounted data-fetching hook to refetch, regardless of which market the hook watches. | In multi-market dashboards, a trade on one market triggers unnecessary refetches for all other markets. Network traffic and UI re-renders scale with the total number of mounted hooks across all markets, not just the affected market. | Accept the global refetch behavior. Do not attempt to build per-market invalidation logic on top of the SDK. The `marketId` parameter is accepted for forward-compatibility and will be respected in a future release. | | 6 | **Only one trading component at a time.** Mounting multiple trading components simultaneously (e.g., a `TradePanel` and a `ShapeCutter` side-by-side) causes conflicting writes to the shared `previewBelief` and `previewPayout` fields on `FunctionSpaceContext`. The last component to write wins, producing unpredictable chart overlays. | Chart preview overlays show the belief/payout from whichever trading component wrote most recently, not necessarily the one the user is interacting with. | Conditionally render trading components using tabs, modals, or conditional logic. Only one trading component should be mounted in the React tree at any given time. Multiple chart components and data display components can coexist without conflict. | | 7 | **Settlement not yet implemented.** The `Position` type includes `settlementPayout` and `MarketState` includes `resolvedOutcome` fields, but no settlement functions exist in the SDK. Markets can be resolved server-side, but the SDK provides no functions to trigger or process settlement. | Positions in resolved markets cannot be settled through the SDK. The `settlementPayout` field is always `null` in current responses. | Monitor the `resolutionState` field on `MarketState` for `'resolved'` or `'voided'` status. Settlement processing must be handled outside the SDK until settlement functions are added. | | 8 | **Race conditions in data hooks on rapid marketId changes.** Data-fetching hooks (useMarket, useConsensus, usePositions, useMarketHistory, useDistributionState, useBucketDistribution) do not use AbortController or generation counters. If `marketId` changes rapidly, an earlier response can resolve after a later one and overwrite it with stale data. | When switching markets quickly (e.g., clicking through a market list), displayed data may briefly show results from a previously selected market. | Avoid rapid programmatic `marketId` switching. If building a market list with click-to-select, the data will self-correct once all in-flight requests settle. | | 9 | **Missing mounted guards in data hooks.** The same data-fetching hooks listed above do not check component mount status before calling `setState`. Fast unmount/remount cycles can trigger React warnings about state updates on unmounted components. | Console warnings during fast navigation. No data corruption, but noisy logs. | These warnings are benign and do not affect functionality. They will be resolved in a future release. | ### 2.2 What IS Supported The following capabilities are fully implemented and production-ready: - **Full widget embedding with auto-theming.** Drop `ConsensusChart`, `TradePanel`, `PositionTable`, and other components into any React app. All widgets inherit the theme from `FunctionSpaceProvider` with zero manual wiring. - **Four theme presets with custom overrides.** Built-in presets: `"fs-dark"`, `"fs-light"`, `"native-dark"`, `"native-light"`. Developers can override any of the 9 core tokens or all 30 tokens for full brand customization. - **Eight belief shape generators.** `generateGaussian`, `generateRange`, `generateDip`, `generateLeftSkew`, `generateRightSkew`, `generateCustomShape`, plus the universal constructor `generateBelief` for arbitrary region composition, and `generateBellShape` for custom editor initialization. - **Chart zoom and pan.** `useChartZoom` provides scroll-wheel zoom and drag-to-pan for `ConsensusChart` and `TimelineChart`. No external zoom libraries required. - **Complete position management.** `PositionTable` with tabbed views (open orders, trade history, market positions), sell actions, pagination, and P&L display. - **Trade history with polling.** `useTradeHistory` and the `TimeSales` component support configurable `pollInterval` for near-real-time trade feeds. - **Six distinct trading interfaces.** `TradePanel` (Gaussian/range), `ShapeCutter` (8 preset shapes), `BinaryPanel` (yes/no), `BucketRangeSelector` (click-to-select buckets), `BucketTradePanel` (composed chart + bucket selector), and `CustomShapeEditor` (drag-to-draw). - **Automatic cross-component coordination.** Trading components write preview beliefs to context; chart components read and render preview overlays automatically. No prop-drilling required. - **Payout preview.** `previewPayoutCurve` previews payout curves across all outcomes before trade submission. `previewSell` previews sell proceeds for open positions. - **Complete authentication flow.** `AuthWidget` for UI-based login/signup, `useAuth` hook for programmatic auth, and auto-auth via Provider config. --- ## 3. Decision Tree: Which Integration Mode? ### 3.1 Decision Tree ``` START | Q: Do you want pre-built UI widgets (charts, trade panels, position tables)? | +-- YES | | | Q: Are you building a new app from scratch, or embedding into an existing app? | | | +-- NEW APP FROM SCRATCH | | -> Go to Section 7: Full App Generation | | Packages: @functionspace/core + @functionspace/react + @functionspace/ui | | | +-- EMBEDDING INTO EXISTING APP | -> Go to Section 4: Embedding Widgets | Packages: @functionspace/core + @functionspace/react + @functionspace/ui | +-- NO | Q: Are you using React? | +-- YES (React, but building custom UI) | -> Go to Section 5: Headless Custom UI | Packages: @functionspace/core + @functionspace/react | +-- NO (Node.js, server-side, non-React frontend) -> Go to Section 6: Core-Only Non-React Packages: @functionspace/core ``` ### 3.2 Integration Mode Summary Table | Mode | Packages | Install Command | Auth Model | Typical Use Case | |------|----------|----------------|------------|-----------------| | **Embedding Widgets** (Section 4) | `core` + `react` + `ui` | `npm install @functionspace/core @functionspace/react @functionspace/ui` | Provider-managed: pass credentials in `config` for auto-auth, or use `useAuth()` / `AuthWidget` for interactive login. | Adding prediction market charts and trading panels to an existing React application. Drop-in components with automatic theming and cross-widget coordination. | | **Headless Custom UI** (Section 5) | `core` + `react` | `npm install @functionspace/core @functionspace/react` | Provider-managed: same as Embedding Widgets. All hooks require `FunctionSpaceProvider` in the component tree. | Building a fully custom trading interface in React. Data-fetching hooks and belief generators are available; all rendering is developer-controlled. | | **Core-Only Non-React** (Section 6) | `core` | `npm install @functionspace/core` | Manual: create `FSClient`, call `loginUser()`, then call `client.setToken(token)` with the returned token. Alternatively, pass `username` and `password` to `FSClient` constructor for auto-auth via `ensureAuth()`. | Server-side scripts, Node.js bots, non-React frontends (Vue, Svelte, vanilla JS), or any JavaScript environment where React is not available. | | **Full App Generation** (Section 7) | `core` + `react` + `ui` | `npm install @functionspace/core @functionspace/react @functionspace/ui` | Provider-managed: same as Embedding Widgets. | Generating a complete Vite + React prediction market application from scratch. Starter kit templates provide a working app in a single file. | ### 3.3 Reading Guide **Read only the section indicated by the decision tree above.** Sections 4, 5, 6, and 7 are independent, self-contained recipes -- each includes its own setup, imports, code examples, and rules. Reading the other recipe sections is unnecessary and may introduce confusion from patterns that do not apply to your integration mode. - **Always read:** Sections 1-3 (overview, limitations, decision tree) and Section 8 (type reference). - **Read one of:** Section 4 (widgets), Section 5 (headless), Section 6 (core-only), or Section 7 (full app) based on the decision tree result. - **Skip the rest.** For example, if you are embedding widgets (Section 4), do not read Section 5 or 6 -- they describe different integration patterns with different imports, auth models, and conventions. --- ## 4. Embedding Widgets Self-contained recipe for dropping pre-built FunctionSpace Trading SDK components into an existing React application. Every code example in this section is complete and runnable. All imports are shown with their exact package sources. ### 4.1 Setup #### Install Packages The SDK consists of three packages in a strict dependency chain: `core` (pure TypeScript) -> `react` (hooks, provider, theming) -> `ui` (pre-built components). Install all three, plus the `recharts` peer dependency: ```bash npm install @functionspace/core @functionspace/react @functionspace/ui recharts ``` #### Provider Setup Every SDK widget must be rendered inside a `FunctionSpaceProvider`. The provider creates the API client, resolves the theme into 30 CSS custom properties, and manages shared state (auth, preview coordination, cache invalidation). No CSS imports are required from the SDK -- styles are self-contained within the components. ```tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { FunctionSpaceProvider } from '@functionspace/react'; import { ConsensusChart, TradePanel } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', }; function App() { return ( ); } ReactDOM.createRoot(document.getElementById('root')!).render( , ); ``` The chart and trade panel automatically coordinate -- moving sliders on `TradePanel` instantly shows a preview overlay on `ConsensusChart`. No prop-drilling or manual wiring is required. #### Authentication Modes The `config` object controls how authentication works. Choose one of three modes: **Auto-authenticate** -- Pass `username` and `password` in config. The provider authenticates on mount. Widgets display an "Authenticating..." placeholder until login completes. ```tsx const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', autoAuthenticate: true, }; ``` `autoAuthenticate` defaults to `true` when `username` and `password` are both present. Set `autoAuthenticate: false` explicitly to override the default. **Interactive** -- Omit credentials from config. Use the `AuthWidget` or `PasswordlessAuthWidget` component for login/signup UI. Charts render in read-only mode until the user logs in. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { AuthWidget, ConsensusChart, TradePanel } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', }; function App() { return ( ); } ``` **Guest** -- Same as interactive but without rendering `AuthWidget`. Users have read-only access to charts and market data. Trading operations are blocked. ```tsx const config = { baseUrl: 'https://your-api.example.com', }; function App() { return ( ); } ``` **Passwordless** -- Use `PasswordlessAuthWidget` for username-only authentication with auto-signup. Pass `storedUsername` to the provider to enable silent re-authentication on mount. Password-protected accounts receive a `PASSWORD_REQUIRED` error and are directed to the admin login form. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { PasswordlessAuthWidget, ConsensusChart } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { const savedUsername = localStorage.getItem('fs-username'); return ( { localStorage.setItem('fs-username', user.username); console.log(`${action}: ${user.username}`); }} /> ); } ``` #### Config Type Reference ```typescript interface FSConfig { baseUrl: string; username?: string; password?: string; autoAuthenticate?: boolean; } ``` ### 4.2 Theming #### Preset Selection The SDK ships with four theme presets. Pass a preset string to the `theme` prop on `FunctionSpaceProvider`: | Preset ID | Description | |-----------|-------------| | `"fs-dark"` | FunctionSpace branded dark theme. Blue primary, dark purple-tinted background, 2px borders, 300ms transitions. | | `"fs-light"` | FunctionSpace branded light theme. Blue primary, light gray background. | | `"native-dark"` | De-branded dark theme. Pure black background, system fonts, compact radius, 1px borders, 150ms transitions. | | `"native-light"` | De-branded light theme. White surface, system fonts, compact radius. Blends into light-themed host applications. | ```tsx {/* All widgets inherit the FS Dark theme */} ``` #### The 9 Core Tokens The theme system is built on 9 required semantic tokens. Every theme -- preset or custom -- resolves these 9 tokens, and the remaining 21 of the 30-token system are derived automatically. | Token | What It Controls | |-------|-----------------| | `primary` | Buttons, active states, consensus curve, header gradients | | `accent` | Preview lines, secondary actions, highlights | | `positive` | Profit indicators, payout curves, buy confirmations | | `negative` | Loss indicators, sell actions, error messages | | `background` | Widget and page background | | `surface` | Card and panel backgrounds | | `text` | Primary text color | | `textSecondary` | Secondary, dimmed text | | `border` | Borders and dividers | #### Custom Theme Examples **Preset string (simplest):** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; function App() { return ( {/* widgets */} ); } ``` **Preset with overrides (brand matching):** Start from a preset and override specific tokens. The remaining tokens stay at their preset values. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import type { FSThemeInput } from '@functionspace/react'; const theme: FSThemeInput = { preset: 'fs-dark', primary: '#ff6600', positive: '#00ff00', }; function App() { return ( {/* Buttons and consensus curve are orange; profit states are green */} ); } ``` **Fully custom 9-token theme (complete brand control):** When no `preset` is specified, the 9 core tokens are required. The remaining 21 tokens are derived automatically via the SDK's `applyDefaults()` function. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import type { FSThemeInput } from '@functionspace/react'; const theme: FSThemeInput = { primary: '#6366f1', accent: '#f59e0b', positive: '#22c55e', negative: '#ef4444', background: '#0a0a0a', surface: '#1a1a1a', text: '#ffffff', textSecondary: '#a0a0a0', border: '#333333', }; function App() { return ( {/* Fully custom-themed widgets */} ); } ``` #### CSS Variable Reference The provider injects all 30 theme tokens as CSS custom properties on a wrapper `
`. SDK widgets reference these properties internally. Host application CSS can also use these variables for consistent styling around SDK widgets. **Core 9:** | CSS Variable | Token | Purpose | |-------------|-------|---------| | `--fs-primary` | `primary` | Primary brand color | | `--fs-accent` | `accent` | Accent/highlight color | | `--fs-positive` | `positive` | Success/profit color | | `--fs-negative` | `negative` | Error/loss color | | `--fs-background` | `background` | Page background | | `--fs-surface` | `surface` | Card/panel background | | `--fs-text` | `text` | Primary text | | `--fs-text-secondary` | `textSecondary` | Secondary text | | `--fs-border` | `border` | Border color | **Tier 1 (derived from Core 9 if not set):** | CSS Variable | Token | Falls Back To | |-------------|-------|--------------| | `--fs-bg-secondary` | `bgSecondary` | `background` | | `--fs-surface-hover` | `surfaceHover` | `surface` | | `--fs-border-subtle` | `borderSubtle` | `border` | | `--fs-text-muted` | `textMuted` | `textSecondary` | **Tier 2 (component-specific defaults):** | CSS Variable | Token | Default | |-------------|-------|---------| | `--fs-nav-from` | `navFrom` | `background` | | `--fs-nav-to` | `navTo` | `background` | | `--fs-overlay` | `overlay` | `rgba(0,0,0,0.2)` | | `--fs-input-bg` | `inputBg` | `background` | | `--fs-code-bg` | `codeBg` | `background` | | `--fs-chart-bg` | `chartBg` | `background` | | `--fs-accent-glow` | `accentGlow` | `rgba(59,130,246,0.25)` | | `--fs-badge-bg` | `badgeBg` | `rgba(128,128,128,0.15)` | | `--fs-badge-border` | `badgeBorder` | `rgba(128,128,128,0.25)` | | `--fs-badge-text` | `badgeText` | `textSecondary` | | `--fs-logo-filter` | `logoFilter` | `none` | **Tier 3 (shape/personality):** | CSS Variable | Token | Default | |-------------|-------|---------| | `--fs-font-family` | `fontFamily` | `inherit` | | `--fs-radius-sm` | `radiusSm` | `0.375rem` | | `--fs-radius-md` | `radiusMd` | `0.75rem` | | `--fs-radius-lg` | `radiusLg` | `1rem` | | `--fs-border-width` | `borderWidth` | `1px` | | `--fs-transition-speed` | `transitionSpeed` | `200ms` | **Using CSS variables in host application styles:** ```css .my-widget-wrapper { background: var(--fs-surface); color: var(--fs-text); border: var(--fs-border-width) solid var(--fs-border); border-radius: var(--fs-radius-md); } ``` #### Chart Colors: CSS Variables vs. `ctx.chartColors` Recharts renders SVG elements. SVG `fill` and `stroke` props do not resolve CSS `var()` expressions -- they need literal color strings. The SDK solves this with a parallel `chartColors` object on context that provides concrete hex values derived from the active theme. | Element Type | Color Source | Reason | |-------------|-------------|--------| | HTML/CSS elements (backgrounds, borders, text) | CSS variables (`var(--fs-primary)`) | Standard CSS cascade | | Recharts SVG elements (line strokes, area fills, grid, axis) | `ctx.chartColors.*` (concrete hex strings) | SVG props require literal strings | The pre-built chart widgets (`ConsensusChart`, `DistributionChart`, `TimelineChart`, `MarketCharts`) handle chart color resolution internally. When building custom Recharts charts in headless mode, access chart colors via `useContext(FunctionSpaceContext).chartColors`. Key `chartColors` fields: | Field | What It Colors | |-------|---------------| | `chartColors.consensus` | Consensus curve stroke/fill | | `chartColors.previewLine` | Trade preview line stroke | | `chartColors.payout` | Payout curve color | | `chartColors.grid` | CartesianGrid stroke | | `chartColors.axisText` | Axis label/tick fill | | `chartColors.tooltipBg` | Tooltip background | | `chartColors.tooltipBorder` | Tooltip border | | `chartColors.tooltipText` | Tooltip text | | `chartColors.crosshair` | Cursor/crosshair stroke | | `chartColors.positions` | Position overlay colors (7-color array) | | `chartColors.fanBands.mean` | Timeline mean line | | `chartColors.fanBands.band25` | Narrowest CI band | | `chartColors.fanBands.band50` | Mid-inner band | | `chartColors.fanBands.band75` | Mid-outer band | | `chartColors.fanBands.band95` | Widest CI band | #### Theme Resolution Utilities Two utility functions are exported from `@functionspace/react` for advanced theme scenarios (e.g., building theme pickers, resolving colors outside the Provider, or SSR): **`resolveTheme(input?: FSThemeInput): ResolvedFSTheme`** -- Resolves a theme input (preset string, partial object, or full theme) into a complete 30-token theme object. Falls back to `FS_DARK` when input is `undefined`. **`resolveChartColors(theme: ResolvedFSTheme, presetOverrides?, customOverrides?): ChartColors`** -- Converts a resolved theme into a `ChartColors` object with concrete hex values for Recharts. Accepts optional preset and custom color overrides. ```typescript import { resolveTheme, resolveChartColors } from '@functionspace/react'; const theme = resolveTheme('fs-light'); const colors = resolveChartColors(theme); console.log(colors.consensus); // "#3b82f6" ``` --- ### 4.3 Chart Widgets All chart components must be rendered inside a `FunctionSpaceProvider`. Each chart handles its own loading and error states. Chart colors are resolved automatically from the active theme. --- #### `ConsensusChart` Probability density chart showing the current market consensus. Automatically overlays trade preview curves and selected position curves from context -- no prop wiring needed. **Import:** ```tsx import { ConsensusChart } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to visualize | | `height` | `number` | `300` | Chart height in pixels | | `overlayCurves` | `OverlayCurve[]` | -- | Additional density curves to overlay (each needs `id`, `label`, `curve`, optional `color`) | | `zoomable` | `boolean` | -- | Enable scroll-wheel zoom and drag-to-pan | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { ConsensusChart } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( ); } ``` **Notes:** - When any trading widget writes `ctx.previewBelief`, a dashed preview curve appears automatically on the chart. - When a `PositionTable` row is clicked, the position's belief renders as a colored overlay. - Payout data from `ctx.previewPayout` appears in the chart tooltip without any configuration. - The `OverlayCurve` type: `{ id: string, label: string, curve: Array<{x: number; y: number}>, color?: string }`. --- #### `DistributionChart` Horizontal bar chart showing probability mass by outcome bucket. Includes an interactive bucket count slider (range 2 to 50). **Import:** ```tsx import { DistributionChart } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to display | | `height` | `number` | `300` | Chart height in pixels | | `defaultBucketCount` | `number` | `12` | Initial number of outcome buckets (2 to 50) | | `distributionState` | `DistributionState` | -- | External shared state for syncing with `BucketRangeSelector` | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { DistributionChart } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( ); } ``` **Notes:** - The bucket count slider is built into the chart header. Users can adjust the number of outcome buckets interactively. - When `distributionState` is provided, the slider controls the shared state, keeping the chart in sync with a `BucketRangeSelector`. See Section 4.6 for the shared state pattern. - Bar opacity is proportional to probability -- the highest-probability bucket renders at full opacity, others at 0.8. --- #### `TimelineChart` Fan chart showing consensus evolution over time with nested confidence interval bands. **Import:** ```tsx import { TimelineChart } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to display | | `height` | `number` | `300` | Chart height in pixels | | `zoomable` | `boolean` | -- | Enable scroll-wheel zoom and drag-to-pan | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { TimelineChart } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( ); } ``` **Notes:** - Renders four nested confidence bands (95%, 75%, 50%, 25% CI) plus a solid mean line. - Built-in time filter buttons: All / 24h / 7d / 30d. Changing the filter resets any active zoom state. - Band entries in the legend are clickable toggles for showing/hiding individual bands. - The chart fetches its own history data via `useMarketHistory` internally. --- #### `MarketCharts` Tabbed container combining `ConsensusChart`, `DistributionChart`, and `TimelineChart` in a single panel. The highest-level chart widget. **Import:** ```tsx import { MarketCharts } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to display | | `height` | `number` | `300` | Chart height in pixels | | `views` | `ChartView[]` | `['consensus']` | Which tabs to show: `'consensus'`, `'distribution'`, `'timeline'` | | `overlayCurves` | `OverlayCurve[]` | -- | Overlay curves for the consensus view | | `defaultBucketCount` | `number` | `12` | Starting bucket count for the distribution view | | `distributionState` | `DistributionState` | -- | External shared bucket state for the distribution view | | `zoomable` | `boolean` | -- | Enable zoom/pan on consensus and timeline views | `ChartView` type: `'consensus' | 'distribution' | 'timeline'` **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { MarketCharts } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( ); } ``` **Notes:** - The tab bar renders only when multiple views are configured. Passing `views={['consensus']}` renders the consensus chart without tabs. - Bucket count and time filter selections persist across tab switches. - The header subtitle dynamically changes to "Compare market consensus with your trade preview" when any trading widget sets a preview belief. - The `TimelineChart` content component fetches history data only when its tab is active, avoiding wasteful API calls. --- ### 4.4 Trading Widgets All trading widgets follow a three-phase trade pattern: | Phase | Timing | What Happens | Chart Effect | |-------|--------|-------------|--------------| | 1. Preview | Instant, on every input change | Generates belief vector, writes to `ctx.setPreviewBelief(belief)` | Dashed overlay on ConsensusChart | | 2. Payout | Debounced, 500ms after last input change | Calls `previewPayoutCurve()`, writes to `ctx.setPreviewPayout(result)` | Payout column in chart tooltip | | 3. Submit | On button click | Calls `buy()`, resets inputs, clears preview, calls `ctx.invalidate(marketId)` | Preview clears, all data hooks refetch | All trading widgets clear preview state on unmount, preventing stale overlays. **Exclusivity rule:** Only one trading widget should be mounted at a time. Mounting multiple trading widgets simultaneously causes conflicting preview writes to shared context. Use tabs, modals, or conditional rendering to switch between trading widgets. --- #### `TradePanel` The simplest parametric trading panel. Offers Gaussian (bell curve) and Range (flat-top) belief shapes with slider-based inputs. **Import:** ```tsx import { TradePanel } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to trade on | | `modes` | `('gaussian' \| 'range')[]` | `['gaussian', 'range']` | Which shape modes to offer. Tab bar hidden when only one mode. | | `onBuy` | `(result: BuyResult) => void` | -- | Called after successful trade | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { TradePanel } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( console.log('Position opened:', result.positionId)} /> ); } ``` **Notes:** - Gaussian mode provides a "My Prediction" slider and a "Confidence" slider. Confidence inversely maps to bell curve width. - Range mode provides a two-handle range slider. Uses `generateRange` internally with hard edges (sharpness `1`). - Default collateral amount is $100 USDC. Displays debounced potential payout. - After a successful trade, all inputs revert to defaults (prediction to midpoint, confidence to 50%, amount to $100). --- #### `BinaryPanel` A Yes/No binary trading interface. Users bet whether the outcome will be above or below a threshold X. Renders a natural-language question: "Will {title} be more than {X}{units}?" **Import:** ```tsx import { BinaryPanel } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to trade on | | `xPoint` | `XPointMode` | `{ mode: 'variable' }` | How the threshold is determined | | `yesColor` | `string` | `'#10b981'` | CSS color for the Yes button | | `noColor` | `string` | `'#f43f5e'` | CSS color for the No button | | `onBuy` | `(result: BuyResult) => void` | -- | Called after successful trade | | `onError` | `(error: Error) => void` | -- | Called on trade failure | **`XPointMode` type:** | Mode | Threshold Source | User-Editable | Fallback | |------|-----------------|---------------|----------| | `{ mode: 'static', value: 91 }` | Fixed at `value` | Never | -- | | `{ mode: 'variable', initial?: 75 }` | `initial` or market midpoint | Always | `(L + H) / 2` | | `{ mode: 'dynamic-mode', allowOverride?: true }` | Consensus peak (mode) | Only if `allowOverride: true` | `(L + H) / 2` | | `{ mode: 'dynamic-mean', allowOverride?: true }` | Consensus mean | Only if `allowOverride: true` | `(L + H) / 2` | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { BinaryPanel } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( console.log('Position:', result.positionId)} /> ); } ``` **Notes:** - "Yes" generates a range from the threshold to the market high. "No" generates a range from the market low to the threshold. - Clicking an already-selected side deselects the choice. The amount input and submit button only appear after a side is chosen. - After a trade, the side resets to `null` (no selection) and the amount resets to $100. The threshold persists. - Default collateral amount is $100 USDC. --- #### `ShapeCutter` Trading panel offering 8 distinct belief shape presets with clickable SVG icon buttons and adaptive parameter sliders. **Import:** ```tsx import { ShapeCutter } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to trade on | | `shapes` | `ShapeId[]` | all 8 shapes | Filter which shapes to offer | | `defaultShape` | `ShapeId` | `'gaussian'` | Pre-selected shape | | `onBuy` | `(result: BuyResult) => void` | -- | Called after successful trade | | `onError` | `(error: Error) => void` | -- | Called on trade failure | `ShapeId` values: `'gaussian'`, `'spike'`, `'range'`, `'bimodal'`, `'dip'`, `'leftskew'`, `'rightskew'`, `'uniform'` **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { ShapeCutter } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( console.log('Position:', result.positionId)} /> ); } ``` **Notes:** - Layout is two columns: left column contains trade summary and adaptive parameter sliders; right column contains the shape icon grid labeled "Strategy Geometry". - Each shape activates different slider combinations -- target outcome or range, confidence, and shape-specific controls (peak balance for bimodal, skew intensity for left/right skew). - After a trade, all parameters revert to defaults. The selected shape persists. - Uses `generateGaussian`, `generateRange`, `generateDip`, `generateLeftSkew`, `generateRightSkew`, and `generateBelief` internally depending on the selected shape. --- #### `CustomShapeEditor` A chart-integrated trading panel where users draw their belief directly by dragging control points on a probability density chart. The most expressive trading component. Embeds its own consensus chart -- no separate chart component is needed. **Import:** ```tsx import { CustomShapeEditor } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to trade on | | `defaultNumPoints` | `number` | `20` | Initial control point count (5 to 25) | | `zoomable` | `boolean` | -- | Enable chart zoom/pan (control dots excluded from pan triggers) | | `onBuy` | `(result: BuyResult) => void` | -- | Called after successful trade | | `onError` | `(error: Error) => void` | -- | Called on trade failure | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { CustomShapeEditor } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( ); } ``` **Notes:** - The editor renders its own consensus chart at 280px height with draggable SVG control dots. A row of vertical sliders below the chart mirrors the control points. - Up to 2 control points can be locked, preventing accidental edits. Locks use FIFO -- locking a third point releases the oldest lock. - Adjusting the point count slider (5 to 25) resets all values to a bell shape and clears all locks. - Uses `generateCustomShape` internally (Fritsch-Carlson spline interpolation). - Because the editor embeds its own chart, mounting `CustomShapeEditor` alongside a separate `ConsensusChart` for the same market is valid -- the context coordination still works. --- #### `BucketRangeSelector` A click-to-select bucket grid for range-based trading. Displays outcome ranges as selectable buttons with probability percentages. **Import:** ```tsx import { BucketRangeSelector } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to trade on | | `distributionState` | `DistributionState` | -- | External shared state for syncing with `DistributionChart`. When omitted, creates its own internal state. | | `defaultBucketCount` | `number` | `12` | Initial number of outcome buckets | | `maxSelections` | `number` | `3` | Maximum simultaneously selected buckets (plus custom range) | | `defaultAutoMode` | `boolean` | `false` | Start in auto mode (crops grid to 95% CI) | | `showCustomRange` | `boolean` | `true` | Show custom min/max range input toggle | | `onBuy` | `(result: BuyResult) => void` | -- | Called after successful trade | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { BucketRangeSelector } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( ); } ``` **Notes:** - `BucketRangeSelector` requires distribution state. When `distributionState` is not provided, the component creates its own state internally via `useDistributionState`. To share state with a `DistributionChart` or `MarketCharts`, pass the same `distributionState` to both components (see Section 4.6). - Bucket selections reset automatically when bucket count or auto mode changes, because bucket boundaries shift. - FIFO selection applies: when `maxSelections` is reached, clicking a new bucket drops the oldest selection. - Uses `generateRange` internally to construct the belief from selected bucket boundaries. --- #### `BucketTradePanel` A composed component stacking `DistributionChart` on top of `BucketRangeSelector` with shared `DistributionState`. Adjusting the bucket count slider in the chart automatically updates the selector grid. **Import:** ```tsx import { BucketTradePanel } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to trade on | | `defaultBucketCount` | `number` | `12` | Shared initial bucket count | | `chartHeight` | `number` | `300` | Distribution chart height in pixels | | `maxSelections` | `number` | -- | Forwarded to `BucketRangeSelector` | | `defaultAutoMode` | `boolean` | -- | Forwarded to `BucketRangeSelector` | | `showCustomRange` | `boolean` | -- | Forwarded to `BucketRangeSelector` | | `onBuy` | `(result: BuyResult) => void` | -- | Forwarded to `BucketRangeSelector` | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { BucketTradePanel } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( ); } ``` **Notes:** - `BucketTradePanel` is a convenience wrapper. It creates the `useDistributionState` internally and passes the state to both `DistributionChart` and `BucketRangeSelector`. - Use `BucketTradePanel` when the chart and selector are always paired. Use separate `DistributionChart` and `BucketRangeSelector` components with an explicit `useDistributionState` when the layout requires them in different positions. --- ### 4.5 Data Display Widgets --- #### `PositionTable` Paginated, tabbed data table for viewing and managing trading positions. Supports row selection that coordinates with chart overlays, inline sell actions, and real-time market value lookup. **Import:** ```tsx import { PositionTable } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to display positions for | | `username` | `string` | required | Authenticated username. Pass `""` for unauthenticated views. | | `tabs` | `PositionTabId[]` | `['open-orders', 'trade-history']` | Which tabs to show. Tab bar hidden when only one tab. | | `pageSize` | `number` | `20` | Rows per page | | `selectedPositionId` | `number \| null` | -- | Controlled selection: externally managed selected position ID | | `onSelectPosition` | `(id: number \| null) => void` | -- | Controlled selection callback | | `onSell` | `(result: SellResult) => void` | -- | Called after successful sell | `PositionTabId` values: `'open-orders'`, `'trade-history'`, `'market-positions'` **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { PositionTable } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( console.log('Sold, returned:', result.collateralReturned)} /> ); } ``` **Notes:** - Clicking a position row writes the position to `ctx.setSelectedPosition`, causing `ConsensusChart` to render that position's belief as a colored overlay. Clicking the same row again deselects the position. - For open positions, the table calls `previewSell` to compute real-time market values. Values update when the user navigates pages. - The "Sell" button appears only for open positions on the "Open Orders" tab. After a sell, the table calls `ctx.invalidate(marketId)` to refresh all widgets. - In the `market-positions` tab, rows belonging to the authenticated user show "(you)" next to the username. --- #### `TimeSales` Live scrollable feed of recent market trades with automatic polling. **Import:** ```tsx import { TimeSales } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to display trades for | | `maxHeight` | `string` | `'500px'` | Scrollable container max-height (CSS value) | | `limit` | `number` | `100` | Maximum trades to fetch | | `pollInterval` | `number` | `5000` | Polling interval in milliseconds. Set to `0` to disable. | | `showFooter` | `boolean` | `true` | Show "Recent Trades" footer with count badge | | `emptyMessage` | `string` | `'No market activity yet'` | Custom empty state text | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { TimeSales } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( ); } ``` **Notes:** - Rows are color-coded: green for buys, red for sells. - Polling is independent of `ctx.invalidate()` -- the `TimeSales` component refreshes on its own `pollInterval` cycle via `useTradeHistory`. - After the initial load succeeds, subsequent poll failures are silent -- the UI never regresses to a loading or error state while stale data remains visible. - Usernames longer than 12 characters are truncated to `first8...last4`. --- #### `MarketStats` Horizontal stats bar showing key market metrics. Display-only with no user interactions. **Import:** ```tsx import { MarketStats } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `marketId` | `string \| number` | required | Market to display stats for | **Displays:** Total Volume, Current Liquidity, Open Positions, Market Status (Active/Resolved). **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { MarketStats } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( ); } ``` **Notes:** - Loading state renders per-stat skeleton placeholders (labels stay visible, values show shimmer animation). - Error state renders per-stat inline "Error" text in the negative color. --- #### `AuthWidget` Self-contained authentication widget handling login, signup, and logout flows. Reads all auth state from context -- no required props. **Import:** ```tsx import { AuthWidget } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `requireAccessCode` | `boolean` | `false` | Show access code field on the signup form | | `onLogin` | `(user: UserProfile) => void` | -- | Called after successful login | | `onSignup` | `(user: UserProfile) => void` | -- | Called after successful signup | | `onLogout` | `() => void` | -- | Called after logout | **Minimal example:** ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { AuthWidget } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com' }; function App() { return ( console.log('Welcome', user.username)} /> ); } ``` **Notes:** - Renders four states: idle (sign in/sign up buttons), login form, signup form, and authenticated (wallet balance + username + sign out button). - Username validation runs on blur: 3 to 32 characters, alphanumeric plus `.`, `-`, `_`. - After successful signup, the widget auto-logs-in and transitions to the authenticated state (wallet balance + username + sign out button). The context `signup()` method chains `signupUser()` → `loginUser()` → token set automatically. - All form fields and errors clear on every view transition (cancel, switch forms, success). --- #### `PasswordlessAuthWidget` Modal-based passwordless authentication widget. Users sign in or sign up with just a username. Password-protected accounts are directed to an admin login form. **Import:** ```tsx import { PasswordlessAuthWidget } from '@functionspace/ui'; ``` **Props:** | Prop | Type | Default | Description | |------|------|---------|-------------| | `requireAccessCode` | `boolean` | `false` | Show access code field on the admin signup form | | `onLogin` | `(user: UserProfile, action: 'login' \| 'signup') => void` | -- | Called after successful passwordless login or auto-signup | | `onSignup` | `(user: UserProfile) => void` | -- | Called after successful admin signup | | `onLogout` | `() => void` | -- | Called after logout | **States:** | State | Renders | Transitions To | |-------|---------|----------------| | **Idle** | "Sign In / Sign Up" button | Passwordless form | | **Passwordless form** | Username input, submit, "Admin Login" link | Idle (cancel/success), Admin login (link) | | **Admin login** | Username + password fields, "Back to Sign In / Sign Up" link, "Create Admin Account" link | Idle (cancel/success), Passwordless (link), Admin signup (link) | | **Admin signup** | Username + password + confirm + optional access code | Idle (cancel/success), Admin login (link) | | **Authenticated** | Wallet balance, username, "Sign Out" button | Idle (sign out) | **Behavior:** - Submitting a username calls `passwordlessLogin()` from `useAuth()`. If the user exists without a password, they log in. If the user doesn't exist, an account is auto-created and they log in. - If the account requires a password, the error `PASSWORD_REQUIRED` is caught and the form shows "This account requires a password. Use Admin Login below." - The `storedUsername` prop on `FunctionSpaceProvider` triggers `silentReAuth` on mount. If the stored account requires a password, the admin login form opens automatically with the username pre-filled. - The modal opens on button click and closes on cancel, success, or Escape key. **Example:** ```tsx localStorage.setItem('fs-username', user.username)} onLogout={() => localStorage.removeItem('fs-username')} /> ``` **Related:** `useAuth` (hook) | `PASSWORD_REQUIRED` (constant) | `PasswordlessLoginResult` (type) --- ### 4.6 Composition Patterns #### Chart + Trading Panel Side by Side (Auto-Preview) Place a chart and a trading panel inside the same `FunctionSpaceProvider`. Preview coordination happens automatically through context -- no props or wiring needed. Use `minWidth: 0` on flex children to prevent SVG chart overflow. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { ConsensusChart, TradePanel } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return (
); } ``` Moving sliders on `TradePanel` instantly shows a dashed preview curve on `ConsensusChart`. When the user pauses, a payout preview appears in the chart tooltip. After submitting a trade, the preview clears and the chart refetches consensus data. #### Shared `useDistributionState` Between Chart and Selector When using `BucketRangeSelector` alongside `DistributionChart` or `MarketCharts`, both components need shared bucket distribution state. Use `useDistributionState` and pass the result to both components. Because `useDistributionState` calls `useContext(FunctionSpaceContext)` internally, the hook must be called from a component that is a child of `FunctionSpaceProvider`. This requires an inner component pattern: ```tsx import { FunctionSpaceProvider, useDistributionState } from '@functionspace/react'; import { MarketCharts, BucketRangeSelector } from '@functionspace/ui'; const MARKET_ID = '15'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function DistRangeContent() { const distState = useDistributionState(MARKET_ID, { defaultBucketCount: 8 }); return ( <> ); } function App() { return ( ); } ``` Adjusting the bucket count slider in the distribution chart updates the selector grid. Selecting buckets in the selector writes a preview belief visible in the consensus chart tab. **Common mistake to avoid:** Calling `useDistributionState` in the same component that renders `FunctionSpaceProvider` throws "must be used within FunctionSpaceProvider". Always call hooks from a child component inside the provider. #### Position Table Row Selection and Chart Overlay `PositionTable` and `ConsensusChart` coordinate automatically through context. Clicking a position row in the table writes to `ctx.setSelectedPosition`, and `ConsensusChart` reads `ctx.selectedPosition` to render that position's belief as a colored overlay. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { ConsensusChart, PositionTable, TradePanel, MarketStats, AuthWidget } from '@functionspace/ui'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return (
); } ``` Clicking a position row overlays the position's belief on the chart. Clicking the same row again removes the overlay. No additional code is required. #### Post-Trade Invalidation Flow After a successful `buy()` or `sell()` call, trading widgets call `ctx.invalidate(marketId)` internally. The invalidation increments a counter in context. All data-fetching hooks (`useMarket`, `useConsensus`, `usePositions`, `useBucketDistribution`, `useMarketHistory`) watch the counter and refetch automatically. No consumer code is needed for this flow. Trading widgets handle invalidation as part of the three-phase trade pattern. Sell actions in `PositionTable` also trigger invalidation. #### Full Dashboard Layout A complete dashboard combining all widget categories: stats header, auth widget, tabbed charts with trading, position management, and a live trade feed. ```tsx import { FunctionSpaceProvider } from '@functionspace/react'; import { MarketStats, AuthWidget, MarketCharts, ShapeCutter, PositionTable, TimeSales, } from '@functionspace/ui'; const MARKET_ID = '15'; const config = { baseUrl: 'https://your-api.example.com', username: 'trader1', password: 'secret', }; function App() { return ( {/* Header: stats + auth */}
{/* Main: chart + trading */}
{/* Footer: positions + live feed */}
); } ``` Every widget in the dashboard coordinates through context: selecting shapes in `ShapeCutter` shows previews on the consensus chart, clicking position rows highlights beliefs on the chart, and trades trigger data refreshes across all widgets. --- ### 4.7 NEVER Rules (Embedding) These rules are non-negotiable. Violating them causes silent failures, broken rendering, or conflicting state. **NEVER mount multiple trading components simultaneously.** Mounting `TradePanel` and `ShapeCutter` at the same time causes conflicting `previewBelief` and `previewPayout` writes to shared context. Use tabs, modals, or conditional rendering to show one trading component at a time. **NEVER use `var(--fs-*)` in Recharts `stroke`/`fill` props.** SVG attributes do not resolve CSS custom properties. The values silently resolve to empty strings, rendering invisible chart elements. Use `ctx.chartColors.*` from `useContext(FunctionSpaceContext)` for all Recharts SVG color props. **NEVER import from `packages/ui/src/theme.ts`.** Those exports are deprecated and hardcoded to the FS Dark theme. Use `ctx.chartColors` from `FunctionSpaceContext` for chart colors instead. **NEVER create a second `FunctionSpaceProvider` for nested sections.** One provider wraps the entire widget tree. Nesting providers creates separate context scopes that prevent widgets from coordinating (previews, invalidation, and position selection will not propagate across provider boundaries). **NEVER hardcode hex colors in SDK component wrappers.** Use CSS variables (`var(--fs-primary)`, etc.) for HTML/CSS elements and `ctx.chartColors.*` for Recharts SVG elements. Hardcoded colors will not respond to theme changes. **NEVER omit `recharts` from dependencies.** `recharts` is a peer dependency of `@functionspace/ui` that the consuming application must install explicitly. Without `recharts`, all chart components fail at import time. --- ## 5. Headless Custom UI Self-contained recipe for building custom React interfaces on top of SDK hooks and core functions, without pre-built UI widgets. This mode gives you full control over rendering while the SDK handles data fetching, belief construction, trade execution, and state coordination. --- ### 5.1 Setup #### Installation Install the core computation library and the React integration layer. The `@functionspace/ui` package is intentionally excluded -- you are building your own UI. ```bash npm install @functionspace/core @functionspace/react ``` #### Provider Setup Every headless integration requires a `FunctionSpaceProvider` at the root of the widget tree. The provider creates the API client, manages authentication, resolves theming, and exposes shared state via React context. ```tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { FunctionSpaceProvider } from '@functionspace/react'; const config = { baseUrl: 'https://api.example.com', username: 'trader1', password: 'secret', autoAuthenticate: true, }; function App() { return ( ); } ReactDOM.createRoot(document.getElementById('root')!).render(); ``` The `config` object accepts these fields: | Field | Type | Required | Description | |-------|------|----------|-------------| | `baseUrl` | `string` | Yes | Base URL of the FunctionSpace API server | | `username` | `string` | No | Username for auto-authentication | | `password` | `string` | No | Password for auto-authentication | | `autoAuthenticate` | `boolean` | No | When `true` (the default if `username` and `password` are provided), the provider logs in automatically on mount | #### Import Map Headless integrations pull from two packages. Core functions and types come from `@functionspace/core`. Hooks, the provider, the context object, and theme utilities come from `@functionspace/react`. ```tsx // --- @functionspace/core --- // Belief generators import { generateBelief, generateGaussian, generateRange, generateDip, generateLeftSkew, generateRightSkew, generateCustomShape, generateBellShape, } from '@functionspace/core'; // Transactions (state-changing) import { buy, sell } from '@functionspace/core'; // Previews (read-only previews) import { previewPayoutCurve, previewSell } from '@functionspace/core'; // Pure math (no network) import { evaluateDensityCurve, computeStatistics, computePercentiles, calculateBucketDistribution, } from '@functionspace/core'; // Types import type { MarketState, MarketConfig, BeliefVector, Position, BuyResult, SellResult, PayoutCurve, PreviewSellResult, ConsensusCurve, BucketData, Region, PointRegion, RangeRegion, SplineRegion, } from '@functionspace/core'; // --- @functionspace/react --- // Provider and context import { FunctionSpaceProvider, FunctionSpaceContext } from '@functionspace/react'; import type { FSContext } from '@functionspace/react'; // Data hooks import { useMarket, useConsensus, usePositions, useTradeHistory, useMarketHistory, useBucketDistribution, useDistributionState, } from '@functionspace/react'; // State and action hooks import { useAuth, useCustomShape, useChartZoom, rechartsPlotArea } from '@functionspace/react'; ``` #### Authentication Modes **Auto-auth (credentials in config).** The provider calls `loginUser()` on mount. All hooks wait for authentication to complete before fetching data. Best for demos, internal tools, and server-rendered pages where credentials are injected from environment variables. ```tsx const config = { baseUrl: 'https://api.example.com', username: import.meta.env.VITE_FS_USERNAME, password: import.meta.env.VITE_FS_PASSWORD, autoAuthenticate: true, }; ``` **Interactive auth (login via `useAuth()`).** Omit credentials. The provider starts in guest mode (read-only). Your custom UI calls `login()` or `signup()` from the `useAuth()` hook. Charts and data hooks work immediately in read-only mode; trading functions require authentication. ```tsx const config = { baseUrl: 'https://api.example.com', autoAuthenticate: false, }; ``` **Guest mode (read-only).** Same as interactive, but you never call `login()`. Data hooks work. Trading functions will fail with authentication errors. Useful for public dashboards. --- ### 5.2 Data Hooks Reference All data-fetching hooks follow a consistent contract. Each returns an object with a named data property, plus `loading`, `error`, and `refetch`. All data hooks react to `ctx.invalidationCount` -- when any component calls `ctx.invalidate(marketId)`, every mounted data hook refetches automatically. #### `useMarket(marketId)` Fetches the complete market state including configuration, consensus, and metadata. ```typescript function useMarket(marketId: string | number): { market: MarketState | null; loading: boolean; error: Error | null; refetch: () => void; } ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `marketId` | `string \| number` | The market identifier | **Return shape:** | Field | Type | Description | |-------|------|-------------| | `market` | `MarketState \| null` | Complete market state, or `null` while loading | | `loading` | `boolean` | `true` during the initial fetch | | `error` | `Error \| null` | Fetch error, if any | | `refetch` | `() => void` | Manually trigger a refetch | The `MarketState` object contains the market configuration (`market.config` with `K`, `L`, `H`, and other protocol parameters), the current consensus vector (`market.consensus`), pool statistics (`market.totalMass`, `market.poolBalance`, `market.totalVolume`), metadata (`market.title`, `market.xAxisUnits`, `market.decimals`), and resolution state (`market.resolutionState`). **Example:** ```tsx import React, { useContext } from 'react'; import { FunctionSpaceContext, useMarket } from '@functionspace/react'; function MarketHeader({ marketId }: { marketId: string }) { const { market, loading, error } = useMarket(marketId); if (loading) return
Loading market...
; if (error) return
Error: {error.message}
; if (!market) return null; return (

{market.title}

Range: {market.config.L} - {market.config.H} {market.xAxisUnits}

Total volume: ${market.totalVolume.toFixed(2)}

Open positions: {market.positionsOpen}

); } ``` #### `useConsensus(marketId, numPoints?)` Fetches the consensus probability density as a renderable curve (array of `{x, y}` points). ```typescript function useConsensus( marketId: string | number, numPoints?: number, ): { consensus: ConsensusCurve | null; loading: boolean; error: Error | null; refetch: () => void; } ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `marketId` | `string \| number` | -- | The market identifier | | `numPoints` | `number` | Server default | Number of sample points for the density curve | **Return shape:** | Field | Type | Description | |-------|------|-------------| | `consensus` | `ConsensusCurve \| null` | Object containing `points: Array<{x: number; y: number}>` and `config: MarketConfig` | | `loading` | `boolean` | `true` during the initial fetch | | `error` | `Error \| null` | Fetch error, if any | | `refetch` | `() => void` | Manually trigger a refetch | **Example:** ```tsx import React from 'react'; import { useConsensus } from '@functionspace/react'; function ConsensusPeakDisplay({ marketId }: { marketId: string }) { const { consensus, loading, error } = useConsensus(marketId, 200); if (loading) return
Loading consensus...
; if (error) return
Error: {error.message}
; if (!consensus) return null; const peak = consensus.points.reduce( (max, pt) => (pt.y > max.y ? pt : max), consensus.points[0], ); return (

Most likely outcome: {peak.x.toFixed(2)} (density: {peak.y.toFixed(4)})

Curve points: {consensus.points.length}

); } ``` #### `usePositions(marketId, username?)` Fetches all positions for a market, with an optional filter by username. ```typescript function usePositions( marketId: string | number, username?: string, ): { positions: Position[] | null; loading: boolean; error: Error | null; refetch: () => void; } ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `marketId` | `string \| number` | -- | The market identifier | | `username` | `string` | `undefined` | When provided, filters positions to this user only. When omitted, returns all market positions. | **Return shape:** | Field | Type | Description | |-------|------|-------------| | `positions` | `Position[] \| null` | Array of position objects, or `null` while loading | | `loading` | `boolean` | `true` during the initial fetch | | `error` | `Error \| null` | Fetch error, if any | | `refetch` | `() => void` | Manually trigger a refetch | Each `Position` contains: `positionId`, `belief` (the belief vector), `collateral`, `claims`, `owner`, `status` (`'open' | 'sold' | 'settled' | 'closed'`), `prediction`, `stdDev`, `createdAt`, `closedAt`, `soldPrice`, and `settlementPayout`. **Example:** ```tsx import React, { useContext } from 'react'; import { FunctionSpaceContext, usePositions } from '@functionspace/react'; function MyPositions({ marketId }: { marketId: string }) { const ctx = useContext(FunctionSpaceContext); if (!ctx) throw new Error('Must be within FunctionSpaceProvider'); const { positions, loading, error } = usePositions( marketId, ctx.user?.username, ); if (loading) return
Loading positions...
; if (error) return
Error: {error.message}
; if (!positions || positions.length === 0) return
No positions
; const openPositions = positions.filter(p => p.status === 'open'); return (
    {openPositions.map(pos => (
  • Position #{pos.positionId}: ${pos.collateral.toFixed(2)} collateral,{' '} {pos.claims.toFixed(4)} claims
  • ))}
); } ``` #### `useTradeHistory(marketId, options?)` Fetches trade history entries. This is the only data hook that supports `pollInterval` for automatic periodic refetching. ```typescript function useTradeHistory( marketId: string | number, options?: { limit?: number; pollInterval?: number }, ): { trades: TradeEntry[] | null; loading: boolean; error: Error | null; refetch: () => void; } ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `marketId` | `string \| number` | -- | The market identifier | | `options.limit` | `number` | `100` | Maximum number of trade entries to return | | `options.pollInterval` | `number` | `undefined` | Polling interval in milliseconds. When set, the hook refetches at this interval automatically. | **Return shape:** | Field | Type | Description | |-------|------|-------------| | `trades` | `TradeEntry[] \| null` | Array of trade entries, or `null` while loading | | `loading` | `boolean` | `true` during the initial fetch | | `error` | `Error \| null` | Fetch error, if any | | `refetch` | `() => void` | Manually trigger a refetch | Each `TradeEntry` contains: `id`, `timestamp`, `side` (`'buy' | 'sell'`), `prediction`, `amount`, `username`, and `positionId`. **Example:** ```tsx import React from 'react'; import { useTradeHistory } from '@functionspace/react'; function LiveTradesFeed({ marketId }: { marketId: string }) { const { trades, loading, error } = useTradeHistory(marketId, { limit: 20, pollInterval: 5000, // refresh every 5 seconds }); if (loading) return
Loading trades...
; if (error) return
Error: {error.message}
; if (!trades || trades.length === 0) return
No trades yet
; return (
    {trades.map(trade => (
  • {trade.timestamp} -- {trade.username} {trade.side}{' '} ${trade.amount.toFixed(2)} {trade.prediction !== null && ` @ ${trade.prediction}`}
  • ))}
); } ``` #### `useMarketHistory(marketId, options?)` Fetches historical market snapshots (alpha vector changes over time). Used to build fan charts showing how the consensus evolved. ```typescript function useMarketHistory( marketId: string | number, options?: { limit?: number }, ): { history: MarketHistory | null; loading: boolean; error: Error | null; refetch: () => void; } ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `marketId` | `string \| number` | -- | The market identifier | | `options.limit` | `number` | `undefined` | Maximum number of snapshots to return | **Return shape:** | Field | Type | Description | |-------|------|-------------| | `history` | `MarketHistory \| null` | Object with `marketId`, `totalSnapshots`, and `snapshots: MarketSnapshot[]` | | `loading` | `boolean` | `true` during the initial fetch | | `error` | `Error \| null` | Fetch error, if any | | `refetch` | `() => void` | Manually trigger a refetch | **Example:** ```tsx import React from 'react'; import { useMarketHistory } from '@functionspace/react'; import { transformHistoryToFanChart } from '@functionspace/core'; import { useMarket } from '@functionspace/react'; function HistoryStats({ marketId }: { marketId: string }) { const { market } = useMarket(marketId); const { history, loading, error } = useMarketHistory(marketId, { limit: 100 }); if (loading || !market || !history) return
Loading history...
; if (error) return
Error: {error.message}
; const { L, H } = market.config; const fanData = transformHistoryToFanChart(history.snapshots, L, H); return (

Total snapshots: {history.totalSnapshots}

Fan chart data points: {fanData.length}

); } ``` #### `useBucketDistribution(marketId, numBuckets?, numPoints?)` Computes a histogram of probability across equal-width outcome buckets. Derived from the consensus curve -- makes no additional API calls beyond what `useConsensus` requires internally. ```typescript function useBucketDistribution( marketId: string | number, numBuckets?: number, numPoints?: number, ): { buckets: BucketData[] | null; loading: boolean; error: Error | null; refetch: () => void; } ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `marketId` | `string \| number` | -- | The market identifier | | `numBuckets` | `number` | `12` | Number of histogram buckets | | `numPoints` | `number` | `200` | Number of density curve points used for bucket calculation | **Return shape:** | Field | Type | Description | |-------|------|-------------| | `buckets` | `BucketData[] \| null` | Array of bucket objects, each with `range`, `min`, `max`, `probability`, and `percentage` | | `loading` | `boolean` | `true` during the initial fetch | | `error` | `Error \| null` | Fetch error, if any | | `refetch` | `() => void` | Manually trigger a refetch | **Example:** ```tsx import React from 'react'; import { useBucketDistribution } from '@functionspace/react'; function ProbabilityBuckets({ marketId }: { marketId: string }) { const { buckets, loading, error } = useBucketDistribution(marketId, 10); if (loading) return
Loading distribution...
; if (error) return
Error: {error.message}
; if (!buckets) return null; return (
{buckets.map((bucket, i) => (
{bucket.range}
{bucket.percentage.toFixed(1)}%
))}
); } ``` --- ### 5.3 State & Action Hooks State and action hooks differ from data hooks: they manage local UI state or expose context actions rather than fetching server data. They do not follow the `{ named, loading, error, refetch }` pattern. #### `useAuth()` Provides authentication state and actions from the provider context. ```typescript function useAuth(): { user: UserProfile | null; isAuthenticated: boolean; loading: boolean; error: Error | null; login: (username: string, password: string) => Promise; signup: (username: string, password: string, options?: SignupOptions) => Promise; logout: () => void; refreshUser: () => Promise; passwordlessLogin: (username: string) => Promise; showAdminLogin: boolean; pendingAdminUsername: string | null; clearAdminLogin: () => void; } ``` **Return shape:** | Field | Type | Description | |-------|------|-------------| | `user` | `UserProfile \| null` | Current user (with `userId`, `username`, `walletValue`, `role`), or `null` if not authenticated | | `isAuthenticated` | `boolean` | Whether a user session is active | | `loading` | `boolean` | `true` while an auth operation (login/signup/auto-auth) is in progress | | `error` | `Error \| null` | Most recent auth error | | `login` | `(username, password) => Promise` | Logs in and returns the user profile. The provider stores the token automatically. | | `signup` | `(username, password, options?) => Promise` | Registers a new user, then auto-logs-in. Returns the user profile. | | `logout` | `() => void` | Clears auth token, user, previews, and triggers invalidation | | `refreshUser` | `() => Promise` | Re-fetches the user profile (call after trades to update wallet balance) | | `passwordlessLogin` | `(username) => Promise` | Passwordless login/auto-signup. Returns `{ action, user, token }`. Throws with `code: PASSWORD_REQUIRED` for password-protected accounts. | | `showAdminLogin` | `boolean` | `true` when silent re-auth detected a password-protected account | | `pendingAdminUsername` | `string \| null` | Username that triggered the admin login prompt | | `clearAdminLogin` | `() => void` | Resets `showAdminLogin` and `pendingAdminUsername` | **Example:** ```tsx import React, { useState } from 'react'; import { useAuth } from '@functionspace/react'; function LoginForm() { const { user, isAuthenticated, loading, error, login, logout } = useAuth(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); if (isAuthenticated && user) { return (
Logged in as {user.username} (${user.walletValue.toFixed(2)})
); } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await login(username, password); }; return (
setUsername(e.target.value)} placeholder="Username" /> setPassword(e.target.value)} type="password" placeholder="Password" /> {error &&

{error.message}

}
); } ``` #### `useCustomShape(market)` State management for custom shape editing. Manages an array of control point values, locked points, drag state, and automatically computes the resulting belief vector and prediction via `generateCustomShape` and `computeStatistics`. ```typescript function useCustomShape(market: MarketState | null): UseCustomShapeReturn ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `market` | `MarketState \| null` | The market object from `useMarket`. When `null`, `pVector` and `prediction` are `null`. | **Return shape (`UseCustomShapeReturn`):** | Field | Type | Description | |-------|------|-------------| | `controlValues` | `number[]` | Current control point Y-values (0-25 range) | | `lockedPoints` | `number[]` | Indices of locked control points (max 2) | | `numPoints` | `number` | Current number of control points (5-25, default 20) | | `pVector` | `BeliefVector \| null` | The belief vector generated from current control values, or `null` if no market | | `prediction` | `number \| null` | The mode (most likely outcome) of the belief distribution | | `setControlValue` | `(index: number, value: number) => void` | Set a specific control point's value (respects locks) | | `toggleLock` | `(index: number) => void` | Toggle lock state for a control point | | `setNumPoints` | `(n: number) => void` | Change the number of control points (resets to bell shape) | | `resetToDefault` | `() => void` | Reset all control values to the default bell shape | | `startDrag` | `(index: number) => void` | Begin dragging a control point | | `handleDrag` | `(value: number) => void` | Update the currently dragged point's value | | `endDrag` | `() => void` | End the drag operation | | `isDragging` | `boolean` | Whether a drag is currently in progress | | `draggingIndex` | `number \| null` | Index of the point currently being dragged | This hook requires `FunctionSpaceContext`. It throws if used outside the provider. **Example:** ```tsx import React, { useContext, useEffect } from 'react'; import { FunctionSpaceContext, useMarket, useCustomShape } from '@functionspace/react'; function CustomShapeSliders({ marketId }: { marketId: string }) { const ctx = useContext(FunctionSpaceContext); if (!ctx) throw new Error('Must be within FunctionSpaceProvider'); const { market } = useMarket(marketId); const shape = useCustomShape(market); // Push belief preview to context whenever it changes useEffect(() => { ctx.setPreviewBelief(shape.pVector); return () => { ctx.setPreviewBelief(null); }; }, [shape.pVector]); return (

Points: {shape.numPoints} | Prediction: {shape.prediction?.toFixed(2) ?? '--'}

{shape.controlValues.map((val, i) => ( shape.setControlValue(i, Number(e.target.value))} disabled={shape.lockedPoints.includes(i)} /> ))}
); } ``` #### `useChartZoom(options)` Scroll-wheel zoom and drag-to-pan state machine for chart containers. This hook has no context dependency -- it manages zoom/pan state locally and returns props to spread onto a container element. ```typescript function useChartZoom(options: ChartZoomOptions): ChartZoomResult ``` **Parameters (`ChartZoomOptions`):** | Field | Type | Required | Description | |-------|------|----------|-------------| | `data` | `any[]` | Yes | The data array being charted | | `xKey` | `string` | Yes | Property name for the x-axis value in each data item | | `fullXDomain` | `[number, number]` | Yes | The full x-axis range `[min, max]`. Must be memoized -- the hook resets zoom when this value changes. | | `getPlotArea` | `(rect: DOMRect) => {left, right}` | Yes | Function that returns the pixel boundaries of the plot area. Use the `rechartsPlotArea` helper for Recharts charts. | | `computeYDomain` | `(visibleData: any[], fullData: any[]) => [number, number]` | No | Custom Y-domain computation. Receives visible (filtered) data and full dataset. | | `resetTrigger` | `any` | No | When this value changes, zoom resets | | `maxZoomFactor` | `number` | No | Maximum zoom level | | `zoomFactor` | `number` | No | Zoom speed per scroll tick | | `panExcludeSelectors` | `string[]` | No | CSS selectors for elements that should not initiate panning | | `enabled` | `boolean` | No | Whether zoom/pan is active (default `true`) | **Return shape (`ChartZoomResult`):** | Field | Type | Description | |-------|------|-------------| | `containerRef` | `React.MutableRefObject` | Mutable ref to attach to the chart container element | | `xDomain` | `[number, number]` | Current x-axis domain (changes as user zooms/pans) | | `yDomain` | `[number, number] \| undefined` | Current y-axis domain (if `computeYDomain` is provided) | | `isZoomed` | `boolean` | Whether the view is currently zoomed in | | `isPanning` | `boolean` | Whether a pan drag is in progress | | `containerProps` | `object` | Event handlers (`onMouseDown`, `onMouseMove`, `onMouseUp`, `onMouseLeave`, `onDoubleClick`, `style`) to spread onto the container. Wheel zoom is handled imperatively via `containerRef` (requires `passive: false`). | | `reset` | `() => void` | Programmatically reset to the full domain | The `rechartsPlotArea` helper creates the `getPlotArea` function for Recharts integration: ```typescript import { rechartsPlotArea } from '@functionspace/react'; const getPlotArea = rechartsPlotArea( { left: 20, right: 20 }, // chart margin 60, // y-axis width (optional, default varies) ); ``` #### `useDistributionState(marketId, config?)` Comprehensive distribution state management for bucket-based components. Combines market data, bucket distribution, and percentile computation into a single shareable state object. ```typescript function useDistributionState( marketId: string | number, config?: DistributionStateConfig, ): DistributionState ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `marketId` | `string \| number` | -- | The market identifier | | `config.defaultBucketCount` | `number` | `12` | Initial number of buckets (clamped to [2, 50]) | **Return shape (`DistributionState`):** | Field | Type | Description | |-------|------|-------------| | `market` | `MarketState \| null` | The market state | | `loading` | `boolean` | Loading state | | `error` | `Error \| null` | Error state | | `refetch` | `() => void` | Refetch data | | `bucketCount` | `number` | Current bucket count | | `setBucketCount` | `(n: number) => void` | Update bucket count | | `buckets` | `BucketData[] \| null` | Computed bucket distribution | | `percentiles` | `PercentileSet \| null` | Computed percentiles (p2.5 through p97.5) | | `getBucketsForRange` | `(low: number, high: number) => BucketData[]` | Get buckets within a specific range | --- ### 5.4 Building Beliefs A belief vector is a `number[]` of length `K + 1` that sums to 1. It expresses a trader's probability distribution over possible outcomes. Every trade in the SDK starts with constructing a belief. The three parameters `K`, `L`, and `H` always come from the market configuration. Never hardcode them. ```typescript const { market } = useMarket(marketId); const { K, L, H } = market.config; ``` #### `generateBelief(regions, K, L, H)` -- Universal Constructor The L1 universal constructor. Every other generator delegates to this function. Use it directly when you need multi-region composition or fine-grained control. ```typescript import { generateBelief } from '@functionspace/core'; import type { Region, BeliefVector } from '@functionspace/core'; function generateBelief( regions: Region[], K: number, L: number, H: number, ): BeliefVector // number[] of length K+1, sums to 1 ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `regions` | `Region[]` | One or more region objects describing where probability mass should concentrate. Regions are additive -- their weighted kernels are summed before normalization. | | `K` | `number` | Polynomial degree. The output vector has `K + 1` elements. From `market.config.K`. | | `L` | `number` | Lower bound of the outcome space. From `market.config.L`. | | `H` | `number` | Upper bound of the outcome space. From `market.config.H`. | #### Region Types **`PointRegion`** -- Gaussian peak (or inverted dip): | Field | Type | Default | Description | |-------|------|---------|-------------| | `type` | `'point'` | -- | Discriminator | | `center` | `number` | -- | Peak location in outcome space | | `spread` | `number` | -- | Width of the bell curve in outcome-space units | | `weight` | `number` | `1` | Relative weight when combining with other regions | | `skew` | `number` | `0` | Asymmetry. Negative = wider left tail, positive = wider right tail. Range: -1 to 1. | | `inverted` | `boolean` | `false` | When `true`, creates a dip: high at edges, low at center | **`RangeRegion`** -- Flat-topped range: | Field | Type | Default | Description | |-------|------|---------|-------------| | `type` | `'range'` | -- | Discriminator | | `low` | `number` | -- | Start of the range in outcome space | | `high` | `number` | -- | End of the range in outcome space | | `weight` | `number` | `1` | Relative weight | | `sharpness` | `number` | `0` | Edge transition: `0` = smooth cosine taper, `1` = hard cliff | **`SplineRegion`** -- Arbitrary freeform curve: | Field | Type | Default | Description | |-------|------|---------|-------------| | `type` | `'spline'` | -- | Discriminator | | `controlX` | `number[]` | -- | X positions in outcome space `[L..H]`, sorted ascending | | `controlY` | `number[]` | -- | Y values (unnormalized heights) | | `weight` | `number` | `1` | Relative weight | #### How Composition Works When you pass multiple regions, `generateBelief` processes each into a raw kernel array, scales by `weight`, sums them element-wise, then normalizes the combined array to sum to 1: ``` for each region: kernel = computeKernel(region, K, L, H) combined[k] += kernel[k] * region.weight return normalize(combined) // sums to 1; returns uniform if sum <= 0 ``` Regions are additive, not multiplicative. Two peaks at different locations create a bimodal distribution. A peak with `weight: 2` gets twice the probability mass of a peak with `weight: 1` before normalization. #### Convenience Generators All L2 generators are thin wrappers around `generateBelief`. They contain no math -- only shape semantics. | Function | Signature | Equivalent `generateBelief` Call | |----------|-----------|----------------------------------| | `generateGaussian` | `(center, spread, K, L, H)` | `generateBelief([{ type: 'point', center, spread }], K, L, H)` | | `generateRange` | `(low, high, K, L, H, sharpness?)` | `generateBelief([{ type: 'range', low, high, sharpness: sharpness ?? 0.5 }], K, L, H)` | | `generateRange` | `(ranges: RangeInput[], K, L, H)` | `generateBelief(ranges.map(r => ({ type: 'range', ...r })), K, L, H)` | | `generateDip` | `(center, spread, K, L, H)` | `generateBelief([{ type: 'point', center, spread: spread * 1.5, inverted: true }], K, L, H)` | | `generateLeftSkew` | `(center, spread, K, L, H, skewAmount?)` | `generateBelief([{ type: 'point', center, spread, skew: -skewAmount }], K, L, H)` (default `skewAmount = 1`) | | `generateRightSkew` | `(center, spread, K, L, H, skewAmount?)` | `generateBelief([{ type: 'point', center, spread, skew: skewAmount }], K, L, H)` (default `skewAmount = 1`) | | `generateCustomShape` | `(controlValues, K, L, H)` | `generateBelief([{ type: 'spline', controlX: [evenly spaced L..H], controlY: controlValues }], K, L, H)` | `generateBellShape(numPoints, peakPosition?, spread?, zeroTailPercent?)` is a helper that generates raw Y-values for initializing a `CustomShapeEditor`. It does not produce a belief vector -- it produces the control point array that `generateCustomShape` consumes. #### Complete Examples **Single Gaussian -- "I think the outcome will be around 75":** ```typescript import { generateGaussian } from '@functionspace/core'; const { K, L, H } = market.config; const belief = generateGaussian(75, 5, K, L, H); ``` **Bimodal -- "I think it will be either 60 or 90, leaning toward 90":** ```typescript import { generateBelief } from '@functionspace/core'; const { K, L, H } = market.config; const belief = generateBelief([ { type: 'point', center: 60, spread: 4, weight: 0.3 }, { type: 'point', center: 90, spread: 4, weight: 0.7 }, ], K, L, H); ``` **Left-skewed -- "Around 80, but could be quite a bit lower":** ```typescript import { generateLeftSkew } from '@functionspace/core'; const { K, L, H } = market.config; const belief = generateLeftSkew(80, 6, K, L, H, 0.8); ``` **Range -- "Somewhere between 70 and 85":** ```typescript import { generateRange } from '@functionspace/core'; const { K, L, H } = market.config; const belief = generateRange(70, 85, K, L, H, 0.5); ``` **Dip -- "Anything but 75":** ```typescript import { generateDip } from '@functionspace/core'; const { K, L, H } = market.config; const belief = generateDip(75, 5, K, L, H); ``` **Mixed regions -- Gaussian peak with a range floor:** ```typescript import { generateBelief } from '@functionspace/core'; const { K, L, H } = market.config; const belief = generateBelief([ { type: 'point', center: 80, spread: 3, weight: 2 }, { type: 'range', low: 60, high: 100, weight: 0.5 }, ], K, L, H); ``` **Non-contiguous ranges -- "Either 50-60 or 80-90, but not the middle":** ```typescript import { generateRange } from '@functionspace/core'; import type { RangeInput } from '@functionspace/core'; const { K, L, H } = market.config; const ranges: RangeInput[] = [ { low: 50, high: 60, weight: 1 }, { low: 80, high: 90, weight: 1 }, ]; const belief = generateRange(ranges, K, L, H); ``` --- ### 5.5 Executing Trades (Headless) The SDK's pre-built trading widgets implement a three-phase pattern for trade execution. When building custom UI, you must replicate this pattern yourself. #### The Three-Phase Pattern **Phase 1: Build belief and set preview (instant, every input change).** Construct the belief vector from the user's inputs and write it to context immediately. This gives chart components (if mounted) an instant overlay showing the trader's intended distribution. ```typescript const belief = generateGaussian(center, spread, K, L, H); ctx.setPreviewBelief(belief); ``` **Phase 2: Preview payout (debounced, 500ms after last input change).** Call `previewPayoutCurve` to compute what the payout would look like across all possible outcomes. Write the result to context so charts can display payout information in tooltips. ```typescript const payout = await previewPayoutCurve(ctx.client, marketId, belief, collateral); ctx.setPreviewPayout(payout); ``` **Phase 3: Submit trade (on user confirmation).** Execute the trade, clear the preview state, invalidate caches so all data hooks refetch, and refresh the user profile to update the wallet balance. ```typescript const result = await buy(ctx.client, marketId, belief, collateral, { prediction }); ctx.setPreviewBelief(null); ctx.setPreviewPayout(null); ctx.invalidate(marketId); await ctx.refreshUser(); ``` #### `buy(client, marketId, belief, collateral, options?)` Opens a new position. Imported from `@functionspace/core`. ```typescript import { buy } from '@functionspace/core'; async function buy( client: FSClient, marketId: string | number, belief: BeliefVector, collateral: number, options?: { prediction?: number }, ): Promise ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `client` | `FSClient` | Authenticated API client. In React, access via `useContext(FunctionSpaceContext).client`. | | `marketId` | `string \| number` | The market to trade in | | `belief` | `BeliefVector` | The probability distribution to trade on | | `collateral` | `number` | Amount of currency to put up (minimum is typically 1) | | `options.prediction` | `number` | Optional center-of-mass hint for the API. Not required for the trade to execute. | **Returns `BuyResult`:** | Field | Type | Description | |-------|------|-------------| | `positionId` | `number` | Unique ID for the new position | | `belief` | `number[]` | The belief vector as stored server-side | | `claims` | `number` | Number of claim tokens minted | | `collateral` | `number` | Collateral amount locked | #### `sell(client, positionId, marketId)` Closes an open position and returns collateral. Imported from `@functionspace/core`. ```typescript import { sell } from '@functionspace/core'; async function sell( client: FSClient, positionId: number, marketId: string | number, ): Promise ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `client` | `FSClient` | Authenticated API client | | `positionId` | `number` | The position to close | | `marketId` | `string \| number` | The market the position belongs to | **Returns `SellResult`:** | Field | Type | Description | |-------|------|-------------| | `positionId` | `number` | The closed position's ID | | `collateralReturned` | `number` | Amount of currency returned to the trader | The returned collateral depends on the current market state. If the consensus has shifted toward your belief since you bought, you get back more than you put in. If it shifted away, you get back less. #### Complete Working Example: Custom Trade Form This example implements all three phases of the trade pattern in a single component. ```tsx import React, { useState, useContext, useEffect, useRef, useCallback } from 'react'; import { FunctionSpaceContext, useMarket } from '@functionspace/react'; import { generateGaussian, buy, previewPayoutCurve, } from '@functionspace/core'; import type { PayoutCurve } from '@functionspace/core'; function CustomTradeForm({ marketId }: { marketId: string }) { const ctx = useContext(FunctionSpaceContext); if (!ctx) throw new Error('Must be within FunctionSpaceProvider'); const { market, loading: marketLoading } = useMarket(marketId); // User inputs const [center, setCenter] = useState(75); const [spread, setSpread] = useState(5); const [collateral, setCollateral] = useState(10); // Trade state const [payout, setPayout] = useState(null); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [lastResult, setLastResult] = useState(null); // Debounce timer ref const debounceRef = useRef | null>(null); // Phase 1: Build belief and set preview (instant) // Phase 2: Preview payout (debounced 500ms) useEffect(() => { if (!market) return; const { K, L, H } = market.config; const belief = generateGaussian(center, spread, K, L, H); // Phase 1: instant preview ctx.setPreviewBelief(belief); // Phase 2: debounced payout preview if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(async () => { try { const curve = await previewPayoutCurve(ctx.client, marketId, belief, collateral); setPayout(curve); ctx.setPreviewPayout(curve); } catch (err) { // Preview errors are non-fatal; clear stale payout setPayout(null); ctx.setPreviewPayout(null); } }, 500); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [market, center, spread, collateral, marketId]); // Clean up previews on unmount useEffect(() => { return () => { ctx.setPreviewBelief(null); ctx.setPreviewPayout(null); }; }, []); // Phase 3: Submit trade const handleBuy = useCallback(async () => { if (!market) return; setError(null); setSubmitting(true); try { const { K, L, H } = market.config; const belief = generateGaussian(center, spread, K, L, H); const result = await buy(ctx.client, marketId, belief, collateral, { prediction: center, }); // Clear previews ctx.setPreviewBelief(null); ctx.setPreviewPayout(null); setPayout(null); // Invalidate caches so all data hooks refetch ctx.invalidate(marketId); // Refresh user profile (wallet balance update) await ctx.refreshUser(); setLastResult( `Position #${result.positionId} opened: ${result.claims.toFixed(4)} claims for $${result.collateral}`, ); } catch (err) { setError(err instanceof Error ? err.message : 'Trade failed'); } finally { setSubmitting(false); } }, [market, center, spread, collateral, marketId]); if (marketLoading || !market) return
Loading market...
; const { L, H } = market.config; return (

Trade: {market.title}

{payout && (

Max payout: ${payout.maxPayout.toFixed(2)} at outcome {payout.maxPayoutOutcome.toFixed(1)}

)} {!ctx.isAuthenticated &&

Log in to trade

} {error &&

{error}

} {lastResult &&

{lastResult}

}
); } export default CustomTradeForm; ``` --- ### 5.6 Previews Previews are read-only API calls that preview what would happen without executing a trade. They are the backbone of the debounced preview phase in the three-phase trade pattern. #### `previewPayoutCurve(client, marketId, belief, collateral, numOutcomes?)` Previews what the settlement payout would be for every possible outcome given a hypothetical belief and collateral amount. ```typescript import { previewPayoutCurve } from '@functionspace/core'; async function previewPayoutCurve( client: FSClient, marketId: string | number, belief: BeliefVector, collateral: number, numOutcomes?: number, ): Promise ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `client` | `FSClient` | -- | Authenticated API client | | `marketId` | `string \| number` | -- | The market to preview against | | `belief` | `BeliefVector` | -- | The belief vector to simulate | | `collateral` | `number` | -- | The collateral amount to simulate | | `numOutcomes` | `number` | Server default | Number of outcome points to sample | **Returns `PayoutCurve`:** | Field | Type | Description | |-------|------|-------------| | `previews` | `Array<{outcome, payout, profitLoss: number}>` | Array of outcome/payout pairs. `profitLoss = payout - collateral`. | | `maxPayout` | `number` | Best-case payout across all outcomes | | `maxPayoutOutcome` | `number` | The outcome value that produces the best payout | | `inputCollateral` | `number` | Echo of the collateral you passed in | **How to use in the debounced preview phase:** ```typescript import { generateGaussian, previewPayoutCurve } from '@functionspace/core'; // Inside a useEffect, debounced 500ms after input change: const belief = generateGaussian(center, spread, K, L, H); const curve = await previewPayoutCurve(ctx.client, marketId, belief, collateral); // Write to context so any mounted chart renders the payout overlay ctx.setPreviewPayout(curve); // Also use locally for UI display const bestCase = curve.maxPayout; const worstCase = Math.min(...curve.previews.map(p => p.payout)); ``` #### `previewSell(client, positionId, marketId)` Previews how much collateral would be returned if a position were sold at the current market state. ```typescript import { previewSell } from '@functionspace/core'; async function previewSell( client: FSClient, positionId: number, marketId: string | number, ): Promise ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `client` | `FSClient` | Authenticated API client | | `positionId` | `number` | The position to simulate selling | | `marketId` | `string \| number` | The market the position belongs to | **Returns `PreviewSellResult`:** | Field | Type | Description | |-------|------|-------------| | `collateralReturned` | `number` | Estimated collateral you would receive | | `iterations` | `number` | Number of solver iterations the server used | **Example -- show live P&L for open positions:** ```tsx import React, { useContext, useState, useEffect } from 'react'; import { FunctionSpaceContext, usePositions } from '@functionspace/react'; import { previewSell } from '@functionspace/core'; function PositionValues({ marketId }: { marketId: string }) { const ctx = useContext(FunctionSpaceContext); if (!ctx) throw new Error('Must be within FunctionSpaceProvider'); const { positions } = usePositions(marketId, ctx.user?.username); const [values, setValues] = useState>({}); useEffect(() => { if (!positions) return; const openPositions = positions.filter(p => p.status === 'open'); Promise.all( openPositions.map(async pos => { const preview = await previewSell(ctx.client, pos.positionId, marketId); return { id: pos.positionId, value: preview.collateralReturned }; }), ).then(results => { const map: Record = {}; results.forEach(r => { map[r.id] = r.value; }); setValues(map); }); }, [positions]); if (!positions) return
Loading...
; return (
    {positions.filter(p => p.status === 'open').map(pos => { const currentValue = values[pos.positionId]; const pnl = currentValue != null ? currentValue - pos.collateral : null; return (
  • #{pos.positionId}: Cost ${pos.collateral.toFixed(2)} {pnl != null && ( = 0 ? 'green' : 'red' }}> {' '}({pnl >= 0 ? '+' : ''}{pnl.toFixed(2)}) )}
  • ); })}
); } ``` --- ### 5.7 Context API Quick Reference The context is accessed via `useContext(FunctionSpaceContext)` from `@functionspace/react`. It is `null` outside the provider -- always check before using. ```typescript import { useContext } from 'react'; import { FunctionSpaceContext } from '@functionspace/react'; import type { FSContext } from '@functionspace/react'; const ctx = useContext(FunctionSpaceContext); if (!ctx) throw new Error('Must be within FunctionSpaceProvider'); ``` #### Read Fields | Field | Type | Description | |-------|------|-------------| | `client` | `FSClient` | The HTTP client instance (authenticated if provider has credentials) | | `previewBelief` | `number[] \| null` | Active belief vector preview (set by trade inputs, read by charts) | | `previewPayout` | `PayoutCurve \| null` | Active payout curve preview | | `invalidationCount` | `number` | Internal counter -- hooks watch this to trigger refetches | | `selectedPosition` | `Position \| null` | Currently selected position for chart/table sync | | `user` | `UserProfile \| null` | Current authenticated user, `null` in guest mode | | `isAuthenticated` | `boolean` | Whether a user is currently logged in | | `authLoading` | `boolean` | Whether an auth operation is in progress | | `authError` | `Error \| null` | Most recent auth error | | `chartColors` | `ChartColors` | Resolved chart colors for the current theme (concrete hex values for Recharts SVG) | #### Write Fields | Field | Type | Description | |-------|------|-------------| | `setPreviewBelief` | `(belief: number[] \| null) => void` | Set/clear the preview belief overlay | | `setPreviewPayout` | `(payout: PayoutCurve \| null) => void` | Set/clear the payout curve overlay | | `setSelectedPosition` | `(pos: Position \| null) => void` | Set the selected position | | `invalidate` | `(marketId: string \| number) => void` | Trigger data refetch across all mounted hooks | | `login` | `(username: string, password: string) => Promise` | Interactive login | | `signup` | `(username: string, password: string, options?: SignupOptions) => Promise` | Interactive signup (auto-logs-in after) | | `logout` | `() => void` | Clear auth state, previews, and trigger invalidation | | `refreshUser` | `() => Promise` | Refresh user profile (wallet balance update after trades) | Total: 18 fields (10 read, 8 write). --- ### 5.8 NEVER Rules (Headless) These rules prevent the most common integration mistakes. Each maps to a silent failure or hard-to-debug behavior. - **NEVER call `buy()` or `sell()` without `ctx.invalidate(marketId)` after success.** Without invalidation, all mounted data hooks (positions, consensus, trade history) continue showing stale data. Charts and tables will not reflect the trade. - **NEVER assume hooks other than `useTradeHistory` support `pollInterval`.** Only `useTradeHistory` accepts a `pollInterval` option. All other data hooks refetch on invalidation only (via `ctx.invalidationCount`). Do not pass `pollInterval` to `useMarket`, `useConsensus`, `usePositions`, `useMarketHistory`, or `useBucketDistribution` -- it will be silently ignored. - **NEVER create `FSClient` manually inside a React tree.** The provider creates and manages the client instance. Access it via `useContext(FunctionSpaceContext).client`. Creating a second client bypasses auth state, token management, and invalidation coordination. - **NEVER skip the debounced payout preview step.** The three-phase pattern exists for a reason. Without Phase 2, any chart rendering payout information in its tooltip will show stale or null data. Debounce at 500ms to avoid flooding the API with preview requests during rapid slider changes. - **NEVER import `buy` or `sell` from `@functionspace/react`.** Transaction functions live in `@functionspace/core`. The React package exports hooks and the provider -- not transaction functions. The correct import is `import { buy, sell } from '@functionspace/core'`. - **NEVER use `useFunctionSpace()`.** This hook does not exist. The correct pattern is `useContext(FunctionSpaceContext)` from React, with `FunctionSpaceContext` imported from `@functionspace/react`. --- ## 6. Core-Only Non-React Self-contained recipe for using the FunctionSpace Trading SDK in Node.js, server-side scripts, CLI tools, or any non-React environment. Only the `@functionspace/core` package is required. No React, no provider, no hooks. --- ### 6.1 Setup Install the core package: ```bash npm install @functionspace/core ``` No other SDK packages are needed. `@functionspace/core` is pure TypeScript with zero framework dependencies. #### Creating a Client Every operation starts with an `FSClient` instance. The constructor accepts an `FSConfig` object: ```typescript import { FSClient } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com', }); ``` `FSConfig` fields: | Field | Type | Required | Description | |-------|------|----------|-------------| | `baseUrl` | `string` | Yes | Root URL of the FunctionSpace API server. | | `username` | `string` | No | Credentials for auto-authentication. | | `password` | `string` | No | Credentials for auto-authentication. | `FSClient` public methods: | Method | Signature | Description | |--------|-----------|-------------| | `setToken` | `(token: string) => void` | Set the auth token (required after manual `loginUser()` call). | | `clearToken` | `() => void` | Remove the current auth token. | | `authenticate` | `() => Promise` | Manually trigger authentication using constructor credentials. Throws if no credentials were provided. | | `get` | `(path: string, params?: Record) => Promise` | HTTP GET request through the client. Handles auth headers automatically. | | `post` | `(path: string, body?: unknown, params?: Record) => Promise` | HTTP POST request through the client. Handles auth headers automatically. | | `base` | `string` (getter) | Returns the base URL passed to the constructor. | | `isAuthenticated` | `boolean` (getter) | `true` if a token is currently set. | **401 retry behavior:** When a request returns HTTP 401 and the client has constructor credentials, the client automatically clears the stale token, re-authenticates, and retries the request once. This is transparent to callers. In guest mode (no credentials), a 401 throws immediately. #### Authentication Patterns There are two ways to authenticate, plus a guest mode for read-only access. **Pattern 1: Auto-auth (recommended for scripts and bots).** Pass credentials in the constructor. The client calls `ensureAuth()` internally on the first request, obtaining a token transparently. No manual token handling is needed. ```typescript import { FSClient } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com', username: 'mybot', password: 'secret', }); // The first API call triggers automatic authentication. // No explicit login step required. ``` **Pattern 2: Manual auth.** Create a bare client, then call `loginUser()` to obtain a token. You must call `client.setToken()` yourself -- the token is not auto-set. ```typescript import { FSClient, loginUser } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const { user, token } = await loginUser(client, 'mybot', 'secret'); client.setToken(token); console.log(`Logged in as ${user.username}, wallet: ${user.walletValue}`); ``` **Guest mode.** Omit all credentials. The client sends read-only requests with an `X-Username: guest` header. Query functions work; `buy()` and `sell()` will throw `"Authentication required"`. ```typescript import { FSClient, discoverMarkets } from '@functionspace/core'; const guest = new FSClient({ baseUrl: 'https://api.example.com' }); const markets = await discoverMarkets(guest); // works without credentials ``` **Pattern 3: Passwordless auth.** Use `passwordlessLoginUser()` for username-only login with auto-signup. Throws an error with `code: PASSWORD_REQUIRED` for password-protected accounts. ```typescript import { FSClient, passwordlessLoginUser, PASSWORD_REQUIRED } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); try { const result = await passwordlessLoginUser(client, 'newuser'); client.setToken(result.token); console.log(`${result.action}: ${result.user.username}`); // "login" or "signup" } catch (err) { if (err instanceof Error && 'code' in err && (err as any).code === PASSWORD_REQUIRED) { console.log('Account requires a password -- use loginUser() instead'); } } ``` **Silent re-auth.** Use `silentReAuth()` to re-authenticate a returning user by username: ```typescript import { FSClient, silentReAuth } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const { user, token } = await silentReAuth(client, 'returning-user'); client.setToken(token); ``` **Signup flow.** `signupUser()` creates an account but returns no token. You must follow it with `loginUser()`: ```typescript import { FSClient, signupUser, loginUser } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const { user } = await signupUser(client, 'newuser', 'password123'); // user is created, but client has NO token yet const { token } = await loginUser(client, 'newuser', 'password123'); client.setToken(token); // now the client is authenticated ``` #### Fetching the Current User **`fetchCurrentUser(client)`** -- Returns the authenticated user's `UserProfile`. Routes through `client.get()`, so the client must have a token set (via auto-auth or manual `setToken()`). Useful for refreshing wallet balance after trades. ```typescript import { FSClient, loginUser, fetchCurrentUser } from '@functionspace/core'; import type { UserProfile } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const { token } = await loginUser(client, 'trader', 'secret'); client.setToken(token); const profile: UserProfile = await fetchCurrentUser(client); console.log(`${profile.username}, wallet: $${profile.walletValue}`); // profile = { userId: number, username: string, walletValue: number, role: 'trader' | 'creator' | 'admin' } ``` Auto-auth clients can call `fetchCurrentUser` at any time -- the first request triggers authentication automatically: ```typescript import { FSClient, fetchCurrentUser } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com', username: 'trader', password: 'secret', }); // No manual login needed -- auto-auth handles it on first request. const profile = await fetchCurrentUser(client); console.log(`Wallet balance: $${profile.walletValue}`); ``` #### Validating Usernames **`validateUsername(name)`** -- Pure client-side validation (no network call). Returns `{ valid: boolean, error?: string }`. Rules: 3-32 characters, alphanumeric plus `.`, `-`, `_`. ```typescript import { validateUsername } from '@functionspace/core'; const result = validateUsername('new.user-1'); // result = { valid: true } const bad = validateUsername('ab'); // bad = { valid: false, error: 'Username must be at least 3 characters' } const invalid = validateUsername('no spaces!'); // invalid = { valid: false, error: 'Only letters, numbers, dots, dashes, and underscores allowed' } ``` Use `validateUsername` before calling `signupUser` to catch invalid usernames without a round-trip: ```typescript import { FSClient, validateUsername, signupUser, loginUser } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const username = 'new_trader'; const check = validateUsername(username); if (!check.valid) { console.error(check.error); process.exit(1); } const { user } = await signupUser(client, username, 'password123'); const { token } = await loginUser(client, username, 'password123'); client.setToken(token); console.log(`Account created: ${user.username}`); ``` --- ### 6.2 Reading Market Data All query functions take an `FSClient` as their first argument and return a Promise. They work in guest mode (no authentication required). #### `discoverMarkets(client)` Lists all available markets. Returns `MarketState[]`. No search or filter parameters are supported -- filter client-side. ```typescript import { FSClient, discoverMarkets } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const markets = await discoverMarkets(client); const openMarkets = markets.filter(m => m.resolutionState === 'open'); console.log(`${openMarkets.length} markets open for trading`); ``` #### `queryMarketState(client, marketId)` The foundational call. Returns a `MarketState` containing the consensus distribution, market config (`K`, `L`, `H`), metadata (title, units, decimals), pool balance, volume, and resolution status. Nearly every workflow begins here. ```typescript import { FSClient, queryMarketState } from '@functionspace/core'; import type { MarketState } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const market: MarketState = await queryMarketState(client, 42); console.log(market.title); // "What will the high temperature be?" console.log(market.config.L, market.config.H); // 30, 110 console.log(market.resolutionState); // "open" console.log(market.positionsOpen); // number of open positions ``` `MarketState` key fields: | Field | Type | Description | |-------|------|-------------| | `alpha` | `number[]` | Raw alpha vector from the AMM. | | `consensus` | `number[]` | Normalized probability distribution (alpha / sum). | | `config` | `MarketConfig` | Contains `K`, `L`, `H`, and AMM parameters. | | `title` | `string` | Market question text. | | `xAxisUnits` | `string` | Unit label for the outcome axis (e.g., "USD", "degF"). | | `decimals` | `number` | Display precision for outcome values. | | `totalMass` | `number` | Total probability mass. | | `totalVolume` | `number` | Total collateral traded. | | `poolBalance` | `number` | Current collateral in the pool. | | `participantCount` | `number` | Number of unique participants. | | `positionsOpen` | `number` | Currently open positions. | | `resolutionState` | `'open' \| 'resolved' \| 'voided'` | Market lifecycle state. | | `resolvedOutcome` | `number \| null` | Settlement value, or null if unresolved. | #### `getConsensusCurve(client, marketId, numPoints?)` Returns the consensus PDF as chart-ready `{ x, y }[]` points. Internally fetches market state and evaluates with `evaluateDensityCurve`. ```typescript import { FSClient, getConsensusCurve } from '@functionspace/core'; import type { ConsensusCurve } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const curve: ConsensusCurve = await getConsensusCurve(client, 42, 200); // curve.points = [{ x: 30, y: 0.001 }, { x: 30.4, y: 0.003 }, ...] // curve.config = { K, L, H, ... } ``` #### `queryConsensusSummary(client, marketId)` Returns statistical summary of the consensus distribution. ```typescript import { FSClient, queryConsensusSummary } from '@functionspace/core'; import type { ConsensusSummary } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const summary: ConsensusSummary = await queryConsensusSummary(client, 42); // summary = { mean, median, mode, variance, stdDev } console.log(`Expected: ${summary.mean.toFixed(1)}, Most likely: ${summary.mode.toFixed(1)}`); ``` #### `queryDensityAt(client, marketId, x)` Returns the probability density at a single outcome value. ```typescript import { FSClient, queryDensityAt } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const result = await queryDensityAt(client, 42, 75); // result = { x: 75, density: 0.0342 } ``` #### `queryMarketHistory(client, marketId, limit?, offset?)` Returns historical alpha vector snapshots. Each snapshot records the market state after a trade. Supports pagination via `limit` and `offset`. ```typescript import { FSClient, queryMarketHistory } from '@functionspace/core'; import type { MarketHistory } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const history: MarketHistory = await queryMarketHistory(client, 42, 100, 0); // history.totalSnapshots = total available for pagination // history.snapshots = MarketSnapshot[] ordered by time ``` #### `queryMarketPositions(client, marketId)` Returns all positions for a market as `Position[]`. ```typescript import { FSClient, queryMarketPositions } from '@functionspace/core'; import type { Position } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const positions: Position[] = await queryMarketPositions(client, 42); const open = positions.filter(p => p.status === 'open'); const totalLocked = open.reduce((sum, p) => sum + p.collateral, 0); ``` #### `queryPositionState(client, positionId, marketId)` Returns a single position by ID. Throws if not found. Internally calls `queryMarketPositions` and filters, so prefer filtering yourself if you already have the list. ```typescript import { FSClient, queryPositionState } from '@functionspace/core'; import type { Position } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const position: Position = await queryPositionState(client, 17, 42); console.log(position.status, position.collateral, position.claims); ``` #### `queryTradeHistory(client, marketId, options?)` Fetches positions and transforms them into a chronological list of `TradeEntry[]` entries. Each position produces a "buy" entry, and if sold, also a "sell" entry. The `options` object accepts `limit` (default 100) and `signal` (AbortSignal for cancellation). ```typescript import { FSClient, queryTradeHistory } from '@functionspace/core'; import type { TradeEntry } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com' }); const trades: TradeEntry[] = await queryTradeHistory(client, 42, { limit: 50 }); // trades[0] = { id: "12_close", side: "sell", amount: 115.3, timestamp: "2025-06-15 10:23:45", ... } ``` --- ### 6.3 Building Beliefs and Trading Trading requires three steps: extract market parameters, build a belief vector, and execute the trade. #### Step 1: Extract K, L, H from the Market Every belief generator requires `K`, `L`, and `H` from the market config. Always destructure them from a live `queryMarketState` response. Never hardcode these values. ```typescript import { FSClient, queryMarketState } from '@functionspace/core'; const client = new FSClient({ baseUrl: 'https://api.example.com', username: 'trader', password: 'secret', }); const market = await queryMarketState(client, 42); const { K, L, H } = market.config; ``` #### Step 2: Build a Belief Use any generator function to construct a normalized `BeliefVector` (a `number[]` of length `K + 1` that sums to 1). ```typescript import { generateGaussian, generateRange, generateBelief, generateDip } from '@functionspace/core'; // Gaussian: "I think the outcome will be around 75" const belief = generateGaussian(75, 5, K, L, H); // Range: "I think it will land between 70 and 85" const rangeBelief = generateRange(70, 85, K, L, H, 0.5); // Range with hard edges (sharpness 1) const hardRangeBelief = generateRange(70, 85, K, L, H, 1); // Dip: "Anything but 75" const dipBelief = generateDip(75, 5, K, L, H); // Advanced: multi-region composition via generateBelief const bimodal = generateBelief([ { type: 'point', center: 60, spread: 4, weight: 0.3 }, { type: 'point', center: 90, spread: 4, weight: 0.7 }, ], K, L, H); ``` All L2 generators (`generateGaussian`, `generateRange`, `generateDip`, `generateLeftSkew`, `generateRightSkew`, `generateCustomShape`) delegate to `generateBelief` internally. Use `generateBelief` directly when you need multi-region composition or fine-grained control with `PointRegion`, `RangeRegion`, or `SplineRegion` types. #### Step 3: Execute the Trade **`buy(client, marketId, belief, collateral, options?)`** -- Opens a new position. Returns `BuyResult`. ```typescript import { buy } from '@functionspace/core'; import type { BuyResult } from '@functionspace/core'; const result: BuyResult = await buy(client, 42, belief, 100, { prediction: 75 }); // result = { positionId: number, belief: number[], claims: number, collateral: number } console.log(`Opened position ${result.positionId}, claims: ${result.claims}`); ``` Parameters: | Parameter | Type | Description | |-----------|------|-------------| | `client` | `FSClient` | Authenticated client (auto-auth or manual). | | `marketId` | `string \| number` | Target market. | | `belief` | `BeliefVector` | Normalized probability vector from any generator. | | `collateral` | `number` | Amount of currency to commit. | | `options.prediction` | `number?` | Optional center-of-mass hint. | **`sell(client, positionId, marketId)`** -- Closes an open position. Returns `SellResult`. ```typescript import { sell } from '@functionspace/core'; import type { SellResult } from '@functionspace/core'; const result: SellResult = await sell(client, 17, 42); // result = { positionId: number, collateralReturned: number } console.log(`Got back ${result.collateralReturned}`); ``` #### Previewing Trades Before committing, use previews to preview outcomes. **`previewPayoutCurve(client, marketId, belief, collateral, numOutcomes?)`** -- Previews payouts across all possible outcomes without executing a trade. Returns `PayoutCurve`. ```typescript import { previewPayoutCurve } from '@functionspace/core'; import type { PayoutCurve } from '@functionspace/core'; const curve: PayoutCurve = await previewPayoutCurve(client, 42, belief, 100); console.log(`Best case: $${curve.maxPayout} if outcome = ${curve.maxPayoutOutcome}`); console.log(`Worst case: $${Math.min(...curve.previews.map(p => p.payout))}`); // curve.previews = [{ outcome, payout, profitLoss }, ...] ``` **`previewSell(client, positionId, marketId)`** -- Previews how much collateral would be returned if a position were sold right now. ```typescript import { previewSell } from '@functionspace/core'; import type { PreviewSellResult } from '@functionspace/core'; const preview: PreviewSellResult = await previewSell(client, 17, 42); // preview = { collateralReturned: number, iterations: number } console.log(`Current market value: $${preview.collateralReturned}`); ``` #### Complete End-to-End Example A full workflow: authenticate, fetch market data, build a belief, preview the payout, execute the trade, and verify the position. ```typescript import { FSClient, queryMarketState, generateGaussian, previewPayoutCurve, buy, queryPositionState, } from '@functionspace/core'; async function main() { // 1. Create an auto-authenticating client const client = new FSClient({ baseUrl: 'https://api.example.com', username: 'trader', password: 'secret', }); // 2. Fetch market state const market = await queryMarketState(client, 42); const { K, L, H } = market.config; console.log(`Market: ${market.title} [${L} - ${H}] ${market.xAxisUnits}`); // 3. Build a belief: "I think the outcome will be around 75" const belief = generateGaussian(75, 5, K, L, H); // 4. Preview the payout before committing const preview = await previewPayoutCurve(client, 42, belief, 100); console.log(`Best payout: $${preview.maxPayout.toFixed(2)} at outcome ${preview.maxPayoutOutcome}`); // 5. Execute the trade const result = await buy(client, 42, belief, 100, { prediction: 75 }); console.log(`Position opened: #${result.positionId}, claims: ${result.claims}`); // 6. Verify the position const position = await queryPositionState(client, result.positionId, 42); console.log(`Status: ${position.status}, collateral locked: $${position.collateral}`); } main().catch(console.error); ``` --- ### 6.4 Pure Math Functions (No Client Required) These L0 functions operate on coefficient vectors and data arrays with no network calls. They work on any normalized `number[]` -- whether it is `market.consensus`, a `position.belief`, or a vector from `generateGaussian`. Import them from `@functionspace/core`. #### Density Evaluation **`evaluateDensityCurve(coefficients, L, H, numPoints?)`** -- Evaluates a coefficient vector as a continuous PDF across the full outcome range. Returns `{ x, y }[]` points suitable for charting. Default `numPoints` is 200. ```typescript import { evaluateDensityCurve, generateGaussian } from '@functionspace/core'; const belief = generateGaussian(75, 5, K, L, H); const curve = evaluateDensityCurve(belief, L, H, 200); // curve = [{ x: 30, y: 0.0001 }, { x: 30.4, y: 0.0003 }, ...] ``` **`evaluateDensityPiecewise(coefficients, x, L, H)`** -- Evaluates density at a single point. Returns a `number` (density value, can exceed 1). Values outside `[L, H]` are clamped. ```typescript import { evaluateDensityPiecewise } from '@functionspace/core'; const density = evaluateDensityPiecewise(market.consensus, 75, L, H); console.log(`Density at 75: ${density.toFixed(4)}`); ``` #### Statistical Analysis **`computeStatistics(coefficients, L, H)`** -- Returns `{ mean, median, mode, variance, stdDev }`. ```typescript import { computeStatistics } from '@functionspace/core'; import type { ConsensusSummary } from '@functionspace/core'; const stats: ConsensusSummary = computeStatistics(market.consensus, L, H); console.log(`Expected: ${stats.mean.toFixed(1)} +/- ${stats.stdDev.toFixed(1)}`); ``` **`computePercentiles(coefficients, L, H)`** -- Returns a `PercentileSet` with 9 percentile values: `p2_5`, `p12_5`, `p25`, `p37_5`, `p50`, `p62_5`, `p75`, `p87_5`, `p97_5`. ```typescript import { computePercentiles } from '@functionspace/core'; import type { PercentileSet } from '@functionspace/core'; const pct: PercentileSet = computePercentiles(market.consensus, L, H); console.log(`95% CI: ${pct.p2_5.toFixed(1)} to ${pct.p97_5.toFixed(1)}`); console.log(`IQR: ${pct.p25.toFixed(1)} to ${pct.p75.toFixed(1)}`); ``` #### Distribution Bucketing **`calculateBucketDistribution(points, L, H, numBuckets?, decimals?)`** -- Integrates a density curve into equal-width histogram buckets. The `points` parameter takes `{ x, y }[]` from `evaluateDensityCurve`, not raw coefficients. Returns `BucketData[]`. Default `numBuckets` is 12, clamped to `[1, 200]`. ```typescript import { evaluateDensityCurve, calculateBucketDistribution } from '@functionspace/core'; import type { BucketData } from '@functionspace/core'; const curve = evaluateDensityCurve(market.consensus, L, H, 200); const buckets: BucketData[] = calculateBucketDistribution(curve, L, H, 12); // buckets[0] = { range: "30-37", min: 30, max: 37, probability: 0.02, percentage: 2.0 } ``` #### Fan Chart Transform **`transformHistoryToFanChart(snapshots, L, H, maxPoints?)`** -- Transforms `MarketSnapshot[]` from `queryMarketHistory` into `FanChartPoint[]` with computed statistics and percentile bands at each snapshot. Automatically downsamples to `maxPoints` (default 200). ```typescript import { queryMarketHistory, transformHistoryToFanChart } from '@functionspace/core'; import type { FanChartPoint } from '@functionspace/core'; const history = await queryMarketHistory(client, 42, 500); const { L, H } = market.config; const fanData: FanChartPoint[] = transformHistoryToFanChart(history.snapshots, L, H, 200); // fanData[0] = { timestamp, createdAt, tradeId, mean, mode, stdDev, percentiles: PercentileSet } ``` #### Data Transforms **`mapPosition(raw)`** -- Converts a raw API response object (snake_case fields like `position_id`, `belief_p`, `input_collateral_C`) into a typed `Position` with camelCase fields. Useful when calling the API directly outside the SDK query functions. ```typescript import { mapPosition } from '@functionspace/core'; import type { Position } from '@functionspace/core'; const response = await fetch(`${baseUrl}/api/market/positions?market_id=42`); const data = await response.json(); const positions: Position[] = data.positions.map(mapPosition); ``` #### Chart Interaction Utilities Low-level pure math functions for scroll-wheel zoom and drag-to-pan on chart containers. These power the `useChartZoom` hook (Section 5.3) internally. Use them directly only if building a fully custom zoom implementation outside the hook. ```typescript import { pixelToDataX, computeZoomedDomain, computePannedDomain, filterVisibleData, generateEvenTicks, } from '@functionspace/core'; import type { ZoomParams, PanParams } from '@functionspace/core'; ``` **`pixelToDataX(clientX, plotAreaLeft, plotAreaRight, xDomain)`** -- Converts a pixel X coordinate to a data-space X value via linear interpolation. Clamps to domain edges. **`computeZoomedDomain(params: ZoomParams)`** -- Computes a new X domain after a scroll-wheel zoom event, centered on the cursor position. Returns `[number, number] | null` (`null` when zoom reaches full domain reset threshold of 99%). `ZoomParams` fields: `currentDomain`, `fullDomain`, `cursorDataX`, `direction` (`1` = zoom out, `-1` = zoom in), `zoomFactor?` (default `0.15`), `maxZoomFactor?` (default `50`). **`computePannedDomain(params: PanParams)`** -- Computes a new X domain after a drag-to-pan operation. Preserves range width, only shifts position. Clamps to full domain boundaries. `PanParams` fields: `startDomain`, `fullDomain`, `pixelDelta`, `plotAreaWidth`. **`filterVisibleData(data, xKey, domain)`** -- Filters a data array to items whose `xKey` value falls within `domain` (inclusive). Generic: `(data: T[], xKey: keyof T & string, domain: [number, number]) => T[]`. **`generateEvenTicks(domain, count)`** -- Generates `count` evenly-spaced tick values across `domain`. Returns `number[]`. **`positionsToTradeEntries(positions, options?)`** -- Converts `Position[]` into a chronological `TradeEntry[]` list. Each position produces a "buy" entry; sold positions also produce a "sell" entry. Sorted by timestamp descending. Default limit is 100. ```typescript import { queryMarketPositions, positionsToTradeEntries } from '@functionspace/core'; import type { TradeEntry } from '@functionspace/core'; const positions = await queryMarketPositions(client, 42); const entries: TradeEntry[] = positionsToTradeEntries(positions, { limit: 20 }); // entries[0] = { id: "45_close", side: "sell", amount: 115.3, timestamp: "2025-06-15 10:23:45", ... } ``` --- ### 6.5 NEVER Rules (Core-Only) These constraints prevent silent failures and hard-to-debug errors in non-React environments. - **NEVER call `buy()` or `sell()` without authenticating first.** The client will throw `"Authentication required"`. Use auto-auth credentials in the constructor or call `loginUser()` + `client.setToken(token)` before any mutation. - **NEVER hardcode K, L, H values.** Always destructure them from `market.config` via `queryMarketState()`. Markets have different parameters, and hardcoded values produce invalid belief vectors. - **NEVER skip `client.setToken(token)` after calling `loginUser()` directly.** The `loginUser()` function returns the token but does not set it on the client. Auto-auth via constructor credentials handles this automatically; manual auth does not. - **NEVER assume `signupUser()` returns a token.** It returns only `{ user: UserProfile }`. You must follow with `loginUser()` to obtain a session token. - **NEVER assume `discoverMarkets()` supports search or filter parameters.** It returns all markets as a flat array. Filter client-side with `Array.filter()`. - **NEVER pass raw API response objects directly to SDK functions that expect typed data.** Use `mapPosition()` to convert snake_case API responses into the SDK's camelCase `Position` type. --- ## 7. Full App Generation Self-contained recipe for generating a complete Vite + React prediction market application from scratch. Every starter kit in this section is a single file that compiles and runs without modification (given valid environment variables). --- ### 7.1 Project Scaffolding #### Create the project ```bash npm create vite@latest my-trading-app -- --template react-ts cd my-trading-app ``` #### Install dependencies All three SDK packages plus the `recharts` peer dependency: ```bash npm install @functionspace/core @functionspace/react @functionspace/ui recharts ``` Version compatibility reference (from the demo app): | Package | Version | |---------|---------| | `react` | `^18.2.0` | | `react-dom` | `^18.2.0` | | `recharts` | `^2.10.0` | | `typescript` | `^5.3.0` | | `vite` | `^5.0.0` | | `@vitejs/plugin-react` | `^4.0.0` | #### Vite configuration No special SDK configuration is needed. A standard React + TypeScript Vite config is sufficient: ```typescript // vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], }); ``` Do NOT add `resolve.alias` entries for `@functionspace/*` packages. Those are only used in the monorepo development environment. Published npm packages resolve automatically. #### Recommended file structure ``` src/ main.tsx # Entry point with Provider App.tsx # Layout with widgets config.ts # SDK config + env vars ``` #### Environment variables Create a `.env` file at the project root: ``` VITE_FS_BASE_URL=https://your-api.example.com VITE_FS_USERNAME=your_username VITE_FS_PASSWORD=your_password VITE_FS_MARKET_ID=15 VITE_FS_AUTO_AUTH=true ``` All environment variables use the `VITE_` prefix so Vite exposes them to client code via `import.meta.env`. For guest mode (read-only, no trading), omit `VITE_FS_USERNAME` and `VITE_FS_PASSWORD`. --- ### 7.2 App Entry Point The entry point mounts React 18 with `createRoot` and renders the root component: ```tsx // src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; ReactDOM.createRoot(document.getElementById('root')!).render( , ); ``` The config file centralizes environment variable access and theme selection: ```tsx // src/config.ts import type { FSThemeInput } from '@functionspace/react'; export const config = { baseUrl: import.meta.env.VITE_FS_BASE_URL, username: import.meta.env.VITE_FS_USERNAME, password: import.meta.env.VITE_FS_PASSWORD, autoAuthenticate: import.meta.env.VITE_FS_AUTO_AUTH !== 'false', }; export const MARKET_ID = import.meta.env.VITE_FS_MARKET_ID; export const widgetTheme: FSThemeInput = 'fs-dark'; // Alternatives: 'fs-light', 'native-dark', 'native-light' // Custom override: // export const widgetTheme: FSThemeInput = { // preset: 'fs-dark', // primary: '#ff00ff', // accent: '#00ffff', // }; ``` Important: `autoAuthenticate` defaults to `true` when `username` and `password` are provided. The expression `import.meta.env.VITE_FS_AUTO_AUTH !== 'false'` means any value other than the literal string `'false'` evaluates to `true`. This is intentional -- environment variables are always strings. --- ### 7.3 Starter Kit: Market Dashboard A complete, production-ready dashboard with tabbed charts, shape-based trading, position management, authentication, and market statistics. Copy this entire file as `src/App.tsx`. ```tsx // src/App.tsx -- Market Dashboard Starter Kit // Complete single-file app. Requires: main.tsx (Section 7.2), config.ts (Section 7.2), .env import { FunctionSpaceProvider } from '@functionspace/react'; import { MarketCharts, ShapeCutter, MarketStats, PositionTable, AuthWidget, } from '@functionspace/ui'; import type { FSThemeInput } from '@functionspace/react'; // ── Configuration ── const config = { baseUrl: import.meta.env.VITE_FS_BASE_URL, username: import.meta.env.VITE_FS_USERNAME, password: import.meta.env.VITE_FS_PASSWORD, autoAuthenticate: import.meta.env.VITE_FS_AUTO_AUTH !== 'false', }; const MARKET_ID = import.meta.env.VITE_FS_MARKET_ID; const theme: FSThemeInput = 'fs-dark'; // Alternatives: 'fs-light', 'native-dark', 'native-light' // ── App ── export default function App() { return (
{/* Header: Market Stats + Auth */}
{/* Charts: Consensus, Distribution, Timeline tabs */}
{/* Trading: ShapeCutter with 8 preset belief shapes */}
{/* Alternative: Replace ShapeCutter with TradePanel for simpler Gaussian/Range trading: */} {/* Positions: Open orders, trade history, market positions */}
); } ``` #### Layout notes - The 7:3 flex ratio places the wider `MarketStats` bar beside the narrower `AuthWidget`. This ratio is used consistently in all demo layouts. - `minWidth: 0` on flex children is mandatory. Without it, Recharts SVG elements overflow their containers and break the layout. - `MarketCharts` with `views={['consensus', 'distribution', 'timeline']}` renders a tabbed container with all three chart types. Omit entries from the array to hide tabs. - `ShapeCutter` and `MarketCharts` coordinate automatically through context. When the user adjusts shape parameters, a preview overlay appears on the consensus chart. No prop-drilling is required. - `AuthWidget` takes no props. It reads all authentication state from context. - `username` on `PositionTable` can be an empty string `""` for unauthenticated users. When a user logs in via `AuthWidget`, positions for that user are highlighted automatically. - `zoomable` enables scroll-wheel zoom and drag-to-pan on chart components. --- ### 7.4 Starter Kit: Minimal Embed The smallest possible working integration -- a consensus chart and a trade panel in under 30 lines. Guest mode, no credentials required. Copy this entire file as `src/App.tsx`. ```tsx // src/App.tsx -- Minimal Embed Starter Kit // Under 30 lines. Guest mode (read-only without credentials). import { FunctionSpaceProvider } from '@functionspace/react'; import { ConsensusChart, TradePanel } from '@functionspace/ui'; const config = { baseUrl: import.meta.env.VITE_FS_BASE_URL, }; const MARKET_ID = import.meta.env.VITE_FS_MARKET_ID; export default function App() { return (
); } ``` This renders a consensus probability density chart with a parametric trade panel below it. The chart automatically shows a preview overlay when the user adjusts trade parameters. The `"native-dark"` theme provides a minimal, brand-neutral appearance suitable for embedding in any dark-themed host page. Without credentials, the provider runs in guest mode. Charts render normally (read-only). The trade panel is visible but trading operations will fail until the user authenticates. To enable trading, either add `username`/`password` to the config or add an `` for interactive login. --- ### 7.5 Starter Kit: Headless Custom A custom trade form built entirely with hooks and core functions -- no pre-built trading widgets. Demonstrates direct context access, manual belief construction, payout preview, and trade execution. Copy this entire file as `src/App.tsx`. ```tsx // src/App.tsx -- Headless Custom Starter Kit // Custom trade form using hooks + core functions. No pre-built trading widgets. import { useContext, useState, useCallback, useEffect, useRef } from 'react'; import { FunctionSpaceProvider, FunctionSpaceContext } from '@functionspace/react'; import { useMarket } from '@functionspace/react'; import { useConsensus } from '@functionspace/react'; import { generateGaussian, computeStatistics, buy, previewPayoutCurve, } from '@functionspace/core'; import type { FSThemeInput } from '@functionspace/react'; import type { MarketState, PayoutCurve, BuyResult } from '@functionspace/core'; // ── Configuration ── const config = { baseUrl: import.meta.env.VITE_FS_BASE_URL, username: import.meta.env.VITE_FS_USERNAME, password: import.meta.env.VITE_FS_PASSWORD, autoAuthenticate: import.meta.env.VITE_FS_AUTO_AUTH !== 'false', }; const MARKET_ID = import.meta.env.VITE_FS_MARKET_ID; const theme: FSThemeInput = 'fs-dark'; // ── Custom Trade Form (must be inside the provider) ── function CustomTradeForm() { const ctx = useContext(FunctionSpaceContext); if (!ctx) throw new Error('CustomTradeForm must be inside FunctionSpaceProvider'); const { market, loading: marketLoading } = useMarket(MARKET_ID); const { consensus } = useConsensus(MARKET_ID); const [center, setCenter] = useState(50); const [spread, setSpread] = useState(10); const [collateral, setCollateral] = useState(10); const [payout, setPayout] = useState(null); const [status, setStatus] = useState(''); const previewTimer = useRef | null>(null); // Compute statistics from consensus const stats = market ? computeStatistics(market.consensus, market.config.L, market.config.H) : null; // Three-phase pattern: // Phase 1: Build belief and set preview (instant) // Phase 2: Preview payout curve (debounced 500ms) // Phase 3: Submit trade on user action // Phase 1 + 2: Update preview and preview payout on parameter change useEffect(() => { if (!market) return; const { K, L, H } = market.config; // Phase 1: Build belief and set preview immediately const belief = generateGaussian(center, spread, K, L, H); ctx.setPreviewBelief(belief); // Phase 2: Debounced payout preview if (previewTimer.current) clearTimeout(previewTimer.current); previewTimer.current = setTimeout(async () => { try { const previewed = await previewPayoutCurve( ctx.client, MARKET_ID, belief, collateral, ); setPayout(previewed); ctx.setPreviewPayout(previewed); } catch { // Preview failed -- clear payout preview setPayout(null); ctx.setPreviewPayout(null); } }, 500); return () => { if (previewTimer.current) clearTimeout(previewTimer.current); }; }, [market, center, spread, collateral, ctx]); // Clean up previews on unmount useEffect(() => { return () => { ctx.setPreviewBelief(null); ctx.setPreviewPayout(null); }; }, [ctx]); // Phase 3: Execute trade const handleBuy = useCallback(async () => { if (!market) return; const { K, L, H } = market.config; const belief = generateGaussian(center, spread, K, L, H); setStatus('Submitting...'); try { const result: BuyResult = await buy( ctx.client, MARKET_ID, belief, collateral, ); setStatus(`Position #${result.positionId} opened. Claims: ${result.claims.toFixed(2)}`); // Post-trade: clear previews and invalidate cache ctx.setPreviewBelief(null); ctx.setPreviewPayout(null); ctx.invalidate(MARKET_ID); ctx.refreshUser(); // Update wallet balance } catch (err: any) { setStatus(`Error: ${err.message}`); } }, [market, center, spread, collateral, ctx]); if (marketLoading || !market) { return
Loading market...
; } const { L, H } = market.config; return (

{market.title}

{/* Market Statistics */} {stats && (
Mean: {stats.mean.toFixed(2)} | Median: {stats.median.toFixed(2)} | Mode: {stats.mode.toFixed(2)} | StdDev: {stats.stdDev.toFixed(2)}
)} {/* Consensus Curve (raw points) */} {consensus && (
Consensus curve: {consensus.points.length} points (range {L} to {H} {market.xAxisUnits})
)} {/* Trade Parameters */}
{/* Payout Preview */} {payout && (
Max Payout: ${payout.maxPayout.toFixed(2)} at {payout.maxPayoutOutcome.toFixed(1)} {market.xAxisUnits}
Input: ${payout.inputCollateral.toFixed(2)}
)} {/* Submit Button */} {/* Status */} {status && (
{status}
)}
); } // ── App (Provider wraps the custom form) ── export default function App() { return (
); } ``` #### Key patterns demonstrated **Context access**: The custom form accesses the SDK client and shared state via `useContext(FunctionSpaceContext)`. There is no `useFunctionSpace()` hook -- always use `useContext(FunctionSpaceContext)` directly. **Inner component pattern**: `CustomTradeForm` is a separate component rendered inside the `FunctionSpaceProvider`. Hooks from `@functionspace/react` must be called from a component that is a child of the provider. Calling them in the same component that renders the provider will throw. **Three-phase trade pattern**: (1) Build the belief vector with `generateGaussian` and set `ctx.setPreviewBelief(belief)` immediately for instant chart overlay feedback. (2) Debounce the `previewPayoutCurve` call by 500ms and set `ctx.setPreviewPayout(previewed)` to show payout preview on the chart. (3) On user confirmation, call `buy()` with the client from context, then clear previews and call `ctx.invalidate(MARKET_ID)` plus `ctx.refreshUser()`. **Market parameters**: The `K`, `L`, and `H` values are always destructured from `market.config`. Never hardcode these values -- they are specific to each market. **Statistics from core**: `computeStatistics` is a pure function (L0) that takes a coefficient vector and market bounds. It requires no API call and returns `{ mean, median, mode, variance, stdDev }`. **Cleanup on unmount**: The component clears `previewBelief` and `previewPayout` when it unmounts. This prevents stale overlays from persisting on charts after the trade form is removed from the DOM. --- ### 7.6 NEVER Rules (Full App) These rules apply to all full-app generation scenarios. Violating them produces silent failures or broken behavior. 1. **NEVER use CSS-in-JS libraries for SDK components.** Do not use `@emotion`, `styled-components`, CSS modules, or Tailwind `@apply` to wrap or restyle SDK widgets. The SDK uses CSS custom properties (`var(--fs-*)`) injected by the provider. External styling systems can conflict with the provider's inline style injection and break theme resolution. 2. **NEVER add a second `FunctionSpaceProvider` for nested routes.** A single provider at the app root is correct. Multiple providers create independent client instances, independent auth state, and independent preview coordination. Widgets in different providers cannot communicate. The only valid use of multiple providers is rendering completely independent market views on the same page (e.g., a showcase with multiple layouts). 3. **NEVER place SDK widgets outside the provider tree.** Every widget calls `useContext(FunctionSpaceContext)` internally and throws an explicit error if the context is null: `" must be used within FunctionSpaceProvider"`. This includes chart components, trading components, `MarketStats`, `PositionTable`, `TimeSales`, and `AuthWidget`. 4. **NEVER use monorepo workspace references in production builds.** Do not add `resolve.alias` entries mapping `@functionspace/*` to local `packages/` paths. This pattern is only for the SDK's own monorepo development. Published packages resolve automatically from `node_modules`. 5. **NEVER omit `recharts` from dependencies.** `recharts` is a peer dependency of `@functionspace/ui`. All chart components (`ConsensusChart`, `DistributionChart`, `TimelineChart`, `MarketCharts`) and the `CustomShapeEditor` depend on Recharts for SVG rendering. Without it, the build fails with missing module errors. 6. **NEVER import CSS files from SDK packages.** The SDK's `base.css` is imported internally by the widget components. Consumers do not need to add any CSS import statements for SDK styling. All visual customization is done through the `theme` prop on `FunctionSpaceProvider`. 7. **NEVER use `var(--fs-*)` CSS variables in Recharts SVG `stroke` or `fill` props.** Recharts renders to SVG, which does not resolve CSS custom properties in inline attributes. Use `ctx.chartColors.*` (concrete hex values) from the context instead. --- ## 8. Type Reference Flat alphabetical listing of every public type across the three SDK packages. Each entry includes the source package, a field table, and a one-line usage note. Types are extracted from `packages/core/src/types.ts`, `packages/react/src/context.ts`, `packages/react/src/themes.ts`, `packages/ui/src/index.ts`, and the `02-api-surface.md` authoritative catalog. --- ### 8.1 Core Types #### `AuthResult` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `user` | `UserProfile` | Authenticated user profile | | `token` | `string` | JWT authentication token | **Usage:** Returned by `loginUser()`. In React mode, the Provider calls `client.setToken(token)` automatically. In core-only mode, the caller must call `client.setToken(result.token)` manually after `loginUser()`. --- #### `BeliefVector` **Package:** `@functionspace/core` **Kind:** Type alias ```typescript type BeliefVector = number[]; ``` **Invariants:** A valid belief vector has length `K + 1`, where `K` is from `market.config.K`. All elements are non-negative. The elements sum to exactly 1 (normalized probability distribution). These invariants are enforced by the `generate*` builder functions. **Usage:** Passed to `buy()` as the trader's belief about outcome probabilities. Passed to `ctx.setPreviewBelief()` for chart overlays. --- #### `BucketData` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `range` | `string` | Human-readable label, e.g. `"80-90"` | | `min` | `number` | Lower bound of the bucket | | `max` | `number` | Upper bound of the bucket | | `probability` | `number` | Probability mass in this bucket (0 to 1) | | `percentage` | `number` | Probability as a percentage (0 to 100) | **Usage:** Returned by `calculateBucketDistribution()` and `useBucketDistribution()`. Consumed by `DistributionChart` and `BucketRangeSelector`. --- #### `BuyResult` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `positionId` | `number` | ID of the newly created position | | `belief` | `number[]` | The belief vector that was submitted | | `claims` | `number` | Number of claims purchased | | `collateral` | `number` | Amount of collateral deposited | **Usage:** Returned by `buy()`. Passed to `onBuy` callbacks on trading components (`ShapeCutter`, `TradePanel`, `BinaryPanel`, etc.). --- #### `ConsensusCurve` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `points` | `Array<{x: number; y: number}>` | Renderable density curve points | | `config` | `MarketConfig` | The market's configuration (for axis bounds) | **Usage:** Returned by `getConsensusCurve()` and `useConsensus()`. Each point maps directly to a chart data point with `x` on the outcome axis and `y` as the probability density. --- #### `ConsensusSummary` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `mean` | `number` | Expected value of the distribution | | `median` | `number` | 50th percentile | | `mode` | `number` | Most likely outcome (peak density) | | `variance` | `number` | Distribution variance | | `stdDev` | `number` | Standard deviation | **Usage:** Returned by `queryConsensusSummary()`. Also computable locally with `computeStatistics()`. --- #### `FanChartPoint` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `timestamp` | `number` | Epoch milliseconds | | `createdAt` | `string` | ISO 8601 timestamp | | `tradeId` | `number` | Trade that produced this snapshot | | `mean` | `number` | Mean of the consensus distribution at this point | | `mode` | `number` | Mode (peak) of the consensus distribution at this point | | `stdDev` | `number` | Standard deviation at this point | | `percentiles` | `PercentileSet` | 9-percentile band data (p2.5 through p97.5) | **Usage:** Returned by `transformHistoryToFanChart()`. Each point represents one snapshot in the market's history with full percentile bands for fan chart rendering in `TimelineChart`. --- #### `FSConfig` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `baseUrl` | `string` | API base URL (required) | | `username` | `string?` | Username for auto-authentication | | `password` | `string?` | Password for auto-authentication | | `autoAuthenticate` | `boolean?` | Auto-login on mount when credentials are present (default: `true` if both username and password are set) | **Usage:** Passed to `new FSClient(config)` in core-only mode, or to the `config` prop of `FunctionSpaceProvider` in React mode. --- #### `MarketConfig` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `K` | `number` | Number of outcome buckets (belief vector length is `K + 1`) | | `L` | `number` | Lower bound of the outcome space | | `H` | `number` | Upper bound of the outcome space | | `P0` | `number` | Initial pool endowment | | `mu` | `number` | AMM sensitivity parameter | | `epsAlpha` | `number` | Minimum alpha component | | `tau` | `number` | Trade fee parameter | | `gamma` | `number` | Claim calculation parameter | | `lambdaS` | `number` | Supply-side scaling | | `lambdaD` | `number` | Demand-side scaling | **Usage:** Accessed via `market.config`. The three fields you use most often are `K`, `L`, and `H` -- always destructure them for belief construction: `const { K, L, H } = market.config;` --- #### `MarketHistory` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `marketId` | `number` | Market identifier | | `totalSnapshots` | `number` | Total number of snapshots available server-side | | `snapshots` | `MarketSnapshot[]` | Array of alpha vector snapshots ordered by trade | **Usage:** Returned by `queryMarketHistory()` and `useMarketHistory()`. Consumed by `transformHistoryToFanChart()` for timeline visualization. --- #### `MarketSnapshot` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `snapshotId` | `number` | Unique snapshot identifier | | `tradeId` | `number` | Trade that produced this snapshot | | `side` | `'buy' \| 'sell'` | Trade direction | | `positionId` | `string` | Position involved in the trade | | `alphaVector` | `number[]` | Alpha vector (market state) at this point in time | | `totalDeposits` | `number` | Cumulative deposits | | `totalWithdrawals` | `number` | Cumulative withdrawals | | `totalVolume` | `number` | Cumulative trading volume | | `currentPool` | `number` | Pool balance at this snapshot | | `numOpenPositions` | `number` | Open positions at this snapshot | | `createdAt` | `string` | ISO 8601 timestamp | **Usage:** Individual entries within `MarketHistory.snapshots`. Passed to `transformHistoryToFanChart()` for fan chart rendering. --- #### `MarketState` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `alpha` | `number[]` | Raw alpha vector (AMM internal state) | | `consensus` | `number[]` | Normalized consensus probability distribution | | `totalMass` | `number` | Total probability mass | | `poolBalance` | `number` | Current pool liquidity | | `participantCount` | `number` | Number of unique participants | | `totalVolume` | `number` | Total trading volume | | `positionsOpen` | `number` | Number of currently open positions | | `config` | `MarketConfig` | Market parameters (K, L, H, AMM params) | | `title` | `string` | Human-readable market question | | `xAxisUnits` | `string` | Units label for the outcome axis (e.g., `"degrees F"`) | | `decimals` | `number` | Display precision for outcome values | | `resolutionState` | `'open' \| 'resolved' \| 'voided'` | Market lifecycle state | | `resolvedOutcome` | `number \| null` | Actual outcome value if resolved, otherwise `null` | **Usage:** The most important type in the SDK. Returned by `queryMarketState()` and `useMarket()`. Contains everything needed to render charts, build beliefs, and display market information. Always destructure `config` for belief construction: `const { K, L, H } = market.config;` --- #### `PayoutCurve` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `previews` | `Array<{outcome: number; payout: number; profitLoss: number}>` | Per-outcome payout previews | | `maxPayout` | `number` | Best-case payout across all outcomes | | `maxPayoutOutcome` | `number` | The outcome value that produces `maxPayout` | | `inputCollateral` | `number` | Collateral amount used for preview | **Usage:** Returned by `previewPayoutCurve()`. Set on context via `ctx.setPreviewPayout()` so chart components can render the payout overlay curve. --- #### `PercentileSet` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `p2_5` | `number` | 2.5th percentile | | `p12_5` | `number` | 12.5th percentile | | `p25` | `number` | 25th percentile | | `p37_5` | `number` | 37.5th percentile | | `p50` | `number` | 50th percentile (median) | | `p62_5` | `number` | 62.5th percentile | | `p75` | `number` | 75th percentile | | `p87_5` | `number` | 87.5th percentile | | `p97_5` | `number` | 97.5th percentile | **Usage:** Returned by `computePercentiles()`. Embedded in `FanChartPoint` for timeline visualization band data. --- #### `Position` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `positionId` | `number` | Unique position identifier | | `belief` | `number[]` | The belief vector submitted when opening | | `collateral` | `number` | Collateral deposited | | `claims` | `number` | Number of claims held | | `owner` | `string` | Username of the position owner | | `status` | `'open' \| 'sold' \| 'settled' \| 'closed'` | Position lifecycle state | | `prediction` | `number` | Predicted outcome value (peak of belief) | | `stdDev` | `number` | Standard deviation of the belief distribution | | `createdAt` | `string` | ISO 8601 creation timestamp | | `closedAt` | `string \| null` | ISO 8601 close timestamp, `null` if still open | | `soldPrice` | `number \| null` | Collateral returned on sell, `null` if not sold | | `settlementPayout` | `number \| null` | Payout at settlement, `null` if not settled (settlement not yet implemented) | **Usage:** Returned by `queryMarketPositions()`, `queryPositionState()`, and `usePositions()`. Displayed in `PositionTable`. Selectable via `ctx.setSelectedPosition()` for chart overlay coordination. --- #### `PreviewSellResult` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `collateralReturned` | `number` | Previewed collateral to be returned | | `iterations` | `number` | Number of solver iterations used | **Usage:** Returned by `previewSell()`. Used by `PositionTable` to display previewed sell value before the user confirms. --- #### `RangeInput` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `low` | `number` | Start of the range, in outcome space | | `high` | `number` | End of the range, in outcome space | | `weight` | `number?` | Relative weight when combining ranges (default: 1) | | `sharpness` | `number?` | Edge transition: 0 = smooth cosine taper (default), 1 = hard cliff edges | **Usage:** Used by the multi-range overload of `generateRange(ranges: RangeInput[], K, L, H)`. Each `RangeInput` defines one flat-topped range segment; multiple segments are combined and normalized into a single belief vector. --- #### `Region` (Union Type) **Package:** `@functionspace/core` **Kind:** Union type ```typescript type Region = PointRegion | RangeRegion | SplineRegion; ``` Regions are the building blocks for `generateBelief()`. Multiple regions combine additively: their weighted kernels are summed, then the result is normalized to produce a valid probability distribution. ##### `PointRegion` | Field | Type | Description | |-------|------|-------------| | `type` | `'point'` | Discriminant (required) | | `center` | `number` | Center of the gaussian kernel, in outcome space | | `spread` | `number` | Width of the bell curve, in outcome-space units | | `weight` | `number?` | Relative weight when combining regions (default: 1) | | `skew` | `number?` | Asymmetry: negative = wider left tail, positive = wider right tail, 0 = symmetric | | `inverted` | `boolean?` | If `true`, produces a dip (high at edges, low at center) | ##### `RangeRegion` | Field | Type | Description | |-------|------|-------------| | `type` | `'range'` | Discriminant (required) | | `low` | `number` | Start of the flat-top range, in outcome space | | `high` | `number` | End of the flat-top range, in outcome space | | `weight` | `number?` | Relative weight when combining regions (default: 1) | | `sharpness` | `number?` | Edge transition: 0 = smooth cosine taper (default), 1 = hard cliff edges | ##### `SplineRegion` | Field | Type | Description | |-------|------|-------------| | `type` | `'spline'` | Discriminant (required) | | `controlX` | `number[]` | X positions in outcome space (`L..H`), must be sorted ascending | | `controlY` | `number[]` | Y values (unnormalized), same length as `controlX` | | `weight` | `number?` | Relative weight when combining regions (default: 1) | **Usage:** Passed as an array to `generateBelief(regions, K, L, H)`. The L2 convenience generators (`generateGaussian`, `generateRange`, etc.) internally construct single-region arrays and delegate to `generateBelief`. Use `Region[]` directly for multi-modal or complex composed beliefs. --- #### `SellResult` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `positionId` | `number` | ID of the sold position | | `collateralReturned` | `number` | Collateral amount returned to the trader | **Usage:** Returned by `sell()`. Passed to `onSell` callbacks on `PositionTable`. --- #### `ShapeDefinition` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `id` | `ShapeId` | Shape identifier (e.g. `'gaussian'`, `'range'`) | | `name` | `string` | Human-readable display name | | `description` | `string` | Short description of the shape's behavior | | `parameters` | `('targetOutcome' \| 'confidence' \| 'rangeValues' \| 'peakBias' \| 'skewAmount')[]` | Which parameter sliders the widget should show for this shape | | `svgPath` | `string` | SVG path data for the shape icon (viewBox `0 0 100 60`) | **Usage:** Elements of the `SHAPE_DEFINITIONS` constant array. Used by `ShapeCutter` to render the shape selection grid, determine which parameter controls to show, and display shape icons. --- #### `SignupOptions` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `accessCode` | `string?` | Optional access code for gated signups | **Usage:** Optional third parameter to `signupUser(client, username, password, options?)`. Also accepted by `ctx.signup()` in the React layer. --- #### `SignupResult` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `user` | `UserProfile` | Newly created user profile | **Usage:** Returned by `signupUser()`. Note: unlike `AuthResult`, `SignupResult` does not include a token. The user must call `loginUser()` after signup to obtain a session token (the React layer's `ctx.signup()` handles this automatically). --- #### `TradeEntry` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `id` | `string` | Unique trade entry identifier | | `timestamp` | `string` | ISO 8601 timestamp | | `side` | `'buy' \| 'sell'` | Trade direction | | `prediction` | `number \| null` | Predicted outcome (null for sells) | | `amount` | `number` | Trade amount (collateral for buys, returned collateral for sells) | | `username` | `string` | Trader username | | `positionId` | `string` | Associated position ID | **Usage:** Returned by `queryTradeHistory()` and `useTradeHistory()`. Displayed in `PositionTable` (trade history tab) and `TimeSales`. --- #### `UserProfile` **Package:** `@functionspace/core` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `userId` | `number` | Unique user identifier | | `username` | `string` | Display username | | `walletValue` | `number` | Current wallet balance | | `role` | `'trader' \| 'creator' \| 'admin'` | User role | **Usage:** Returned by `loginUser()`, `signupUser()`, `fetchCurrentUser()`. Accessible via `ctx.user` from context. Displayed by `AuthWidget`. --- ### 8.2 React/UI Types #### `FSContext` **Package:** `@functionspace/react` **Kind:** Interface The complete shape of the React context object, accessible via `useContext(FunctionSpaceContext)`. | Field | Type | Description | |-------|------|-------------| | `client` | `FSClient` | HTTP client instance for direct API calls | | `previewBelief` | `number[] \| null` | Active belief vector preview (set by trade inputs, read by charts) | | `setPreviewBelief` | `(belief: number[] \| null) => void` | Set the belief preview overlay | | `previewPayout` | `PayoutCurve \| null` | Active payout curve preview | | `setPreviewPayout` | `(payout: PayoutCurve \| null) => void` | Set the payout curve overlay | | `invalidate` | `(marketId: string \| number) => void` | Trigger data refetch across all hooks (the `marketId` parameter is accepted but currently triggers a global refetch) | | `invalidationCount` | `number` | Internal counter -- hooks watch this to detect invalidation | | `selectedPosition` | `Position \| null` | Currently selected position for chart/table sync | | `setSelectedPosition` | `(pos: Position \| null) => void` | Set the selected position | | `user` | `UserProfile \| null` | Current authenticated user, `null` in guest mode | | `isAuthenticated` | `boolean` | Whether a user is currently logged in | | `authLoading` | `boolean` | Whether an auth operation is in progress | | `authError` | `Error \| null` | Most recent auth error | | `login` | `(username: string, password: string) => Promise` | Interactive login action | | `signup` | `(username: string, password: string, options?: SignupOptions) => Promise` | Interactive signup (auto-logins after) | | `logout` | `() => void` | Clear token, user, previews; trigger invalidation | | `refreshUser` | `() => Promise` | Refresh user profile (wallet balance update) | | `chartColors` | `ChartColors` | Resolved chart colors for the current theme | **Usage:** Access via `useContext(FunctionSpaceContext)` from any component inside `FunctionSpaceProvider`. There is no `useFunctionSpace()` hook. The context is `null` outside the provider tree, so always null-check or use the pattern of a dedicated inner component. --- #### `FSTheme` **Package:** `@functionspace/react` **Kind:** Interface The theme object has three tiers: 9 required core tokens, and 21 optional tokens that are automatically derived from the core 9 if omitted. ##### Core 9 (Required) | Token | CSS Variable | Description | |-------|-------------|-------------| | `primary` | `--fs-primary` | Primary brand color (buttons, active states, consensus curve) | | `accent` | `--fs-accent` | Accent/highlight color (preview lines, secondary actions) | | `positive` | `--fs-positive` | Positive/success color (profit, buy side, payout) | | `negative` | `--fs-negative` | Negative/error color (loss, sell side, errors) | | `background` | `--fs-background` | Page background | | `surface` | `--fs-surface` | Card/panel background | | `text` | `--fs-text` | Primary text color | | `textSecondary` | `--fs-text-secondary` | Secondary text color | | `border` | `--fs-border` | Primary border color | ##### Tier 1 (Optional -- defaults derived from Core 9) | Token | CSS Variable | Falls Back To | |-------|-------------|---------------| | `bgSecondary` | `--fs-bg-secondary` | `background` | | `surfaceHover` | `--fs-surface-hover` | `surface` | | `borderSubtle` | `--fs-border-subtle` | `border` | | `textMuted` | `--fs-text-muted` | `textSecondary` | ##### Tier 2 (Optional -- component-specific) | Token | CSS Variable | Default | |-------|-------------|---------| | `navFrom` | `--fs-nav-from` | `background` | | `navTo` | `--fs-nav-to` | `background` | | `overlay` | `--fs-overlay` | `rgba(0,0,0,0.2)` | | `inputBg` | `--fs-input-bg` | `background` | | `codeBg` | `--fs-code-bg` | `background` | | `chartBg` | `--fs-chart-bg` | `background` | | `accentGlow` | `--fs-accent-glow` | `rgba(59,130,246,0.25)` | | `badgeBg` | `--fs-badge-bg` | `rgba(128,128,128,0.15)` | | `badgeBorder` | `--fs-badge-border` | `rgba(128,128,128,0.25)` | | `badgeText` | `--fs-badge-text` | `textSecondary` | | `logoFilter` | `--fs-logo-filter` | `none` | ##### Tier 3 (Optional -- shape/personality) | Token | CSS Variable | Default | |-------|-------------|---------| | `fontFamily` | `--fs-font-family` | `inherit` | | `radiusSm` | `--fs-radius-sm` | `0.375rem` | | `radiusMd` | `--fs-radius-md` | `0.75rem` | | `radiusLg` | `--fs-radius-lg` | `1rem` | | `borderWidth` | `--fs-border-width` | `1px` | | `transitionSpeed` | `--fs-transition-speed` | `200ms` | **Usage:** When building a fully custom theme without a preset base, provide all 9 core tokens. The 21 optional tokens are derived automatically. When extending a preset, provide only the tokens you want to override. --- #### `FSThemeInput` **Package:** `@functionspace/react` **Kind:** Type alias ```typescript type FSThemeInput = ThemePresetId | (Partial & { preset?: ThemePresetId }); ``` Accepts three forms: 1. **Preset string:** `"fs-dark"` -- resolves to the full 30-token preset. 2. **Preset with overrides:** `{ preset: "fs-dark", primary: "#ff00ff" }` -- starts from the preset, merges overrides. 3. **Custom object (no preset):** `{ primary: "#ff00ff", accent: "#00ffff", ... }` -- requires all 9 core tokens; optional tokens are derived. **Usage:** Passed to the `theme` prop on `FunctionSpaceProvider`. --- #### `ChartColors` **Package:** `@functionspace/react` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `grid` | `string` | `CartesianGrid` stroke color | | `axisText` | `string` | Axis label/tick fill color | | `tooltipBg` | `string` | Tooltip background color | | `tooltipBorder` | `string` | Tooltip border color | | `tooltipText` | `string` | Tooltip text color | | `crosshair` | `string` | Cursor/crosshair stroke color | | `consensus` | `string` | Consensus curve stroke/fill color | | `previewLine` | `string` | Trade preview line stroke color | | `payout` | `string` | Payout curve color (defaults to `theme.positive`) | | `positions` | `string[]` | Array of colors for position curve overlays | | `fanBands` | `FanBandColors` | Fan chart band colors | **Usage:** Accessible via `ctx.chartColors` from context. These are concrete hex/rgba values safe for Recharts SVG `stroke` and `fill` attributes. Do NOT use `var(--fs-*)` CSS variables in Recharts props -- use these resolved values instead. --- #### `FanBandColors` **Package:** `@functionspace/react` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `mean` | `string` | Mean line color | | `band25` | `string` | 25th-75th percentile band fill | | `band50` | `string` | 12.5th-87.5th percentile band fill | | `band75` | `string` | 5th-95th percentile band fill (deprecated label; wider band) | | `band95` | `string` | 2.5th-97.5th percentile band fill (outermost) | **Usage:** Nested within `ChartColors` as `ctx.chartColors.fanBands`. Used by `TimelineChart` for fan band fills. --- #### `OverlayCurve` **Package:** `@functionspace/ui` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `id` | `string` | Unique identifier for the overlay | | `label` | `string` | Display label for the legend | | `curve` | `Array<{x: number; y: number}>` | Data points for the curve | | `color` | `string?` | Override color (defaults to theme accent) | **Usage:** Passed to the `overlayCurves` prop on `ConsensusChart` or `MarketCharts` to render additional curves on the consensus view. --- #### `XPointMode` **Package:** `@functionspace/ui` **Kind:** Union type ```typescript type XPointMode = | { mode: 'static'; value: number } | { mode: 'variable'; initial?: number } | { mode: 'dynamic-mode'; allowOverride?: boolean } | { mode: 'dynamic-mean'; allowOverride?: boolean }; ``` | Variant | Description | |---------|-------------| | `static` | Fixed x-point, user cannot change it. Requires `value`. | | `variable` | User-editable x-point with optional `initial` value (defaults to midpoint). | | `dynamic-mode` | Auto-set to consensus peak (mode). `allowOverride` lets the user adjust. | | `dynamic-mean` | Auto-set to consensus expected value (mean). `allowOverride` lets the user adjust. | **Usage:** Passed to the `xPoint` prop on `BinaryPanel` to configure where the Yes/No threshold is placed. --- #### `TradeInputBaseProps` **Package:** `@functionspace/ui` **Kind:** Interface | Field | Type | Description | |-------|------|-------------| | `marketId` | `string \| number` | Target market identifier | | `onBuy` | `(result: BuyResult) => void` | Optional callback after successful buy | | `onError` | `(error: Error) => void` | Optional callback on trade error | **Usage:** Base contract for all trade input components. `BinaryPanel` formally extends this interface. Other trading components (`ShapeCutter`, `TradePanel`, `BucketRangeSelector`, `BucketTradePanel`, `CustomShapeEditor`) follow the same pattern but may not formally extend it. --- ### 8.3 Enum/Literal Types #### `ThemePresetId` **Package:** `@functionspace/react` ```typescript type ThemePresetId = 'fs-dark' | 'fs-light' | 'native-dark' | 'native-light'; ``` | Value | Description | |-------|-------------| | `'fs-dark'` | FunctionSpace branded dark theme (vibrant purples, blues) | | `'fs-light'` | FunctionSpace branded light theme | | `'native-dark'` | Neutral dark theme (minimal branding, suitable for embedding) | | `'native-light'` | Neutral light theme | --- #### `ShapeId` **Package:** `@functionspace/core` ```typescript type ShapeId = 'gaussian' | 'spike' | 'range' | 'bimodal' | 'dip' | 'leftskew' | 'rightskew' | 'uniform'; ``` | Value | Description | Generator Used | |-------|-------------|----------------| | `'gaussian'` | Standard bell curve | `generateGaussian` | | `'spike'` | Narrow, high-confidence gaussian | `generateGaussian` (small spread) | | `'range'` | Flat-top range belief | `generateRange` | | `'bimodal'` | Two peaks (two `PointRegion`s via `generateBelief`) | `generateBelief` | | `'dip'` | Inverted gaussian (high at edges) | `generateDip` | | `'leftskew'` | Left-skewed distribution | `generateLeftSkew` | | `'rightskew'` | Right-skewed distribution | `generateRightSkew` | | `'uniform'` | Flat distribution across all outcomes | `generateRange` (full range) | Used by `ShapeCutter` to render a grid of shape presets and by `SHAPE_DEFINITIONS` for shape metadata. --- #### `ChartView` **Package:** `@functionspace/ui` ```typescript type ChartView = 'consensus' | 'distribution' | 'timeline'; ``` | Value | Renders | |-------|---------| | `'consensus'` | Probability density area chart (`ConsensusChart`) | | `'distribution'` | Horizontal bar chart of probability buckets (`DistributionChart`) | | `'timeline'` | Fan chart showing consensus evolution over time (`TimelineChart`) | --- #### `PositionTabId` **Package:** `@functionspace/ui` ```typescript type PositionTabId = 'open-orders' | 'trade-history' | 'market-positions'; ``` | Value | Description | |-------|-------------| | `'open-orders'` | Current user's open positions with sell buttons | | `'trade-history'` | Chronological trade feed (buys and sells) | | `'market-positions'` | All positions from all participants | --- #### Position Status Defined inline on `Position.status`: ```typescript 'open' | 'sold' | 'settled' | 'closed' ``` | Value | Description | |-------|-------------| | `'open'` | Position is active, can be sold | | `'sold'` | Position was sold by the owner | | `'settled'` | Position was settled at market resolution (not yet implemented) | | `'closed'` | Position is closed (terminal state) | --- #### Resolution State Defined inline on `MarketState.resolutionState`: ```typescript 'open' | 'resolved' | 'voided' ``` | Value | Description | |-------|-------------| | `'open'` | Market is active, trading is allowed | | `'resolved'` | Market has been resolved with an actual outcome (see `resolvedOutcome`) | | `'voided'` | Market was voided (cancelled without resolution) | --- *Generated for AI coding assistants. Source: FunctionSpace Trading SDK. For package-specific guides, see `core.txt`, `react.txt`, and `ui.txt`.*