diff --git a/core/api-doc-config.generated.json b/core/api-doc-config.generated.json index 40aaff04..79797bdf 100644 --- a/core/api-doc-config.generated.json +++ b/core/api-doc-config.generated.json @@ -1,5 +1,5 @@ { - "_generated": "Auto-generated by extract-jsdoc.js on 2026-07-10T06:40:05.097Z. Do not edit manually.", + "_generated": "Auto-generated by extract-jsdoc.js on 2026-07-18T04:30:30.917Z. Do not edit manually.", "methods": { "has": { "summary": "HTTP verb for the endpoint (e.g. GET, POST). */", @@ -9,17 +9,114 @@ "type": "ExchangeHas", "description": "Result" }, - "source": "BaseExchange.ts:42" + "source": "BaseExchange.ts:46" }, - "implicitApi": { + "getAuthNonce": { "summary": "Override in subclasses to force specific capability values.", - "description": "Use `'emulated'` for methods backed by a non-native mechanism,\nor `false` for methods that override the base only to throw a\nbetter error message (e.g. \"pari-mutuel bets cannot be cancelled\").\n/\n protected readonly capabilityOverrides: Partial> = {};\n\n protected credentials?: ExchangeCredentials;\n // Implicit API (merged across multiple defineImplicitApi calls)\n protected apiDescriptor?: ApiDescriptor;\n private _throttler: Throttler;\n // Snapshot state for cursor-based pagination\n private _snapshotTTL: number;\n private _snapshot?: { markets: UnifiedMarket[]; takenAt: number; id: string };\n private _eventSnapshot?: { events: UnifiedEvent[]; takenAt: number; id: string };\n private apiDescriptors: ApiDescriptor[] = [];\n\n constructor(credentials?: ExchangeCredentials, options?: ExchangeOptions) {\n this.credentials = credentials;\n this._snapshotTTL = options?.snapshotTTL ?? 0;\n this.http = axios.create({\n headers: {\n 'User-Agent': `pmxt (https://github.com/pmxt-dev/pmxt)`\n },\n paramsSerializer: {\n serialize: (params) => {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n if (Array.isArray(v)) v.forEach((x) => sp.append(k, String(x)));\n else sp.append(k, String(v));\n }\n return sp.toString();\n },\n },\n });\n this._throttler = new Throttler({\n refillRate: 1 / this._rateLimit,\n capacity: 1,\n delay: 1,\n });\n\n // Rate Limit Interceptor\n this.http.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {\n if (this.enableRateLimit) {\n await this._throttler.throttle();\n }\n return config;\n });\n\n // Request Interceptor\n this.http.interceptors.request.use((config: InternalAxiosRequestConfig) => {\n if (this.verbose) {\n logger.debug(`-> ${config.method?.toUpperCase()} ${config.url}`);\n if (config.params) logger.debug('params:', { params: config.params });\n if (config.data) logger.debug('body:', { body: config.data });\n }\n return config;\n });\n\n // Response Interceptor\n this.http.interceptors.response.use(\n (response: AxiosResponse) => {\n if (this.verbose) {\n logger.debug(`<- ${response.status} ${response.statusText} ${response.config.url}`);\n }\n return response;\n },\n (error: any) => {\n if (this.verbose) {\n logger.debug(`REQUEST FAILED: ${error.config?.url}`, {\n error: error.message,\n status: error.response?.status,\n data: error.response?.data,\n });\n }\n return Promise.reject(error);\n }\n );\n }\n\n private _rateLimit: number = 1000;\n\n get rateLimit(): number {\n return this._rateLimit;\n }\n\n set rateLimit(value: number) {\n this._rateLimit = value;\n this._throttler = new Throttler({\n refillRate: 1 / value,\n capacity: 1,\n delay: 1,\n });\n }\n\n abstract get name(): string;\n\n /**\nIntrospection getter: returns info about all implicit API methods.", + "description": "Use `'emulated'` for methods backed by a non-native mechanism,\nor `false` for methods that override the base only to throw a\nbetter error message (e.g. \"pari-mutuel bets cannot be cancelled\").\n/\n protected readonly capabilityOverrides: Partial> = {};\n\n protected credentials?: ExchangeCredentials;\n // Implicit API (merged across multiple defineImplicitApi calls)\n protected apiDescriptor?: ApiDescriptor;\n private _throttler: Throttler;\n // Snapshot state for cursor-based pagination\n private _snapshotTTL: number;\n private _snapshot?: { markets: UnifiedMarket[]; takenAt: number; id: string };\n private _eventSnapshot?: { events: UnifiedEvent[]; takenAt: number; id: string };\n private apiDescriptors: ApiDescriptor[] = [];\n protected _sessions: Map = new Map();\n\n constructor(credentials?: ExchangeCredentials, options?: ExchangeOptions) {\n this.credentials = credentials;\n this._snapshotTTL = options?.snapshotTTL ?? 0;\n this.http = axios.create({\n headers: {\n 'User-Agent': `pmxt (https://github.com/pmxt-dev/pmxt)`\n },\n paramsSerializer: {\n serialize: (params) => {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n if (Array.isArray(v)) v.forEach((x) => sp.append(k, String(x)));\n else sp.append(k, String(v));\n }\n return sp.toString();\n },\n },\n });\n this._throttler = new Throttler({\n refillRate: 1 / this._rateLimit,\n capacity: 1,\n delay: 1,\n });\n\n // Rate Limit Interceptor\n this.http.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {\n if (this.enableRateLimit) {\n await this._throttler.throttle();\n }\n return config;\n });\n\n // Request Interceptor\n this.http.interceptors.request.use((config: InternalAxiosRequestConfig) => {\n if (this.verbose) {\n logger.debug(`-> ${config.method?.toUpperCase()} ${config.url}`);\n if (config.params) logger.debug('params:', { params: config.params });\n if (config.data) logger.debug('body:', { body: config.data });\n }\n return config;\n });\n\n // Response Interceptor\n this.http.interceptors.response.use(\n (response: AxiosResponse) => {\n if (this.verbose) {\n logger.debug(`<- ${response.status} ${response.statusText} ${response.config.url}`);\n }\n return response;\n },\n (error: any) => {\n if (this.verbose) {\n logger.debug(`REQUEST FAILED: ${error.config?.url}`, {\n error: error.message,\n status: error.response?.status,\n data: error.response?.data,\n });\n }\n return Promise.reject(error);\n }\n );\n }\n\n private _rateLimit: number = 1000;\n\n get rateLimit(): number {\n return this._rateLimit;\n }\n\n set rateLimit(value: number) {\n this._rateLimit = value;\n this._throttler = new Throttler({\n refillRate: 1 / value,\n capacity: 1,\n delay: 1,\n });\n }\n\n abstract get name(): string;\n\n /**\nGet a cryptographic nonce for Web3 login.", + "params": [ + { + "name": "walletAddress", + "type": "string", + "optional": false, + "description": "The wallet address to authenticate" + } + ], + "returns": { + "type": "AuthNonceResponse", + "description": "Result" + }, + "source": "BaseExchange.ts:463" + }, + "loginWithSignature": { + "summary": "Login with a signed nonce to obtain session credentials.", + "description": "Login with a signed nonce to obtain session credentials.", + "params": [ + { + "name": "walletAddress", + "type": "string", + "optional": false, + "description": "The wallet address" + }, + { + "name": "signature", + "type": "string", + "optional": false, + "description": "The signature of the nonce message" + }, + { + "name": "nonce", + "type": "string", + "optional": false, + "description": "The nonce that was signed" + } + ], + "returns": { + "type": "AuthLoginResponse", + "description": "Result" + }, + "source": "BaseExchange.ts:571" + }, + "logout": { + "summary": "Logout and invalidate the current session.", + "description": "Logout and invalidate the current session.", + "params": [ + { + "name": "walletAddress", + "type": "string", + "optional": true, + "description": "The wallet address to logout (optional)" + } + ], + "returns": { + "type": "void", + "description": "Result" + }, + "source": "BaseExchange.ts:585" + }, + "isSessionActive": { + "summary": "Check if a session is active for the given wallet address.", + "description": "Check if a session is active for the given wallet address.", + "params": [ + { + "name": "walletAddress", + "type": "string", + "optional": false, + "description": "The wallet address to check" + } + ], + "returns": { + "type": "boolean", + "description": "Result" + }, + "source": "BaseExchange.ts:593" + }, + "getSession": { + "summary": "Get the stored session for a wallet address.", + "description": "SECURITY: This is an in-process helper only and is deliberately excluded\nfrom the HTTP/RPC server surface (see EXCLUDED_METHODS in\nscripts/generate-openapi.js). It returns session credentials (apiKey /\napiSecret / passphrase), so it must never be reachable over the network,\nwhere the wallet address — a public value — would otherwise let any caller\nretrieve another user's secrets.", + "params": [ + { + "name": "walletAddress", + "type": "string", + "optional": false, + "description": "The wallet address" + } + ], + "returns": { + "type": "AuthSession | undefined", + "description": "Result" + }, + "source": "BaseExchange.ts:607" + }, + "implicitApi": { + "summary": "Introspection getter: returns info about all implicit API methods.", + "description": "Introspection getter: returns info about all implicit API methods.", "params": [], "returns": { "type": "ImplicitApiMethodInfo[]", "description": "Result" }, - "source": "BaseExchange.ts:459" + "source": "BaseExchange.ts:657" }, "loadMarkets": { "summary": "Load and cache all markets from the exchange into `this.markets` and `this.marketsBySlug`.", @@ -36,7 +133,7 @@ "type": "Record", "description": "Dictionary of markets indexed by marketId" }, - "source": "BaseExchange.ts:572" + "source": "BaseExchange.ts:671" }, "fetchMarkets": { "summary": "Fetch markets with optional filtering, search, or slug lookup.", @@ -83,7 +180,7 @@ "ordering — exchanges may reorder or add markets between requests. For stable iteration\nacross pages, use `loadMarkets()` and paginate over `Object.values(exchange.markets)`.", "Some exchanges (like Limitless) may only support status 'active' for search results." ], - "source": "BaseExchange.ts:609" + "source": "BaseExchange.ts:708" }, "fetchMarketsPaginated": { "summary": "Fetch markets with cursor-based pagination backed by a stable in-memory snapshot.", @@ -110,7 +207,7 @@ "type": "PaginatedMarketsResult", "description": "PaginatedMarketsResult with data, total, and optional nextCursor" }, - "source": "BaseExchange.ts:664" + "source": "BaseExchange.ts:763" }, "fetchEventsPaginated": { "summary": "Paginated variant of {@link fetchEvents}.", @@ -137,7 +234,7 @@ "type": "PaginatedEventsResult", "description": "PaginatedEventsResult with data, total, and optional nextCursor" }, - "source": "BaseExchange.ts:733" + "source": "BaseExchange.ts:832" }, "fetchEvents": { "summary": "Fetch events with optional keyword search.", @@ -175,7 +272,7 @@ "notes": [ "Some exchanges (like Limitless) may only support status 'active' for search results." ], - "source": "BaseExchange.ts:802" + "source": "BaseExchange.ts:901" }, "fetchSeries": { "summary": "Fetch the recurring series (fourth tier above Event -> Market -> Outcome)", @@ -192,7 +289,7 @@ "type": "UnifiedSeries[]", "description": "Array of unified series. Always an array, including the singular-lookup case." }, - "source": "BaseExchange.ts:841" + "source": "BaseExchange.ts:940" }, "fetchMarket": { "summary": "Fetch a single market by lookup parameters.", @@ -209,7 +306,7 @@ "type": "UnifiedMarket", "description": "A single unified market" }, - "source": "BaseExchange.ts:859" + "source": "BaseExchange.ts:958" }, "fetchEvent": { "summary": "Fetch a single event by lookup parameters.", @@ -226,7 +323,7 @@ "type": "UnifiedEvent", "description": "A single unified event" }, - "source": "BaseExchange.ts:959" + "source": "BaseExchange.ts:1058" }, "fetchEventMetadata": { "summary": "Fetch venue-native metadata for a specific event when the exchange", @@ -243,7 +340,7 @@ "type": "Record", "description": "Result" }, - "source": "BaseExchange.ts:976" + "source": "BaseExchange.ts:1075" }, "fetchOHLCV": { "summary": "Fetch historical OHLCV (candlestick) price data for a specific market outcome.", @@ -271,7 +368,7 @@ "Polymarket: outcomeId is the CLOB Token ID. Kalshi: outcomeId is the Market Ticker.", "Common resolutions: '1m' | '5m' | '15m' | '1h' | '6h' | '1d'. Arbitrary intervals (e.g. '30s', '120s', '3h') accepted by venues that support them." ], - "source": "BaseExchange.ts:987" + "source": "BaseExchange.ts:1086" }, "fetchOrderBook": { "summary": "Fetch the order book (bids/asks) for a specific outcome.", @@ -300,7 +397,7 @@ "type": "OrderBook", "description": "Order book with bids and asks. Returns OrderBook[] when" }, - "source": "BaseExchange.ts:1002" + "source": "BaseExchange.ts:1101" }, "fetchOrderBooks": { "summary": "Batch variant of {@link fetchOrderBook}. Fetches order books for", @@ -317,7 +414,7 @@ "type": "Record", "description": "A map keyed by the input id (preserving the caller's exact" }, - "source": "BaseExchange.ts:1030" + "source": "BaseExchange.ts:1129" }, "fetchTrades": { "summary": "Fetch raw trade history for a specific outcome.", @@ -343,7 +440,7 @@ "notes": [ "Polymarket requires an API key for trade history. Use fetchOHLCV for public historical data." ], - "source": "BaseExchange.ts:1043" + "source": "BaseExchange.ts:1142" }, "createOrder": { "summary": "Place a new order on the exchange.", @@ -360,7 +457,7 @@ "type": "Order", "description": "The created order" }, - "source": "BaseExchange.ts:1060" + "source": "BaseExchange.ts:1159" }, "buildOrder": { "summary": "Build an order payload without submitting it to the exchange.", @@ -377,7 +474,7 @@ "type": "BuiltOrder", "description": "A BuiltOrder containing the exchange-native payload" }, - "source": "BaseExchange.ts:1074" + "source": "BaseExchange.ts:1173" }, "submitOrder": { "summary": "Submit a pre-built order returned by buildOrder().", @@ -394,7 +491,7 @@ "type": "Order", "description": "The submitted order" }, - "source": "BaseExchange.ts:1086" + "source": "BaseExchange.ts:1185" }, "cancelOrder": { "summary": "Cancel an existing open order.", @@ -411,7 +508,7 @@ "type": "Order", "description": "The cancelled order" }, - "source": "BaseExchange.ts:1096" + "source": "BaseExchange.ts:1195" }, "fetchOrder": { "summary": "Fetch a specific order by ID.", @@ -428,7 +525,7 @@ "type": "Order", "description": "The order details" }, - "source": "BaseExchange.ts:1106" + "source": "BaseExchange.ts:1205" }, "fetchOpenOrders": { "summary": "Fetch all open orders, optionally filtered by market.", @@ -445,7 +542,7 @@ "type": "Order[]", "description": "Array of open orders" }, - "source": "BaseExchange.ts:1116" + "source": "BaseExchange.ts:1215" }, "fetchMyTrades": { "summary": "Fetch authenticated user trade history.", @@ -488,7 +585,7 @@ "type": "UserTrade[]", "description": "Array of user trades" }, - "source": "BaseExchange.ts:1126" + "source": "BaseExchange.ts:1225" }, "fetchClosedOrders": { "summary": "Fetch authenticated closed orders.", @@ -527,7 +624,7 @@ "type": "Order[]", "description": "Array of closed orders" }, - "source": "BaseExchange.ts:1142" + "source": "BaseExchange.ts:1241" }, "fetchAllOrders": { "summary": "Fetch authenticated order history across open and closed orders.", @@ -566,7 +663,7 @@ "type": "Order[]", "description": "Array of orders" }, - "source": "BaseExchange.ts:1157" + "source": "BaseExchange.ts:1256" }, "fetchPositions": { "summary": "Fetch current user positions across all markets.", @@ -583,7 +680,7 @@ "type": "Position[]", "description": "Array of user positions" }, - "source": "BaseExchange.ts:1172" + "source": "BaseExchange.ts:1271" }, "fetchBalance": { "summary": "Fetch account balances.", @@ -600,7 +697,7 @@ "type": "Balance[]", "description": "Array of account balances" }, - "source": "BaseExchange.ts:1182" + "source": "BaseExchange.ts:1281" }, "getExecutionPrice": { "summary": "Calculate the volume-weighted average execution price for a given order size.", @@ -629,7 +726,7 @@ "type": "number", "description": "Average execution price, or 0 if insufficient liquidity" }, - "source": "BaseExchange.ts:1192" + "source": "BaseExchange.ts:1291" }, "getExecutionPriceDetailed": { "summary": "Calculate detailed execution price information including partial fill data.", @@ -658,7 +755,7 @@ "type": "ExecutionPriceResult", "description": "Detailed execution result with price, filled amount, and fill status" }, - "source": "BaseExchange.ts:1205" + "source": "BaseExchange.ts:1304" }, "filterMarkets": { "summary": "Filter a list of markets by criteria.", @@ -681,7 +778,7 @@ "type": "UnifiedMarket[]", "description": "Filtered array of markets" }, - "source": "BaseExchange.ts:1221" + "source": "BaseExchange.ts:1320" }, "filterEvents": { "summary": "Filter a list of events by criteria.", @@ -704,7 +801,7 @@ "type": "UnifiedEvent[]", "description": "Filtered array of events" }, - "source": "BaseExchange.ts:1381" + "source": "BaseExchange.ts:1480" }, "watchOrderBook": { "summary": "Watch order book updates in real-time via WebSocket.", @@ -733,7 +830,7 @@ "type": "OrderBook", "description": "Promise that resolves with the current orderbook state" }, - "source": "BaseExchange.ts:1477" + "source": "BaseExchange.ts:1576" }, "watchOrderBooks": { "summary": "Watch multiple order books simultaneously via WebSocket.", @@ -762,7 +859,7 @@ "type": "Record", "description": "Promise that resolves with order books keyed by ID" }, - "source": "BaseExchange.ts:1490" + "source": "BaseExchange.ts:1589" }, "unwatchOrderBook": { "summary": "Unsubscribe from a previously watched order book stream.", @@ -779,7 +876,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1518" + "source": "BaseExchange.ts:1617" }, "watchTrades": { "summary": "Watch trade executions in real-time via WebSocket.", @@ -814,7 +911,7 @@ "type": "Trade[]", "description": "Promise that resolves with recent trades" }, - "source": "BaseExchange.ts:1531" + "source": "BaseExchange.ts:1630" }, "watchAddress": { "summary": "Stream activity for a public wallet address", @@ -837,7 +934,7 @@ "type": "SubscribedAddressSnapshot", "description": "Promise that resolves with the latest SubscribedAddressSnapshot snapshot" }, - "source": "BaseExchange.ts:1545" + "source": "BaseExchange.ts:1644" }, "unwatchAddress": { "summary": "Stop watching a previously registered wallet address and release its resource updates.", @@ -854,7 +951,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1558" + "source": "BaseExchange.ts:1657" }, "close": { "summary": "Close all WebSocket connections and clean up resources.", @@ -864,7 +961,7 @@ "type": "void", "description": "Result" }, - "source": "BaseExchange.ts:1567" + "source": "BaseExchange.ts:1666" }, "fetchMarketMatches": { "summary": "Find the same or related market on other venues. Two modes:", @@ -881,7 +978,7 @@ "type": "MatchResult[]", "description": "Array of matched markets with relation and confidence" }, - "source": "BaseExchange.ts:1581" + "source": "BaseExchange.ts:1680" }, "fetchMatches": { "summary": "fetchMatches", @@ -898,7 +995,7 @@ "type": "MatchResult[]", "description": "Result" }, - "source": "BaseExchange.ts:1597" + "source": "BaseExchange.ts:1696" }, "fetchEventMatches": { "summary": "Find the same or related event on other venues. Two modes:", @@ -915,7 +1012,7 @@ "type": "EventMatchResult[]", "description": "Array of matched events with market-level match details" }, - "source": "BaseExchange.ts:1605" + "source": "BaseExchange.ts:1704" }, "compareMarketPrices": { "summary": "Compare live prices for the same market across venues. Finds identity matches and returns side-by-side best bid/ask prices so you can spot price differences at a glance.", @@ -923,7 +1020,7 @@ "params": [ { "name": "params", - "type": "FetchMatchesParams", + "type": "CompareMarketPricesParams", "optional": false, "description": "Match filter parameters (uses relation: 'identity' internally)" } @@ -932,7 +1029,7 @@ "type": "PriceComparison[]", "description": "Array of price comparisons across venues" }, - "source": "BaseExchange.ts:1621" + "source": "BaseExchange.ts:1720" }, "fetchRelatedMarkets": { "summary": "Find related markets across venues. Discovers subset/superset market relationships", @@ -949,7 +1046,7 @@ "type": "PriceComparison[]", "description": "Array of subset/superset matches with live prices" }, - "source": "BaseExchange.ts:1631" + "source": "BaseExchange.ts:1730" }, "fetchMatchedMarkets": { "summary": "fetchMatchedMarkets", @@ -966,7 +1063,7 @@ "type": "MatchedMarketPair[]", "description": "Result" }, - "source": "BaseExchange.ts:1642" + "source": "BaseExchange.ts:1741" }, "fetchMatchedPrices": { "summary": "fetchMatchedPrices", @@ -983,7 +1080,7 @@ "type": "MatchedPricePair[]", "description": "Array of matched market pairs with prices from each venue" }, - "source": "BaseExchange.ts:1650" + "source": "BaseExchange.ts:1749" }, "fetchHedges": { "summary": "fetchHedges", @@ -1000,7 +1097,7 @@ "type": "PriceComparison[]", "description": "Array of subset/superset matches with live prices" }, - "source": "BaseExchange.ts:1661" + "source": "BaseExchange.ts:1760" }, "fetchArbitrage": { "summary": "fetchArbitrage", @@ -1017,7 +1114,7 @@ "type": "ArbitrageOpportunity[]", "description": "Array of arbitrage opportunities sorted by spread" }, - "source": "BaseExchange.ts:1671" + "source": "BaseExchange.ts:1770" }, "watchPrices": { "summary": "Watch AMM price updates for a market address (Limitless only).", @@ -1041,7 +1138,7 @@ "description": "Result" }, "exchangeOnly": "limitless", - "source": "index.ts:492" + "source": "index.ts:498" }, "watchUserPositions": { "summary": "Watch user positions in real-time (Limitless only).", @@ -1059,7 +1156,7 @@ "description": "Result" }, "exchangeOnly": "limitless", - "source": "index.ts:504" + "source": "index.ts:510" }, "watchUserTransactions": { "summary": "Watch user transactions in real-time (Limitless only).", @@ -1077,7 +1174,7 @@ "description": "Result" }, "exchangeOnly": "limitless", - "source": "index.ts:516" + "source": "index.ts:522" }, "initAuth": { "summary": "Initialize L2 API credentials for implicit API signing.", @@ -1124,7 +1221,7 @@ "description": "The UnifiedEvent, or null if not found" }, "exchangeOnly": "probable", - "source": "index.ts:152" + "source": "index.ts:197" }, "getEventBySlug": { "summary": "Fetch a single event by its URL slug (Probable only).", @@ -1142,7 +1239,7 @@ "description": "The UnifiedEvent, or null if not found" }, "exchangeOnly": "probable", - "source": "index.ts:171" + "source": "index.ts:216" }, "watchAllOrderBooks": { "summary": "Stream all orderbook updates across venues via the hosted WebSocket API.", @@ -1159,7 +1256,7 @@ "type": "FirehoseEvent", "description": "Next event with source, symbol, and orderbook" }, - "source": "client.ts:2057" + "source": "client.ts:2172" }, "firehose": { "summary": "Stream all orderbook updates across venues.", @@ -1176,7 +1273,7 @@ "type": "FirehoseEvent", "description": "Next event with source, symbol, and orderbook" }, - "source": "client.ts:2098" + "source": "client.ts:2213" } }, "workflowExample": { diff --git a/package-lock.json b/package-lock.json index f7483395..12c0bc50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4332,6 +4332,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/dedent": { "version": "1.7.2", "dev": true, @@ -9192,6 +9198,7 @@ "name": "pmxtjs", "version": "2.18.0", "dependencies": { + "decimal.js": "^10.6.0", "pmxt-core": "2.17.1", "ws": "^8.18.0" }, diff --git a/sdks/python/API_REFERENCE.md b/sdks/python/API_REFERENCE.md index eabcd275..2002e92b 100644 --- a/sdks/python/API_REFERENCE.md +++ b/sdks/python/API_REFERENCE.md @@ -127,6 +127,133 @@ exchange.has ``` +--- +### `get_auth_nonce` + +Override in subclasses to force specific capability values. + + +**Signature:** + +```python +def get_auth_nonce(wallet_address: str) -> AuthNonceResponse: +``` + +**Parameters:** + +- `wallet_address` (str): The wallet address to authenticate + +**Returns:** AuthNonceResponse - Result + +**Example:** + +```python +exchange.get_auth_nonce(wallet_address="...") +``` + + +--- +### `login_with_signature` + +Login with a signed nonce to obtain session credentials. + + +**Signature:** + +```python +def login_with_signature(wallet_address: str, signature: str, nonce: str) -> AuthLoginResponse: +``` + +**Parameters:** + +- `wallet_address` (str): The wallet address +- `signature` (str): The signature of the nonce message +- `nonce` (str): The nonce that was signed + +**Returns:** AuthLoginResponse - Result + +**Example:** + +```python +exchange.login_with_signature(wallet_address="...", signature="...", nonce="...") +``` + + +--- +### `logout` + +Logout and invalidate the current session. + + +**Signature:** + +```python +def logout(wallet_address: Optional[str] = None) -> None: +``` + +**Parameters:** + +- `wallet_address` (str) - **Optional**: The wallet address to logout (optional) + +**Returns:** None - Result + +**Example:** + +```python +exchange.logout(wallet_address="...") +``` + + +--- +### `is_session_active` + +Check if a session is active for the given wallet address. + + +**Signature:** + +```python +def is_session_active(wallet_address: str) -> bool: +``` + +**Parameters:** + +- `wallet_address` (str): The wallet address to check + +**Returns:** bool - Result + +**Example:** + +```python +exchange.is_session_active(wallet_address="...") +``` + + +--- +### `get_session` + +Get the stored session for a wallet address. + + +**Signature:** + +```python +def get_session(wallet_address: str) -> Optional[AuthSession]: +``` + +**Parameters:** + +- `wallet_address` (str): The wallet address + +**Returns:** Optional[AuthSession] - Result + +**Example:** + +```python +exchange.get_session(wallet_address="...") +``` + + --- ### `load_markets` @@ -1172,12 +1299,12 @@ Compare live prices for the same market across venues. Finds identity matches an **Signature:** ```python -def compare_market_prices(params: FetchMatchesParams) -> List[PriceComparison]: +def compare_market_prices(params: CompareMarketPricesParams) -> List[PriceComparison]: ``` **Parameters:** -- `params` (FetchMatchesParams): Match filter parameters (uses relation: 'identity' internally) +- `params` (CompareMarketPricesParams): Match filter parameters (uses relation: 'identity' internally) **Returns:** List[[PriceComparison](#pricecomparison)] - Array of price comparisons across venues diff --git a/sdks/typescript/API_REFERENCE.md b/sdks/typescript/API_REFERENCE.md index bbe5a1fe..d2d3e6ab 100644 --- a/sdks/typescript/API_REFERENCE.md +++ b/sdks/typescript/API_REFERENCE.md @@ -129,6 +129,133 @@ exchange.has ``` +--- +### `getAuthNonce` + +Override in subclasses to force specific capability values. + + +**Signature:** + +```typescript +async getAuthNonce(walletAddress: string): Promise +``` + +**Parameters:** + +- `walletAddress` (string): The wallet address to authenticate + +**Returns:** Promise - Result + +**Example:** + +```typescript +await exchange.getAuthNonce("...") +``` + + +--- +### `loginWithSignature` + +Login with a signed nonce to obtain session credentials. + + +**Signature:** + +```typescript +async loginWithSignature(walletAddress: string, signature: string, nonce: string): Promise +``` + +**Parameters:** + +- `walletAddress` (string): The wallet address +- `signature` (string): The signature of the nonce message +- `nonce` (string): The nonce that was signed + +**Returns:** Promise - Result + +**Example:** + +```typescript +await exchange.loginWithSignature("...", "...", "...") +``` + + +--- +### `logout` + +Logout and invalidate the current session. + + +**Signature:** + +```typescript +async logout(walletAddress?: string): Promise +``` + +**Parameters:** + +- `walletAddress` (string) - **Optional**: The wallet address to logout (optional) + +**Returns:** Promise - Result + +**Example:** + +```typescript +await exchange.logout({ walletAddress: "..." }) +``` + + +--- +### `isSessionActive` + +Check if a session is active for the given wallet address. + + +**Signature:** + +```typescript +async isSessionActive(walletAddress: string): Promise +``` + +**Parameters:** + +- `walletAddress` (string): The wallet address to check + +**Returns:** Promise - Result + +**Example:** + +```typescript +await exchange.isSessionActive("...") +``` + + +--- +### `getSession` + +Get the stored session for a wallet address. + + +**Signature:** + +```typescript +async getSession(walletAddress: string): Promise +``` + +**Parameters:** + +- `walletAddress` (string): The wallet address + +**Returns:** Promise - Result + +**Example:** + +```typescript +await exchange.getSession("...") +``` + + --- ### `loadMarkets` @@ -1174,12 +1301,12 @@ Compare live prices for the same market across venues. Finds identity matches an **Signature:** ```typescript -async compareMarketPrices(params: FetchMatchesParams): Promise +async compareMarketPrices(params: CompareMarketPricesParams): Promise ``` **Parameters:** -- `params` (FetchMatchesParams): Match filter parameters (uses relation: 'identity' internally) +- `params` (CompareMarketPricesParams): Match filter parameters (uses relation: 'identity' internally) **Returns:** Promise<[PriceComparison](#pricecomparison)[]> - Array of price comparisons across venues diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 9e0abc7f..750d8208 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -43,6 +43,7 @@ "unified" ], "dependencies": { + "decimal.js": "^10.6.0", "pmxt-core": "2.17.1", "ws": "^8.18.0" }, diff --git a/sdks/typescript/pmxt/hosted-typed-data.ts b/sdks/typescript/pmxt/hosted-typed-data.ts index 356d7e0f..4aaaf4c6 100644 --- a/sdks/typescript/pmxt/hosted-typed-data.ts +++ b/sdks/typescript/pmxt/hosted-typed-data.ts @@ -12,6 +12,7 @@ import { InvalidSignature } from "./hosted-errors"; import { to6dec } from "./hosted-mappers"; import { TypedData, loadEthers } from "./signers"; +import Decimal from 'decimal.js'; // The constants module is updated in a parallel-agent change to add these // allowlists. We import them at runtime so we don't hard-fail if the change @@ -478,7 +479,9 @@ function validatePolymarketSellEconomics( } } -const SIX_DEC_DIVISOR = 1_000_000; + + +const SIX_DEC_DIVISOR = new Decimal(1_000_000); function validateWorstPrice( message: Record, @@ -487,7 +490,8 @@ function validateWorstPrice( buildResponse: any, ): void { const worstPriceMicro = messageBigInt(message, "worst_price", "worstPrice"); - const worstPrice = Number(worstPriceMicro) / SIX_DEC_DIVISOR; + + const worstPrice = new Decimal(worstPriceMicro.toString()).div(SIX_DEC_DIVISOR); // Hosted MARKET orders pin worst_price to the tick-grid extreme by // design ("textbook market semantics"): the binding user protection is @@ -502,9 +506,10 @@ function validateWorstPrice( ), ).toLowerCase(); if (orderType === "market") { - if (!(worstPrice > 0 && worstPrice < 1)) { + + if (!(worstPrice.greaterThan(0) && worstPrice.lessThan(1))) { economicFail( - `worst_price expected within (0, 1) got ${worstPrice}`, + `worst_price expected within (0, 1) got ${worstPrice.toString()}`, ); } return; @@ -519,33 +524,39 @@ function validateWorstPrice( ); const slippagePct = toFiniteNumber(slippagePctRaw, "slippage_pct"); + + const bestPriceRaw = firstPresent( + getPath(buildResponse, "quote", "best_price"), + getField(buildResponse, "best_price"), + getField(buildResponse, "best_ask"), + getField(buildResponse, "bestAsk"), + ); + if (route === "polymarket_buy") { - const bestPrice = toFiniteNumber( - firstPresent( - getPath(buildResponse, "quote", "best_price"), - getField(buildResponse, "best_price"), - getField(buildResponse, "best_ask"), - getField(buildResponse, "bestAsk"), - ), - "quote.best_price", - ); - const upper = bestPrice * (1 + slippagePct / 100); - if (worstPrice > upper) { - economicFail(`worst_price expected <= ${upper} got ${worstPrice}`); + + const bestPrice = new Decimal(String(bestPriceRaw)); + const slippageFactor = new Decimal(slippagePct).div(100); + + const upper = bestPrice.times(new Decimal(1).plus(slippageFactor)); + + + if (worstPrice.greaterThan(upper)) { + economicFail( + `worst_price expected <= ${upper.toString()} got ${worstPrice.toString()}`, + ); } } else { - const bestPrice = toFiniteNumber( - firstPresent( - getPath(buildResponse, "quote", "best_price"), - getField(buildResponse, "best_price"), - getField(buildResponse, "best_bid"), - getField(buildResponse, "bestBid"), - ), - "quote.best_price", - ); - const lower = bestPrice * (1 - slippagePct / 100); - if (worstPrice < lower) { - economicFail(`worst_price expected >= ${lower} got ${worstPrice}`); + + const bestPrice = new Decimal(String(bestPriceRaw)); + const slippageFactor = new Decimal(slippagePct).div(100); + + const lower = bestPrice.times(new Decimal(1).minus(slippageFactor)); + + + if (worstPrice.lessThan(lower)) { + economicFail( + `worst_price expected >= ${lower.toString()} got ${worstPrice.toString()}`, + ); } } } diff --git a/sdks/typescript/tests/hosted-typed-data.test-decimal.test.ts b/sdks/typescript/tests/hosted-typed-data.test-decimal.test.ts new file mode 100644 index 00000000..97bf072c --- /dev/null +++ b/sdks/typescript/tests/hosted-typed-data.test-decimal.test.ts @@ -0,0 +1,51 @@ +import { validateWorstPrice } from '../pmxt/hosted-typed-data'; + +describe('validateWorstPrice with Decimal', () => { + it('should accept worst price within slippage bounds', () => { + const message = {}; + const order = { + worst_price: '500000', // 0.50 + best_price: '500000', // 0.50 + slippage_pct: '1', // 1% + }; + + expect(() => validateWorstPrice(message, order)).not.toThrow(); + }); + + it('should reject worst price above upper bound', () => { + const message = {}; + const order = { + worst_price: '510000', // 0.51 + best_price: '500000', // 0.50 + slippage_pct: '1', // 1% + }; + + expect(() => validateWorstPrice(message, order)).toThrow( + 'Worst price outside slippage bounds' + ); + }); + + it('should reject worst price below lower bound', () => { + const message = {}; + const order = { + worst_price: '490000', // 0.49 + best_price: '500000', // 0.50 + slippage_pct: '1', // 1% + }; + + expect(() => validateWorstPrice(message, order)).toThrow( + 'Worst price outside slippage bounds' + ); + }); + + it('should handle edge cases with Decimal precision', () => { + const message = {}; + const order = { + worst_price: '500000', // 0.50 + best_price: '500000', // 0.50 + slippage_pct: '0.1', // 0.1% + }; + + expect(() => validateWorstPrice(message, order)).not.toThrow(); + }); +}); \ No newline at end of file