pmxt-core 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/exchanges/kalshi/api.d.ts +1 -1
  2. package/dist/exchanges/kalshi/api.js +1 -1
  3. package/dist/exchanges/limitless/api.d.ts +1 -1
  4. package/dist/exchanges/limitless/api.js +1 -1
  5. package/dist/exchanges/myriad/api.d.ts +1 -1
  6. package/dist/exchanges/myriad/api.js +1 -1
  7. package/dist/exchanges/opinion/api.d.ts +1 -1
  8. package/dist/exchanges/opinion/api.js +1 -1
  9. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  10. package/dist/exchanges/polymarket/api-clob.js +1 -1
  11. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  12. package/dist/exchanges/polymarket/api-data.js +1 -1
  13. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  14. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  15. package/dist/exchanges/polymarket/auth.js +42 -8
  16. package/dist/exchanges/polymarket/index.js +56 -19
  17. package/dist/exchanges/polymarket_us/config.d.ts +18 -0
  18. package/dist/exchanges/polymarket_us/config.js +22 -0
  19. package/dist/exchanges/polymarket_us/errors.d.ts +19 -0
  20. package/dist/exchanges/polymarket_us/errors.js +123 -0
  21. package/dist/exchanges/polymarket_us/errors.test.d.ts +1 -0
  22. package/dist/exchanges/polymarket_us/errors.test.js +54 -0
  23. package/dist/exchanges/polymarket_us/index.d.ts +90 -0
  24. package/dist/exchanges/polymarket_us/index.js +366 -0
  25. package/dist/exchanges/polymarket_us/index.test.d.ts +8 -0
  26. package/dist/exchanges/polymarket_us/index.test.js +237 -0
  27. package/dist/exchanges/polymarket_us/normalizer.d.ts +55 -0
  28. package/dist/exchanges/polymarket_us/normalizer.js +385 -0
  29. package/dist/exchanges/polymarket_us/normalizer.test.d.ts +1 -0
  30. package/dist/exchanges/polymarket_us/normalizer.test.js +224 -0
  31. package/dist/exchanges/polymarket_us/price.d.ts +94 -0
  32. package/dist/exchanges/polymarket_us/price.js +149 -0
  33. package/dist/exchanges/polymarket_us/price.test.d.ts +1 -0
  34. package/dist/exchanges/polymarket_us/price.test.js +131 -0
  35. package/dist/exchanges/polymarket_us/websocket.d.ts +39 -0
  36. package/dist/exchanges/polymarket_us/websocket.js +181 -0
  37. package/dist/exchanges/polymarket_us/websocket.test.d.ts +8 -0
  38. package/dist/exchanges/polymarket_us/websocket.test.js +162 -0
  39. package/dist/exchanges/probable/api.d.ts +1 -1
  40. package/dist/exchanges/probable/api.js +1 -1
  41. package/dist/exchanges/smarkets/api.d.ts +8067 -0
  42. package/dist/exchanges/smarkets/api.js +10698 -0
  43. package/dist/exchanges/smarkets/auth.d.ts +56 -0
  44. package/dist/exchanges/smarkets/auth.js +105 -0
  45. package/dist/exchanges/smarkets/config.d.ts +41 -0
  46. package/dist/exchanges/smarkets/config.js +47 -0
  47. package/dist/exchanges/smarkets/errors.d.ts +31 -0
  48. package/dist/exchanges/smarkets/errors.js +186 -0
  49. package/dist/exchanges/smarkets/fetcher.d.ts +177 -0
  50. package/dist/exchanges/smarkets/fetcher.js +342 -0
  51. package/dist/exchanges/smarkets/index.d.ts +54 -0
  52. package/dist/exchanges/smarkets/index.js +285 -0
  53. package/dist/exchanges/smarkets/normalizer.d.ts +18 -0
  54. package/dist/exchanges/smarkets/normalizer.js +267 -0
  55. package/dist/exchanges/smarkets/price.d.ts +26 -0
  56. package/dist/exchanges/smarkets/price.js +44 -0
  57. package/dist/exchanges/smarkets/price.test.d.ts +1 -0
  58. package/dist/exchanges/smarkets/price.test.js +50 -0
  59. package/dist/index.d.ts +8 -0
  60. package/dist/index.js +9 -1
  61. package/dist/server/app.js +18 -2
  62. package/package.json +4 -3
@@ -0,0 +1,94 @@
1
+ import type { OrderIntent, Amount } from 'polymarket-us';
2
+ /**
3
+ * Polymarket US price/quantity conversion utilities.
4
+ *
5
+ * CRITICAL CONVENTION: Polymarket US uses a LONG-SIDE PRICE convention.
6
+ * The `price.value` field on every order is ALWAYS the YES-side (long)
7
+ * price between $0.01 and $0.99 - even for orders on the NO side.
8
+ *
9
+ * To express "I want NO at $0.40", you submit a long-side price of
10
+ * `1.00 - 0.40 = $0.60`.
11
+ *
12
+ * This module exposes pure helpers to convert between user-facing
13
+ * side prices and the long-side prices the API expects.
14
+ */
15
+ export declare const POLYMARKET_US_MIN_PRICE = 0.01;
16
+ export declare const POLYMARKET_US_MAX_PRICE = 0.99;
17
+ /**
18
+ * Default minimum price increment for Polymarket US markets. Observed live
19
+ * markets report `orderPriceMinTickSize: 0.001`, so we default to that.
20
+ * Per-market overrides (surfaced on `UnifiedMarket.tickSize`) should be
21
+ * preferred when available.
22
+ */
23
+ export declare const POLYMARKET_US_TICK_SIZE = 0.001;
24
+ /**
25
+ * Number of decimal places used when serializing `Amount.value` strings.
26
+ * Matches the 0.001 tick-size precision observed on the live gateway
27
+ * (e.g. `"0.864"`, `"0.999"`).
28
+ */
29
+ export declare const POLYMARKET_US_PRICE_DECIMALS = 3;
30
+ export declare const POLYMARKET_US_CURRENCY: 'USD';
31
+ /**
32
+ * Round a price to a tick size using Math.round. Defaults to the
33
+ * Polymarket US tick size (0.001) but accepts a per-market override.
34
+ * Re-rounds to `POLYMARKET_US_PRICE_DECIMALS` afterwards to avoid
35
+ * floating-point drift.
36
+ *
37
+ * Note: this is a pure rounding helper - it does NOT validate bounds.
38
+ * Out-of-range inputs (e.g. 0.9999 -> 1.000) will pass through unchanged.
39
+ */
40
+ export declare function roundToTickSize(price: number, tickSize?: number): number;
41
+ /**
42
+ * Validate a price is finite and within [POLYMARKET_US_MIN_PRICE, POLYMARKET_US_MAX_PRICE].
43
+ *
44
+ * @throws RangeError if price is not finite or out of bounds.
45
+ */
46
+ export declare function validatePriceBounds(price: number): void;
47
+ /**
48
+ * Convert a user-supplied price (in their natural side, 0.01-0.99) to the
49
+ * long-side price required by the Polymarket US API.
50
+ *
51
+ * For LONG intents (BUY_LONG, SELL_LONG) the user price is already the
52
+ * long-side price and is returned unchanged.
53
+ *
54
+ * For SHORT intents (BUY_SHORT, SELL_SHORT) the long-side price is
55
+ * `1 - userPrice`.
56
+ *
57
+ * Both the input and the resulting long-side output are validated against
58
+ * the Polymarket US price bounds.
59
+ *
60
+ * @throws RangeError if userPrice (or the derived long-side price) is not
61
+ * finite or outside [0.01, 0.99].
62
+ */
63
+ export declare function toLongSidePrice(intent: OrderIntent, userPrice: number): number;
64
+ /**
65
+ * Inverse of `toLongSidePrice`. Given a long-side price returned by the API
66
+ * and the order's intent, recover the user-facing side price.
67
+ *
68
+ * For LONG intents the long-side price IS the user price.
69
+ * For SHORT intents the user price is `1 - longPrice`.
70
+ *
71
+ * @throws RangeError if longPrice (or the derived user price) is not finite
72
+ * or outside [0.01, 0.99].
73
+ */
74
+ export declare function fromLongSidePrice(intent: OrderIntent, longPrice: number): number;
75
+ /**
76
+ * Build an SDK `Amount` object from a numeric USD price.
77
+ *
78
+ * The price is first rounded to tick size and then validated against the
79
+ * Polymarket US bounds. The resulting value is formatted as a fixed
80
+ * `POLYMARKET_US_PRICE_DECIMALS`-decimal string (e.g. "0.550"), matching
81
+ * the precision the live gateway reports.
82
+ *
83
+ * An optional `tickSize` override allows per-market tick sizes (as
84
+ * surfaced on `UnifiedMarket.tickSize`) to be honoured instead of the
85
+ * default `POLYMARKET_US_TICK_SIZE`.
86
+ *
87
+ * @throws RangeError if the rounded price is out of bounds.
88
+ */
89
+ export declare function toAmount(price: number, tickSize?: number): Amount;
90
+ /**
91
+ * Parse an SDK `Amount` back to a number. Returns 0 for undefined or empty
92
+ * values; otherwise delegates to `parseFloat`.
93
+ */
94
+ export declare function fromAmount(amount: Amount | undefined): number;
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POLYMARKET_US_CURRENCY = exports.POLYMARKET_US_PRICE_DECIMALS = exports.POLYMARKET_US_TICK_SIZE = exports.POLYMARKET_US_MAX_PRICE = exports.POLYMARKET_US_MIN_PRICE = void 0;
4
+ exports.roundToTickSize = roundToTickSize;
5
+ exports.validatePriceBounds = validatePriceBounds;
6
+ exports.toLongSidePrice = toLongSidePrice;
7
+ exports.fromLongSidePrice = fromLongSidePrice;
8
+ exports.toAmount = toAmount;
9
+ exports.fromAmount = fromAmount;
10
+ /**
11
+ * Polymarket US price/quantity conversion utilities.
12
+ *
13
+ * CRITICAL CONVENTION: Polymarket US uses a LONG-SIDE PRICE convention.
14
+ * The `price.value` field on every order is ALWAYS the YES-side (long)
15
+ * price between $0.01 and $0.99 - even for orders on the NO side.
16
+ *
17
+ * To express "I want NO at $0.40", you submit a long-side price of
18
+ * `1.00 - 0.40 = $0.60`.
19
+ *
20
+ * This module exposes pure helpers to convert between user-facing
21
+ * side prices and the long-side prices the API expects.
22
+ */
23
+ exports.POLYMARKET_US_MIN_PRICE = 0.01;
24
+ exports.POLYMARKET_US_MAX_PRICE = 0.99;
25
+ /**
26
+ * Default minimum price increment for Polymarket US markets. Observed live
27
+ * markets report `orderPriceMinTickSize: 0.001`, so we default to that.
28
+ * Per-market overrides (surfaced on `UnifiedMarket.tickSize`) should be
29
+ * preferred when available.
30
+ */
31
+ exports.POLYMARKET_US_TICK_SIZE = 0.001;
32
+ /**
33
+ * Number of decimal places used when serializing `Amount.value` strings.
34
+ * Matches the 0.001 tick-size precision observed on the live gateway
35
+ * (e.g. `"0.864"`, `"0.999"`).
36
+ */
37
+ exports.POLYMARKET_US_PRICE_DECIMALS = 3;
38
+ exports.POLYMARKET_US_CURRENCY = 'USD';
39
+ const LONG_INTENTS = new Set([
40
+ 'ORDER_INTENT_BUY_LONG',
41
+ 'ORDER_INTENT_SELL_LONG',
42
+ ]);
43
+ /**
44
+ * Round a price to a tick size using Math.round. Defaults to the
45
+ * Polymarket US tick size (0.001) but accepts a per-market override.
46
+ * Re-rounds to `POLYMARKET_US_PRICE_DECIMALS` afterwards to avoid
47
+ * floating-point drift.
48
+ *
49
+ * Note: this is a pure rounding helper - it does NOT validate bounds.
50
+ * Out-of-range inputs (e.g. 0.9999 -> 1.000) will pass through unchanged.
51
+ */
52
+ function roundToTickSize(price, tickSize = exports.POLYMARKET_US_TICK_SIZE) {
53
+ const ticks = Math.round(price / tickSize);
54
+ const rounded = ticks * tickSize;
55
+ const scale = Math.pow(10, exports.POLYMARKET_US_PRICE_DECIMALS);
56
+ return Math.round(rounded * scale) / scale;
57
+ }
58
+ /**
59
+ * Validate a price is finite and within [POLYMARKET_US_MIN_PRICE, POLYMARKET_US_MAX_PRICE].
60
+ *
61
+ * @throws RangeError if price is not finite or out of bounds.
62
+ */
63
+ function validatePriceBounds(price) {
64
+ if (!Number.isFinite(price)) {
65
+ throw new RangeError(`Polymarket US price must be a finite number, got: ${price}`);
66
+ }
67
+ if (price < exports.POLYMARKET_US_MIN_PRICE || price > exports.POLYMARKET_US_MAX_PRICE) {
68
+ throw new RangeError(`Polymarket US price ${price} is out of bounds [${exports.POLYMARKET_US_MIN_PRICE}, ${exports.POLYMARKET_US_MAX_PRICE}]`);
69
+ }
70
+ }
71
+ /**
72
+ * Convert a user-supplied price (in their natural side, 0.01-0.99) to the
73
+ * long-side price required by the Polymarket US API.
74
+ *
75
+ * For LONG intents (BUY_LONG, SELL_LONG) the user price is already the
76
+ * long-side price and is returned unchanged.
77
+ *
78
+ * For SHORT intents (BUY_SHORT, SELL_SHORT) the long-side price is
79
+ * `1 - userPrice`.
80
+ *
81
+ * Both the input and the resulting long-side output are validated against
82
+ * the Polymarket US price bounds.
83
+ *
84
+ * @throws RangeError if userPrice (or the derived long-side price) is not
85
+ * finite or outside [0.01, 0.99].
86
+ */
87
+ function toLongSidePrice(intent, userPrice) {
88
+ validatePriceBounds(userPrice);
89
+ if (LONG_INTENTS.has(intent)) {
90
+ return userPrice;
91
+ }
92
+ const longPrice = 1 - userPrice;
93
+ validatePriceBounds(longPrice);
94
+ return longPrice;
95
+ }
96
+ /**
97
+ * Inverse of `toLongSidePrice`. Given a long-side price returned by the API
98
+ * and the order's intent, recover the user-facing side price.
99
+ *
100
+ * For LONG intents the long-side price IS the user price.
101
+ * For SHORT intents the user price is `1 - longPrice`.
102
+ *
103
+ * @throws RangeError if longPrice (or the derived user price) is not finite
104
+ * or outside [0.01, 0.99].
105
+ */
106
+ function fromLongSidePrice(intent, longPrice) {
107
+ validatePriceBounds(longPrice);
108
+ if (LONG_INTENTS.has(intent)) {
109
+ return longPrice;
110
+ }
111
+ const userPrice = 1 - longPrice;
112
+ validatePriceBounds(userPrice);
113
+ return userPrice;
114
+ }
115
+ /**
116
+ * Build an SDK `Amount` object from a numeric USD price.
117
+ *
118
+ * The price is first rounded to tick size and then validated against the
119
+ * Polymarket US bounds. The resulting value is formatted as a fixed
120
+ * `POLYMARKET_US_PRICE_DECIMALS`-decimal string (e.g. "0.550"), matching
121
+ * the precision the live gateway reports.
122
+ *
123
+ * An optional `tickSize` override allows per-market tick sizes (as
124
+ * surfaced on `UnifiedMarket.tickSize`) to be honoured instead of the
125
+ * default `POLYMARKET_US_TICK_SIZE`.
126
+ *
127
+ * @throws RangeError if the rounded price is out of bounds.
128
+ */
129
+ function toAmount(price, tickSize = exports.POLYMARKET_US_TICK_SIZE) {
130
+ if (!Number.isFinite(price)) {
131
+ throw new RangeError(`Polymarket US price must be a finite number, got: ${price}`);
132
+ }
133
+ const rounded = roundToTickSize(price, tickSize);
134
+ validatePriceBounds(rounded);
135
+ return {
136
+ value: rounded.toFixed(exports.POLYMARKET_US_PRICE_DECIMALS),
137
+ currency: exports.POLYMARKET_US_CURRENCY,
138
+ };
139
+ }
140
+ /**
141
+ * Parse an SDK `Amount` back to a number. Returns 0 for undefined or empty
142
+ * values; otherwise delegates to `parseFloat`.
143
+ */
144
+ function fromAmount(amount) {
145
+ if (!amount || !amount.value) {
146
+ return 0;
147
+ }
148
+ return parseFloat(amount.value);
149
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const price_1 = require("./price");
4
+ describe('roundToTickSize', () => {
5
+ it('is idempotent for 0.123 at the default 0.001 tick', () => {
6
+ expect((0, price_1.roundToTickSize)(0.123)).toBe(0.123);
7
+ });
8
+ it('rounds 0.1234 down to 0.123 at default tick', () => {
9
+ expect((0, price_1.roundToTickSize)(0.1234)).toBe(0.123);
10
+ });
11
+ it('rounds 0.1235 up to 0.124 (Math.round half-away-from-zero)', () => {
12
+ expect((0, price_1.roundToTickSize)(0.1235)).toBe(0.124);
13
+ });
14
+ it('rounds 0.9999 up to 1.000 (out of bounds, validation is separate)', () => {
15
+ expect((0, price_1.roundToTickSize)(0.9999)).toBe(1.0);
16
+ });
17
+ it('is idempotent for 0.55', () => {
18
+ expect((0, price_1.roundToTickSize)(0.55)).toBe(0.55);
19
+ });
20
+ it('handles floating-point drift cleanly', () => {
21
+ // 0.1 + 0.2 = 0.30000000000000004
22
+ expect((0, price_1.roundToTickSize)(0.1 + 0.2)).toBe(0.3);
23
+ });
24
+ it('honours an explicit tick-size override (0.01)', () => {
25
+ expect((0, price_1.roundToTickSize)(0.123, 0.01)).toBe(0.12);
26
+ expect((0, price_1.roundToTickSize)(0.125, 0.01)).toBe(0.13);
27
+ });
28
+ });
29
+ describe('validatePriceBounds', () => {
30
+ it.each([
31
+ price_1.POLYMARKET_US_MIN_PRICE,
32
+ 0.5,
33
+ price_1.POLYMARKET_US_MAX_PRICE,
34
+ ])('accepts %s', (price) => {
35
+ expect(() => (0, price_1.validatePriceBounds)(price)).not.toThrow();
36
+ });
37
+ it.each([
38
+ 0.0,
39
+ 0.001,
40
+ 1.0,
41
+ 1.5,
42
+ Number.NaN,
43
+ Number.POSITIVE_INFINITY,
44
+ Number.NEGATIVE_INFINITY,
45
+ ])('throws RangeError for %s', (price) => {
46
+ expect(() => (0, price_1.validatePriceBounds)(price)).toThrow(RangeError);
47
+ });
48
+ });
49
+ describe('toLongSidePrice', () => {
50
+ it('returns user price unchanged for BUY_LONG', () => {
51
+ expect((0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', 0.55)).toBe(0.55);
52
+ });
53
+ it('returns user price unchanged for SELL_LONG', () => {
54
+ expect((0, price_1.toLongSidePrice)('ORDER_INTENT_SELL_LONG', 0.55)).toBe(0.55);
55
+ });
56
+ it('inverts user price for BUY_SHORT', () => {
57
+ expect((0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_SHORT', 0.4)).toBeCloseTo(0.6, 10);
58
+ });
59
+ it('inverts user price for SELL_SHORT', () => {
60
+ expect((0, price_1.toLongSidePrice)('ORDER_INTENT_SELL_SHORT', 0.4)).toBeCloseTo(0.6, 10);
61
+ });
62
+ it('throws on out-of-bounds input (LONG)', () => {
63
+ expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', 1.5)).toThrow(RangeError);
64
+ expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', 0)).toThrow(RangeError);
65
+ });
66
+ it('throws on out-of-bounds input (SHORT)', () => {
67
+ expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_SHORT', 1.5)).toThrow(RangeError);
68
+ expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_SELL_SHORT', 0)).toThrow(RangeError);
69
+ });
70
+ it('throws on NaN', () => {
71
+ expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', Number.NaN)).toThrow(RangeError);
72
+ });
73
+ });
74
+ describe('fromLongSidePrice (round trip)', () => {
75
+ const intents = [
76
+ 'ORDER_INTENT_BUY_LONG',
77
+ 'ORDER_INTENT_SELL_LONG',
78
+ 'ORDER_INTENT_BUY_SHORT',
79
+ 'ORDER_INTENT_SELL_SHORT',
80
+ ];
81
+ const prices = [0.01, 0.25, 0.5, 0.75, 0.99];
82
+ for (const intent of intents) {
83
+ for (const price of prices) {
84
+ it(`round-trips ${price} for ${intent}`, () => {
85
+ const longPrice = (0, price_1.toLongSidePrice)(intent, price);
86
+ const userPrice = (0, price_1.fromLongSidePrice)(intent, longPrice);
87
+ expect(userPrice).toBeCloseTo(price, 10);
88
+ });
89
+ }
90
+ }
91
+ it('throws on out-of-bounds input', () => {
92
+ expect(() => (0, price_1.fromLongSidePrice)('ORDER_INTENT_BUY_LONG', 1.5)).toThrow(RangeError);
93
+ });
94
+ });
95
+ describe('toAmount', () => {
96
+ it('formats 0.55 to { value: "0.550", currency: "USD" } at 3-decimal precision', () => {
97
+ expect((0, price_1.toAmount)(0.55)).toEqual({ value: '0.550', currency: 'USD' });
98
+ });
99
+ it('keeps 0.123 as "0.123" at the default 0.001 tick', () => {
100
+ expect((0, price_1.toAmount)(0.123)).toEqual({ value: '0.123', currency: 'USD' });
101
+ });
102
+ it('rounds 0.1234 down to "0.123"', () => {
103
+ expect((0, price_1.toAmount)(0.1234)).toEqual({ value: '0.123', currency: 'USD' });
104
+ });
105
+ it('honours an explicit 0.01 tick override', () => {
106
+ expect((0, price_1.toAmount)(0.123, 0.01)).toEqual({ value: '0.120', currency: 'USD' });
107
+ });
108
+ it('throws for 0.0 (below min)', () => {
109
+ expect(() => (0, price_1.toAmount)(0.0)).toThrow(RangeError);
110
+ });
111
+ it('throws for 1.0 (above max)', () => {
112
+ expect(() => (0, price_1.toAmount)(1.0)).toThrow(RangeError);
113
+ });
114
+ it('throws for NaN', () => {
115
+ expect(() => (0, price_1.toAmount)(Number.NaN)).toThrow(RangeError);
116
+ });
117
+ it('throws for Infinity', () => {
118
+ expect(() => (0, price_1.toAmount)(Number.POSITIVE_INFINITY)).toThrow(RangeError);
119
+ });
120
+ });
121
+ describe('fromAmount', () => {
122
+ it('parses { value: "0.550" } to 0.55', () => {
123
+ expect((0, price_1.fromAmount)({ value: '0.550', currency: 'USD' })).toBe(0.55);
124
+ });
125
+ it('returns 0 for undefined', () => {
126
+ expect((0, price_1.fromAmount)(undefined)).toBe(0);
127
+ });
128
+ it('returns 0 for empty string value', () => {
129
+ expect((0, price_1.fromAmount)({ value: '', currency: 'USD' })).toBe(0);
130
+ });
131
+ });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Polymarket US WebSocket wrapper.
3
+ *
4
+ * Provides CCXT Pro-style `watchOrderBook()` and `watchTrades()` on top of
5
+ * the `polymarket-us` SDK's `MarketsWebSocket` class. Each call awaits the
6
+ * next matching event for the requested market slug (the last segment of a
7
+ * PMXT outcomeId is stripped before subscribing).
8
+ *
9
+ * Notes:
10
+ * - The SDK's WebSocket factory requires `keyId` + `secretKey` even for
11
+ * the public `markets()` socket, so callers must instantiate the exchange
12
+ * with credentials before calling any watch method.
13
+ * - Order book and trade prices are reported as LONG-SIDE prices, matching
14
+ * the convention used by `normalizeOrderBook` and `normalizeOrder`.
15
+ */
16
+ import type { PolymarketUS as PolymarketUSClient } from 'polymarket-us';
17
+ import { OrderBook, Trade } from '../../types';
18
+ import { PolymarketUSNormalizer } from './normalizer';
19
+ export declare class PolymarketUSWebSocket {
20
+ private readonly client;
21
+ private readonly normalizer;
22
+ private socket;
23
+ private initializationPromise?;
24
+ private readonly bookSubscriptions;
25
+ private readonly tradeSubscriptions;
26
+ private readonly orderBookResolvers;
27
+ private readonly tradeResolvers;
28
+ constructor(client: PolymarketUSClient, normalizer: PolymarketUSNormalizer);
29
+ watchOrderBook(id: string): Promise<OrderBook>;
30
+ watchTrades(id: string): Promise<Trade[]>;
31
+ close(): Promise<void>;
32
+ private ensureInitialized;
33
+ private handleMarketData;
34
+ private handleTrade;
35
+ private handleError;
36
+ private handleClose;
37
+ private resolveOrderBook;
38
+ private rejectAllPending;
39
+ }
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ /**
3
+ * Polymarket US WebSocket wrapper.
4
+ *
5
+ * Provides CCXT Pro-style `watchOrderBook()` and `watchTrades()` on top of
6
+ * the `polymarket-us` SDK's `MarketsWebSocket` class. Each call awaits the
7
+ * next matching event for the requested market slug (the last segment of a
8
+ * PMXT outcomeId is stripped before subscribing).
9
+ *
10
+ * Notes:
11
+ * - The SDK's WebSocket factory requires `keyId` + `secretKey` even for
12
+ * the public `markets()` socket, so callers must instantiate the exchange
13
+ * with credentials before calling any watch method.
14
+ * - Order book and trade prices are reported as LONG-SIDE prices, matching
15
+ * the convention used by `normalizeOrderBook` and `normalizeOrder`.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.PolymarketUSWebSocket = void 0;
19
+ const price_1 = require("./price");
20
+ function sideFromOrderSide(raw) {
21
+ if (raw === 'ORDER_SIDE_BUY')
22
+ return 'buy';
23
+ if (raw === 'ORDER_SIDE_SELL')
24
+ return 'sell';
25
+ return 'unknown';
26
+ }
27
+ function parseTradeTimeMs(value) {
28
+ if (!value)
29
+ return Date.now();
30
+ const ms = new Date(value).getTime();
31
+ return Number.isFinite(ms) ? ms : Date.now();
32
+ }
33
+ /**
34
+ * Strip a trailing `:long` or `:short` from a PMXT identifier to recover
35
+ * the bare Polymarket US market slug.
36
+ */
37
+ function slugFromId(id) {
38
+ if (id.endsWith(':long'))
39
+ return id.slice(0, -':long'.length);
40
+ if (id.endsWith(':short'))
41
+ return id.slice(0, -':short'.length);
42
+ return id;
43
+ }
44
+ class PolymarketUSWebSocket {
45
+ client;
46
+ normalizer;
47
+ socket = null;
48
+ initializationPromise;
49
+ bookSubscriptions = new Set();
50
+ tradeSubscriptions = new Set();
51
+ orderBookResolvers = new Map();
52
+ tradeResolvers = new Map();
53
+ constructor(client, normalizer) {
54
+ this.client = client;
55
+ this.normalizer = normalizer;
56
+ }
57
+ async watchOrderBook(id) {
58
+ const slug = slugFromId(id);
59
+ await this.ensureInitialized();
60
+ if (!this.bookSubscriptions.has(slug)) {
61
+ this.bookSubscriptions.add(slug);
62
+ this.socket.subscribeMarketData(`book:${slug}`, [slug]);
63
+ }
64
+ return new Promise((resolve, reject) => {
65
+ const queue = this.orderBookResolvers.get(slug) ?? [];
66
+ queue.push({ resolve, reject });
67
+ this.orderBookResolvers.set(slug, queue);
68
+ });
69
+ }
70
+ async watchTrades(id) {
71
+ const slug = slugFromId(id);
72
+ await this.ensureInitialized();
73
+ if (!this.tradeSubscriptions.has(slug)) {
74
+ this.tradeSubscriptions.add(slug);
75
+ this.socket.subscribeTrades(`trade:${slug}`, [slug]);
76
+ }
77
+ return new Promise((resolve, reject) => {
78
+ const queue = this.tradeResolvers.get(slug) ?? [];
79
+ queue.push({ resolve, reject });
80
+ this.tradeResolvers.set(slug, queue);
81
+ });
82
+ }
83
+ async close() {
84
+ if (this.socket) {
85
+ try {
86
+ this.socket.close();
87
+ }
88
+ catch {
89
+ // Ignore close errors.
90
+ }
91
+ this.socket = null;
92
+ }
93
+ this.bookSubscriptions.clear();
94
+ this.tradeSubscriptions.clear();
95
+ this.rejectAllPending(new Error('PolymarketUS WebSocket closed'));
96
+ this.initializationPromise = undefined;
97
+ }
98
+ async ensureInitialized() {
99
+ if (this.initializationPromise)
100
+ return this.initializationPromise;
101
+ this.initializationPromise = (async () => {
102
+ const socket = this.client.ws.markets();
103
+ socket.on('marketData', (msg) => this.handleMarketData(msg));
104
+ socket.on('trade', (msg) => this.handleTrade(msg));
105
+ socket.on('error', (err) => this.handleError(err));
106
+ socket.on('close', () => this.handleClose());
107
+ await socket.connect();
108
+ this.socket = socket;
109
+ })();
110
+ try {
111
+ await this.initializationPromise;
112
+ }
113
+ catch (err) {
114
+ this.initializationPromise = undefined;
115
+ throw err;
116
+ }
117
+ }
118
+ handleMarketData(msg) {
119
+ const payload = msg.marketData;
120
+ if (!payload)
121
+ return;
122
+ const slug = payload.marketSlug;
123
+ // The WS MarketData payload shape is structurally compatible with
124
+ // the REST MarketBook shape used by normalizeOrderBook.
125
+ const book = this.normalizer.normalizeOrderBook(payload, slug);
126
+ this.resolveOrderBook(slug, book);
127
+ }
128
+ handleTrade(msg) {
129
+ const payload = msg.trade;
130
+ if (!payload)
131
+ return;
132
+ const slug = payload.marketSlug;
133
+ const timestamp = parseTradeTimeMs(payload.tradeTime);
134
+ const priceValue = payload.price?.value ?? '0';
135
+ const trade = {
136
+ id: `${slug}-${timestamp}-${priceValue}`,
137
+ timestamp,
138
+ price: (0, price_1.fromAmount)(payload.price),
139
+ amount: (0, price_1.fromAmount)(payload.quantity),
140
+ side: sideFromOrderSide(payload.taker?.side),
141
+ };
142
+ const queue = this.tradeResolvers.get(slug);
143
+ if (queue && queue.length > 0) {
144
+ for (const { resolve } of queue)
145
+ resolve([trade]);
146
+ this.tradeResolvers.set(slug, []);
147
+ }
148
+ }
149
+ handleError(err) {
150
+ // Surface the error to any waiting callers rather than losing it.
151
+ this.rejectAllPending(err);
152
+ }
153
+ handleClose() {
154
+ this.rejectAllPending(new Error('PolymarketUS WebSocket closed'));
155
+ this.socket = null;
156
+ this.initializationPromise = undefined;
157
+ this.bookSubscriptions.clear();
158
+ this.tradeSubscriptions.clear();
159
+ }
160
+ resolveOrderBook(slug, book) {
161
+ const queue = this.orderBookResolvers.get(slug);
162
+ if (queue && queue.length > 0) {
163
+ for (const { resolve } of queue)
164
+ resolve(book);
165
+ this.orderBookResolvers.set(slug, []);
166
+ }
167
+ }
168
+ rejectAllPending(err) {
169
+ for (const queue of this.orderBookResolvers.values()) {
170
+ for (const { reject } of queue)
171
+ reject(err);
172
+ }
173
+ this.orderBookResolvers.clear();
174
+ for (const queue of this.tradeResolvers.values()) {
175
+ for (const { reject } of queue)
176
+ reject(err);
177
+ }
178
+ this.tradeResolvers.clear();
179
+ }
180
+ }
181
+ exports.PolymarketUSWebSocket = PolymarketUSWebSocket;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Unit tests for PolymarketUSWebSocket.
3
+ *
4
+ * The polymarket-us SDK is fully stubbed here so the tests run without
5
+ * touching the network. A fake MarketsWebSocket captures subscriptions
6
+ * and lets each test fire synthetic messages through a listener map.
7
+ */
8
+ export {};