Repository Public
1/**2 * Client of the router routes (docs/v4/ROUTER.md): `POST /derivatives/route` and `GET /derivatives/route/assets`. SIMULATION3 * ONLY — neither route places anything. The answer types are the repository's own (type-only imports, erased at build).4 * Also here: the hand-over to the PAPER order panel («Ouvrir sur Hyperliquid en simulation»), which only pre-fills a form.5 */6import { get, post } from '../api';7import type { CostComponent, CostId, Differences, ExecutionEstimate, OrderIntent, Ranking, RankingRow, Reason, RouterSource, TermKey } from '../../../../packages/derivatives/src/router.ts';8import type { VenueId } from './types';910export type { CostComponent, CostId, Differences, ExecutionEstimate, OrderIntent, Ranking, RankingRow, Reason, RouterSource, TermKey };11export interface PolicyCheck { accountId: string; label: string; kind: 'paper' | 'venue'; mode: string; network: string; accepted: boolean | null; code: string | null; reason: string | null; source: 'risk_engine' | 'not_checked' }12export type RoutedCandidate = ExecutionEstimate & { risk: { venue: { accepted: boolean; codes: string[]; note: string }; accounts: PolicyCheck[] } };13export interface RouteAnswer { intent: OrderIntent; candidates: RoutedCandidate[]; ranking: Ranking; differences: Differences; warnings: string[]; errors: { venue: string; message: string }[]; ts: number }14export interface RouteAsset { base: string; venues: { venue: VenueId; name: string; instruments: string[]; restriction: string | null }[]; candidateVenues: number }15/** what the form sends: the intent as the router takes it (decimal strings), plus the accounts whose policy is asked */16export interface RouteBody { base: string; side: 'long' | 'short'; sizeBase?: string; notionalUsd?: string; leverage: string; type: 'market' | 'limit'; price: string | null; reduceOnly: boolean; horizonHours: string; accountIds: string[] }1718export const rapi = {19 assets: async (): Promise<RouteAsset[]> => { const j = await get('/derivatives/route/assets'); return Array.isArray(j.assets) ? j.assets : []; },20 route: async (body: RouteBody): Promise<RouteAnswer> => {21 const j = await post('/derivatives/route', body);22 return { intent: j.intent, candidates: Array.isArray(j.candidates) ? j.candidates : [], ranking: j.ranking, differences: j.differences, warnings: Array.isArray(j.warnings) ? j.warnings : [], errors: Array.isArray(j.errors) ? j.errors : [], ts: Number(j.ts) || Date.now() };23 },24};2526// ---------------------------------------------------------------- hand-over to the paper order panel (a pre-filled form, nothing more)27export interface PaperPrefill { instrument: string; side: 'buy' | 'sell'; kind: 'market' | 'limit'; unit: 'base'; size: string; lev: number; price: string; reduceOnly: boolean }28const KEY = 'spw-fut-prefill';29export function setPaperPrefill(p: PaperPrefill): void { try { sessionStorage.setItem(KEY, JSON.stringify(p)); } catch { /* private mode: the panel simply opens empty */ } }30const read = (): PaperPrefill | null => { try { const s = sessionStorage.getItem(KEY); return s ? (JSON.parse(s) as PaperPrefill) : null; } catch { return null; } };31/** is a hand-over waiting for this instrument (the terminal opens its order tab) */32export const hasPaperPrefill = (instrument: string): boolean => read()?.instrument === instrument;33/** the waiting hand-over for this instrument. Reading does not consume it (a state initializer may run twice in development): the panel clears it once mounted. */34export function peekPaperPrefill(instrument: string): PaperPrefill | null { const p = read(); return p && p.instrument === instrument ? p : null; }35/** consumed: the hand-over fills the form once */36export function clearPaperPrefill(instrument: string): void { if (read()?.instrument === instrument) { try { sessionStorage.removeItem(KEY); } catch { /* nothing to clear */ } } }37