pmxt-core 2.37.14 → 2.39.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 (64) hide show
  1. package/dist/BaseExchange.d.ts +14 -14
  2. package/dist/BaseExchange.js +19 -19
  3. package/dist/exchanges/baozi/index.d.ts +2 -2
  4. package/dist/exchanges/baozi/index.js +5 -5
  5. package/dist/exchanges/hyperliquid/auth.d.ts +30 -0
  6. package/dist/exchanges/hyperliquid/auth.js +145 -0
  7. package/dist/exchanges/hyperliquid/config.d.ts +17 -0
  8. package/dist/exchanges/hyperliquid/config.js +28 -0
  9. package/dist/exchanges/hyperliquid/errors.d.ts +18 -0
  10. package/dist/exchanges/hyperliquid/errors.js +61 -0
  11. package/dist/exchanges/hyperliquid/fetcher.d.ts +140 -0
  12. package/dist/exchanges/hyperliquid/fetcher.js +137 -0
  13. package/dist/exchanges/hyperliquid/index.d.ts +31 -0
  14. package/dist/exchanges/hyperliquid/index.js +219 -0
  15. package/dist/exchanges/hyperliquid/normalizer.d.ts +18 -0
  16. package/dist/exchanges/hyperliquid/normalizer.js +339 -0
  17. package/dist/exchanges/hyperliquid/utils.d.ts +41 -0
  18. package/dist/exchanges/hyperliquid/utils.js +76 -0
  19. package/dist/exchanges/kalshi/api.d.ts +1 -1
  20. package/dist/exchanges/kalshi/api.js +1 -1
  21. package/dist/exchanges/kalshi/index.d.ts +6 -6
  22. package/dist/exchanges/kalshi/index.js +14 -14
  23. package/dist/exchanges/limitless/api.d.ts +1 -1
  24. package/dist/exchanges/limitless/api.js +1 -1
  25. package/dist/exchanges/limitless/index.d.ts +5 -5
  26. package/dist/exchanges/limitless/index.js +15 -12
  27. package/dist/exchanges/myriad/api.d.ts +1 -1
  28. package/dist/exchanges/myriad/api.js +1 -1
  29. package/dist/exchanges/myriad/index.d.ts +5 -5
  30. package/dist/exchanges/myriad/index.js +14 -14
  31. package/dist/exchanges/myriad/websocket.d.ts +2 -2
  32. package/dist/exchanges/myriad/websocket.js +12 -12
  33. package/dist/exchanges/opinion/api.d.ts +1 -1
  34. package/dist/exchanges/opinion/api.js +1 -1
  35. package/dist/exchanges/opinion/index.d.ts +4 -4
  36. package/dist/exchanges/opinion/index.js +9 -9
  37. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  38. package/dist/exchanges/polymarket/api-clob.js +1 -1
  39. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  40. package/dist/exchanges/polymarket/api-data.js +1 -1
  41. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  42. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  43. package/dist/exchanges/polymarket/index.d.ts +6 -6
  44. package/dist/exchanges/polymarket/index.js +19 -19
  45. package/dist/exchanges/polymarket/websocket.d.ts +3 -3
  46. package/dist/exchanges/polymarket/websocket.js +21 -21
  47. package/dist/exchanges/polymarket_us/index.d.ts +3 -3
  48. package/dist/exchanges/polymarket_us/index.js +7 -7
  49. package/dist/exchanges/polymarket_us/websocket.d.ts +2 -2
  50. package/dist/exchanges/polymarket_us/websocket.js +6 -6
  51. package/dist/exchanges/probable/api.d.ts +1 -1
  52. package/dist/exchanges/probable/api.js +1 -1
  53. package/dist/exchanges/probable/index.d.ts +4 -4
  54. package/dist/exchanges/probable/index.js +9 -9
  55. package/dist/exchanges/smarkets/index.d.ts +2 -2
  56. package/dist/exchanges/smarkets/index.js +5 -5
  57. package/dist/index.d.ts +4 -0
  58. package/dist/index.js +5 -1
  59. package/dist/router/Router.d.ts +1 -1
  60. package/dist/router/Router.js +3 -3
  61. package/dist/server/exchange-factory.js +6 -0
  62. package/dist/server/method-verbs.json +7 -7
  63. package/dist/server/openapi.yaml +18 -3
  64. package/package.json +4 -3
@@ -449,7 +449,7 @@ export declare abstract class PredictionMarketExchange {
449
449
  /**
450
450
  * Fetch historical OHLCV (candlestick) price data for a specific market outcome.
451
451
  *
452
- * @param id - The Outcome ID (outcomeId). Use outcome.outcomeId, NOT market.marketId
452
+ * @param outcomeId - The Outcome ID (outcomeId). Use outcome.outcomeId, NOT market.marketId
453
453
  * @param params - OHLCV parameters including resolution (required)
454
454
  * @returns Array of price candles
455
455
  *
@@ -457,29 +457,29 @@ export declare abstract class PredictionMarketExchange {
457
457
  * @notes Polymarket: outcomeId is the CLOB Token ID. Kalshi: outcomeId is the Market Ticker.
458
458
  * @notes Common resolutions: '1m' | '5m' | '15m' | '1h' | '6h' | '1d'. Arbitrary intervals (e.g. '30s', '120s', '3h') accepted by venues that support them.
459
459
  */
460
- fetchOHLCV(id: string, params: OHLCVParams): Promise<PriceCandle[]>;
460
+ fetchOHLCV(outcomeId: string, params: OHLCVParams): Promise<PriceCandle[]>;
461
461
  /**
462
462
  * Fetch the current order book (bids/asks) for a specific outcome.
463
463
  * Essential for calculating spread, depth, and execution prices.
464
464
  *
465
- * @param id - The Outcome ID (outcomeId) or market slug
465
+ * @param outcomeId - The Outcome ID (outcomeId) or market slug
466
466
  * @param side - Optional 'yes' or 'no' to explicitly indicate the
467
467
  * outcome side. Required for exchanges where the API returns a
468
468
  * single orderbook per market (e.g. Limitless) and the caller
469
469
  * passes a slug instead of a token ID.
470
470
  * @returns Current order book with bids and asks
471
471
  */
472
- fetchOrderBook(id: string, side?: 'yes' | 'no'): Promise<OrderBook>;
472
+ fetchOrderBook(outcomeId: string, side?: 'yes' | 'no'): Promise<OrderBook>;
473
473
  /**
474
474
  * Fetch raw trade history for a specific outcome.
475
475
  *
476
- * @param id - The Outcome ID (outcomeId)
476
+ * @param outcomeId - The Outcome ID (outcomeId)
477
477
  * @param params - Trade filter parameters
478
478
  * @returns Array of recent trades
479
479
  *
480
480
  * @notes Polymarket requires an API key for trade history. Use fetchOHLCV for public historical data.
481
481
  */
482
- fetchTrades(id: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]>;
482
+ fetchTrades(outcomeId: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]>;
483
483
  /**
484
484
  * Place a new order on the exchange.
485
485
  *
@@ -582,39 +582,39 @@ export declare abstract class PredictionMarketExchange {
582
582
  * Watch order book updates in real-time via WebSocket.
583
583
  * Returns a promise that resolves with the next order book update. Call repeatedly in a loop to stream updates (CCXT Pro pattern).
584
584
  *
585
- * @param id - The Outcome ID to watch
585
+ * @param outcomeId - The Outcome ID to watch
586
586
  * @param limit - Optional limit for orderbook depth
587
587
  * @returns Promise that resolves with the current orderbook state
588
588
  */
589
- watchOrderBook(id: string, limit?: number): Promise<OrderBook>;
589
+ watchOrderBook(outcomeId: string, limit?: number): Promise<OrderBook>;
590
590
  /**
591
591
  * Watch multiple order books simultaneously via WebSocket.
592
592
  * Returns a promise that resolves with a record of order book snapshots keyed by ID.
593
593
  * Exchanges with native batch support (e.g. Kalshi) send a single subscribe message
594
594
  * for all tickers; others fall back to individual watchOrderBook calls.
595
595
  *
596
- * @param ids - Array of Outcome IDs to watch
596
+ * @param outcomeIds - Array of Outcome IDs to watch
597
597
  * @param limit - Optional limit for orderbook depth
598
598
  * @returns Promise that resolves with order books keyed by ID
599
599
  */
600
- watchOrderBooks(ids: string[], limit?: number): Promise<Record<string, OrderBook>>;
600
+ watchOrderBooks(outcomeIds: string[], limit?: number): Promise<Record<string, OrderBook>>;
601
601
  /**
602
602
  * Unsubscribe from a previously watched order book stream.
603
603
  *
604
- * @param id - The Outcome ID to stop watching
604
+ * @param outcomeId - The Outcome ID to stop watching
605
605
  */
606
- unwatchOrderBook(id: string): Promise<void>;
606
+ unwatchOrderBook(outcomeId: string): Promise<void>;
607
607
  /**
608
608
  * Watch trade executions in real-time via WebSocket.
609
609
  * Returns a promise that resolves with the next trade(s). Call repeatedly in a loop to stream updates (CCXT Pro pattern).
610
610
  *
611
- * @param id - The Outcome ID to watch
611
+ * @param outcomeId - The Outcome ID to watch
612
612
  * @param address - Public wallet address
613
613
  * @param since - Optional timestamp to filter trades from
614
614
  * @param limit - Optional limit for number of trades
615
615
  * @returns Promise that resolves with recent trades
616
616
  */
617
- watchTrades(id: string, address?: string, since?: number, limit?: number): Promise<Trade[]>;
617
+ watchTrades(outcomeId: string, address?: string, since?: number, limit?: number): Promise<Trade[]>;
618
618
  /**
619
619
  * Stream activity for a public wallet address
620
620
  * Returns a promise that resolves with the next activity snapshot whenever a change
@@ -391,7 +391,7 @@ class PredictionMarketExchange {
391
391
  /**
392
392
  * Fetch historical OHLCV (candlestick) price data for a specific market outcome.
393
393
  *
394
- * @param id - The Outcome ID (outcomeId). Use outcome.outcomeId, NOT market.marketId
394
+ * @param outcomeId - The Outcome ID (outcomeId). Use outcome.outcomeId, NOT market.marketId
395
395
  * @param params - OHLCV parameters including resolution (required)
396
396
  * @returns Array of price candles
397
397
  *
@@ -399,33 +399,33 @@ class PredictionMarketExchange {
399
399
  * @notes Polymarket: outcomeId is the CLOB Token ID. Kalshi: outcomeId is the Market Ticker.
400
400
  * @notes Common resolutions: '1m' | '5m' | '15m' | '1h' | '6h' | '1d'. Arbitrary intervals (e.g. '30s', '120s', '3h') accepted by venues that support them.
401
401
  */
402
- async fetchOHLCV(id, params) {
402
+ async fetchOHLCV(outcomeId, params) {
403
403
  throw new Error("Method fetchOHLCV not implemented.");
404
404
  }
405
405
  /**
406
406
  * Fetch the current order book (bids/asks) for a specific outcome.
407
407
  * Essential for calculating spread, depth, and execution prices.
408
408
  *
409
- * @param id - The Outcome ID (outcomeId) or market slug
409
+ * @param outcomeId - The Outcome ID (outcomeId) or market slug
410
410
  * @param side - Optional 'yes' or 'no' to explicitly indicate the
411
411
  * outcome side. Required for exchanges where the API returns a
412
412
  * single orderbook per market (e.g. Limitless) and the caller
413
413
  * passes a slug instead of a token ID.
414
414
  * @returns Current order book with bids and asks
415
415
  */
416
- async fetchOrderBook(id, side) {
416
+ async fetchOrderBook(outcomeId, side) {
417
417
  throw new Error("Method fetchOrderBook not implemented.");
418
418
  }
419
419
  /**
420
420
  * Fetch raw trade history for a specific outcome.
421
421
  *
422
- * @param id - The Outcome ID (outcomeId)
422
+ * @param outcomeId - The Outcome ID (outcomeId)
423
423
  * @param params - Trade filter parameters
424
424
  * @returns Array of recent trades
425
425
  *
426
426
  * @notes Polymarket requires an API key for trade history. Use fetchOHLCV for public historical data.
427
427
  */
428
- async fetchTrades(id, params) {
428
+ async fetchTrades(outcomeId, params) {
429
429
  // Deprecation warning for resolution parameter
430
430
  if ('resolution' in params && params.resolution !== undefined) {
431
431
  console.warn('[pmxt] Warning: The "resolution" parameter is deprecated for fetchTrades() and will be ignored. ' +
@@ -761,11 +761,11 @@ class PredictionMarketExchange {
761
761
  * Watch order book updates in real-time via WebSocket.
762
762
  * Returns a promise that resolves with the next order book update. Call repeatedly in a loop to stream updates (CCXT Pro pattern).
763
763
  *
764
- * @param id - The Outcome ID to watch
764
+ * @param outcomeId - The Outcome ID to watch
765
765
  * @param limit - Optional limit for orderbook depth
766
766
  * @returns Promise that resolves with the current orderbook state
767
767
  */
768
- async watchOrderBook(id, limit) {
768
+ async watchOrderBook(outcomeId, limit) {
769
769
  throw new Error(`watchOrderBook() is not supported by ${this.name}`);
770
770
  }
771
771
  /**
@@ -774,30 +774,30 @@ class PredictionMarketExchange {
774
774
  * Exchanges with native batch support (e.g. Kalshi) send a single subscribe message
775
775
  * for all tickers; others fall back to individual watchOrderBook calls.
776
776
  *
777
- * @param ids - Array of Outcome IDs to watch
777
+ * @param outcomeIds - Array of Outcome IDs to watch
778
778
  * @param limit - Optional limit for orderbook depth
779
779
  * @returns Promise that resolves with order books keyed by ID
780
780
  */
781
- async watchOrderBooks(ids, limit) {
781
+ async watchOrderBooks(outcomeIds, limit) {
782
782
  // Default implementation: subscribe to each ID individually.
783
783
  // Exchanges with native batch support (e.g. Kalshi) override this
784
784
  // to send a single subscribe message for all tickers.
785
- const entries = await Promise.all(ids.map(async (id) => {
786
- const book = await this.watchOrderBook(id, limit);
787
- return [id, book];
785
+ const entries = await Promise.all(outcomeIds.map(async (oid) => {
786
+ const book = await this.watchOrderBook(oid, limit);
787
+ return [oid, book];
788
788
  }));
789
789
  const result = {};
790
- for (const [id, book] of entries) {
791
- result[id] = book;
790
+ for (const [oid, book] of entries) {
791
+ result[oid] = book;
792
792
  }
793
793
  return result;
794
794
  }
795
795
  /**
796
796
  * Unsubscribe from a previously watched order book stream.
797
797
  *
798
- * @param id - The Outcome ID to stop watching
798
+ * @param outcomeId - The Outcome ID to stop watching
799
799
  */
800
- async unwatchOrderBook(id) {
800
+ async unwatchOrderBook(outcomeId) {
801
801
  throw new Error(`unwatchOrderBook() is not supported by ${this.name}`);
802
802
  }
803
803
  // ----------------------------------------------------------------------------
@@ -807,13 +807,13 @@ class PredictionMarketExchange {
807
807
  * Watch trade executions in real-time via WebSocket.
808
808
  * Returns a promise that resolves with the next trade(s). Call repeatedly in a loop to stream updates (CCXT Pro pattern).
809
809
  *
810
- * @param id - The Outcome ID to watch
810
+ * @param outcomeId - The Outcome ID to watch
811
811
  * @param address - Public wallet address
812
812
  * @param since - Optional timestamp to filter trades from
813
813
  * @param limit - Optional limit for number of trades
814
814
  * @returns Promise that resolves with recent trades
815
815
  */
816
- async watchTrades(id, address, since, limit) {
816
+ async watchTrades(outcomeId, address, since, limit) {
817
817
  throw new Error(`watchTrades() is not supported by ${this.name}`);
818
818
  }
819
819
  /**
@@ -23,7 +23,7 @@ export declare class BaoziExchange extends PredictionMarketExchange {
23
23
  protected fetchMarketsImpl(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
24
24
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
25
25
  fetchOHLCV(): Promise<PriceCandle[]>;
26
- fetchOrderBook(id: string): Promise<OrderBook>;
26
+ fetchOrderBook(outcomeId: string): Promise<OrderBook>;
27
27
  fetchTrades(): Promise<Trade[]>;
28
28
  fetchBalance(): Promise<Balance[]>;
29
29
  fetchPositions(): Promise<Position[]>;
@@ -31,7 +31,7 @@ export declare class BaoziExchange extends PredictionMarketExchange {
31
31
  cancelOrder(): Promise<Order>;
32
32
  fetchOrder(orderId: string): Promise<Order>;
33
33
  fetchOpenOrders(): Promise<Order[]>;
34
- watchOrderBook(id: string): Promise<OrderBook>;
34
+ watchOrderBook(outcomeId: string): Promise<OrderBook>;
35
35
  watchTrades(): Promise<Trade[]>;
36
36
  close(): Promise<void>;
37
37
  private ensureAuth;
@@ -71,9 +71,9 @@ class BaoziExchange extends BaseExchange_1.PredictionMarketExchange {
71
71
  // Baozi has no historical price/trade API without a custom indexer
72
72
  return [];
73
73
  }
74
- async fetchOrderBook(id) {
75
- const rawMarket = await this.fetcher.fetchRawOrderBook(id);
76
- return this.normalizer.normalizeOrderBook(rawMarket, id);
74
+ async fetchOrderBook(outcomeId) {
75
+ const rawMarket = await this.fetcher.fetchRawOrderBook(outcomeId);
76
+ return this.normalizer.normalizeOrderBook(rawMarket, outcomeId);
77
77
  }
78
78
  async fetchTrades() {
79
79
  // Baozi has no trade history API without a custom indexer
@@ -313,11 +313,11 @@ class BaoziExchange extends BaseExchange_1.PredictionMarketExchange {
313
313
  // -----------------------------------------------------------------------
314
314
  // WebSocket
315
315
  // -----------------------------------------------------------------------
316
- async watchOrderBook(id) {
316
+ async watchOrderBook(outcomeId) {
317
317
  if (!this.ws) {
318
318
  this.ws = new websocket_1.BaoziWebSocket();
319
319
  }
320
- return this.ws.watchOrderBook(this.connection, id);
320
+ return this.ws.watchOrderBook(this.connection, outcomeId);
321
321
  }
322
322
  async watchTrades() {
323
323
  throw new errors_1.ExchangeNotAvailable('Trade streaming is not available for Baozi', 'Baozi');
@@ -0,0 +1,30 @@
1
+ import { ExchangeCredentials } from '../../BaseExchange';
2
+ export interface HyperliquidSignature {
3
+ r: string;
4
+ s: string;
5
+ v: number;
6
+ }
7
+ export declare function floatToWire(x: number): string;
8
+ export declare class HyperliquidAuth {
9
+ private readonly wallet;
10
+ private readonly isMainnet;
11
+ constructor(credentials: ExchangeCredentials, testnet: boolean);
12
+ getAddress(): string;
13
+ /**
14
+ * Sign an L1 action using the phantom agent EIP-712 scheme.
15
+ *
16
+ * Flow:
17
+ * 1. msgpack-encode the action
18
+ * 2. Append nonce (8 bytes BE) + vault marker
19
+ * 3. keccak256 -> connectionId
20
+ * 4. EIP-712 sign {source, connectionId} with domain "Exchange"
21
+ */
22
+ signL1Action(action: Record<string, unknown>, vaultAddress?: string | null, nonce?: number): Promise<{
23
+ signature: HyperliquidSignature;
24
+ nonce: number;
25
+ }>;
26
+ /**
27
+ * Build and sign a complete exchange request body.
28
+ */
29
+ signExchangeRequest(action: Record<string, unknown>, vaultAddress?: string | null): Promise<Record<string, unknown>>;
30
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HyperliquidAuth = void 0;
4
+ exports.floatToWire = floatToWire;
5
+ const ethers_1 = require("ethers");
6
+ const msgpackr_1 = require("msgpackr");
7
+ const errors_1 = require("../../errors");
8
+ const config_1 = require("./config");
9
+ // Standard msgpack encoder — variableMapSize ensures fixmap/fixarray encoding
10
+ // which matches the Python/Rust msgpack libraries that Hyperliquid's server uses.
11
+ const packr = new msgpackr_1.Packr({ useRecords: false, variableMapSize: true });
12
+ // ----------------------------------------------------------------------------
13
+ // EIP-712 domain and types for Hyperliquid L1 action signing
14
+ // ----------------------------------------------------------------------------
15
+ const EIP712_DOMAIN = {
16
+ name: 'Exchange',
17
+ version: '1',
18
+ chainId: config_1.EXCHANGE_CHAIN_ID,
19
+ verifyingContract: '0x0000000000000000000000000000000000000000',
20
+ };
21
+ const AGENT_TYPES = {
22
+ Agent: [
23
+ { name: 'source', type: 'string' },
24
+ { name: 'connectionId', type: 'bytes32' },
25
+ ],
26
+ };
27
+ // ----------------------------------------------------------------------------
28
+ // msgpack helpers -- Hyperliquid requires int64 encoding for large integers
29
+ // ----------------------------------------------------------------------------
30
+ function convertLargeInts(obj) {
31
+ if (typeof obj === 'number' && Number.isInteger(obj) &&
32
+ (obj >= 0x100000000 || obj < -0x80000000)) {
33
+ return BigInt(obj);
34
+ }
35
+ if (Array.isArray(obj)) {
36
+ return obj.map(convertLargeInts);
37
+ }
38
+ if (typeof obj === 'object' && obj !== null) {
39
+ const result = {};
40
+ for (const [key, val] of Object.entries(obj)) {
41
+ if (val !== undefined) {
42
+ result[key] = convertLargeInts(val);
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ return obj;
48
+ }
49
+ // ----------------------------------------------------------------------------
50
+ // Action hash -- constructs the connectionId for the phantom agent
51
+ // ----------------------------------------------------------------------------
52
+ function computeActionHash(action, vaultAddress, nonce) {
53
+ // 1. msgpack-encode the action (large ints as int64)
54
+ const actionBytes = packr.pack(convertLargeInts(action));
55
+ // 2. nonce as 8 bytes big-endian
56
+ const nonceBytes = Buffer.alloc(8);
57
+ nonceBytes.writeBigUInt64BE(BigInt(nonce));
58
+ // 3. vault address marker
59
+ const parts = [Buffer.from(actionBytes), nonceBytes];
60
+ if (vaultAddress) {
61
+ parts.push(Buffer.from([0x01]));
62
+ parts.push(Buffer.from(vaultAddress.replace('0x', ''), 'hex'));
63
+ }
64
+ else {
65
+ parts.push(Buffer.from([0x00]));
66
+ }
67
+ // 4. keccak256 of concatenated bytes
68
+ return ethers_1.utils.keccak256(Buffer.concat(parts));
69
+ }
70
+ // ----------------------------------------------------------------------------
71
+ // Price/size formatting -- must match Hyperliquid's wire format
72
+ // ----------------------------------------------------------------------------
73
+ function floatToWire(x) {
74
+ const rounded = x.toFixed(8);
75
+ if (Math.abs(parseFloat(rounded) - x) >= 1e-12) {
76
+ throw new Error(`floatToWire causes rounding: ${x}`);
77
+ }
78
+ return parseFloat(rounded).toString();
79
+ }
80
+ // ----------------------------------------------------------------------------
81
+ // Auth class
82
+ // ----------------------------------------------------------------------------
83
+ class HyperliquidAuth {
84
+ wallet;
85
+ isMainnet;
86
+ constructor(credentials, testnet) {
87
+ if (!credentials.privateKey) {
88
+ throw new errors_1.AuthenticationError('Hyperliquid trading requires a privateKey for EIP-712 signing.', 'Hyperliquid');
89
+ }
90
+ let privateKey = credentials.privateKey;
91
+ if (privateKey.includes('\\n')) {
92
+ privateKey = privateKey.replace(/\\n/g, '\n');
93
+ }
94
+ const stripped = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
95
+ if (!/^[0-9a-fA-F]{64}$/.test(stripped)) {
96
+ throw new errors_1.AuthenticationError('Invalid private key format. Hyperliquid requires a 32-byte hex EVM private key (e.g. 0xabc123...).', 'Hyperliquid');
97
+ }
98
+ this.wallet = new ethers_1.Wallet(privateKey);
99
+ this.isMainnet = !testnet;
100
+ }
101
+ getAddress() {
102
+ return this.wallet.address;
103
+ }
104
+ /**
105
+ * Sign an L1 action using the phantom agent EIP-712 scheme.
106
+ *
107
+ * Flow:
108
+ * 1. msgpack-encode the action
109
+ * 2. Append nonce (8 bytes BE) + vault marker
110
+ * 3. keccak256 -> connectionId
111
+ * 4. EIP-712 sign {source, connectionId} with domain "Exchange"
112
+ */
113
+ async signL1Action(action, vaultAddress = null, nonce = Date.now()) {
114
+ const connectionId = computeActionHash(action, vaultAddress, nonce);
115
+ const message = {
116
+ source: this.isMainnet ? 'a' : 'b',
117
+ connectionId,
118
+ };
119
+ // ethers v5 uses _signTypedData (underscore prefix)
120
+ const sig = await this.wallet._signTypedData(EIP712_DOMAIN, AGENT_TYPES, message);
121
+ const split = ethers_1.utils.splitSignature(sig);
122
+ return {
123
+ signature: {
124
+ r: split.r,
125
+ s: split.s,
126
+ v: split.v,
127
+ },
128
+ nonce,
129
+ };
130
+ }
131
+ /**
132
+ * Build and sign a complete exchange request body.
133
+ */
134
+ async signExchangeRequest(action, vaultAddress = null) {
135
+ const nonce = Date.now();
136
+ const { signature } = await this.signL1Action(action, vaultAddress, nonce);
137
+ return {
138
+ action,
139
+ nonce,
140
+ signature,
141
+ vaultAddress,
142
+ };
143
+ }
144
+ }
145
+ exports.HyperliquidAuth = HyperliquidAuth;
@@ -0,0 +1,17 @@
1
+ export declare const DEFAULT_HYPERLIQUID_BASE_URL = "https://api.hyperliquid.xyz";
2
+ export declare const HYPERLIQUID_TESTNET_BASE_URL = "https://api.hyperliquid-testnet.xyz";
3
+ export declare const HYPERLIQUID_WS_URL = "wss://api.hyperliquid.xyz/ws";
4
+ export declare const HYPERLIQUID_TESTNET_WS_URL = "wss://api.hyperliquid-testnet.xyz/ws";
5
+ export declare const OUTCOME_ASSET_BASE = 100000000;
6
+ export declare const OUTCOME_MULTIPLIER = 10;
7
+ export declare const SIDE_YES = 0;
8
+ export declare const SIDE_NO = 1;
9
+ export declare const EXCHANGE_DOMAIN = "Exchange";
10
+ export declare const EXCHANGE_CHAIN_ID = 1337;
11
+ export declare const MIN_ORDER_VALUE = 10;
12
+ export interface HyperliquidApiConfig {
13
+ baseUrl: string;
14
+ wsUrl: string;
15
+ testnet: boolean;
16
+ }
17
+ export declare function getHyperliquidConfig(baseUrlOverride?: string, testnet?: boolean): HyperliquidApiConfig;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIN_ORDER_VALUE = exports.EXCHANGE_CHAIN_ID = exports.EXCHANGE_DOMAIN = exports.SIDE_NO = exports.SIDE_YES = exports.OUTCOME_MULTIPLIER = exports.OUTCOME_ASSET_BASE = exports.HYPERLIQUID_TESTNET_WS_URL = exports.HYPERLIQUID_WS_URL = exports.HYPERLIQUID_TESTNET_BASE_URL = exports.DEFAULT_HYPERLIQUID_BASE_URL = void 0;
4
+ exports.getHyperliquidConfig = getHyperliquidConfig;
5
+ exports.DEFAULT_HYPERLIQUID_BASE_URL = 'https://api.hyperliquid.xyz';
6
+ exports.HYPERLIQUID_TESTNET_BASE_URL = 'https://api.hyperliquid-testnet.xyz';
7
+ exports.HYPERLIQUID_WS_URL = 'wss://api.hyperliquid.xyz/ws';
8
+ exports.HYPERLIQUID_TESTNET_WS_URL = 'wss://api.hyperliquid-testnet.xyz/ws';
9
+ // HIP-4 Outcome Markets asset ID encoding
10
+ // Asset ID = 100_000_000 + (10 * outcome_id) + side
11
+ // side: 0 = Yes, 1 = No
12
+ exports.OUTCOME_ASSET_BASE = 100_000_000;
13
+ exports.OUTCOME_MULTIPLIER = 10;
14
+ exports.SIDE_YES = 0;
15
+ exports.SIDE_NO = 1;
16
+ // EIP-712 signing constants
17
+ exports.EXCHANGE_DOMAIN = 'Exchange';
18
+ exports.EXCHANGE_CHAIN_ID = 1337;
19
+ // Minimum order value in USDH
20
+ exports.MIN_ORDER_VALUE = 10;
21
+ function getHyperliquidConfig(baseUrlOverride, testnet) {
22
+ const isTestnet = testnet ?? false;
23
+ return {
24
+ baseUrl: baseUrlOverride ?? (isTestnet ? exports.HYPERLIQUID_TESTNET_BASE_URL : exports.DEFAULT_HYPERLIQUID_BASE_URL),
25
+ wsUrl: isTestnet ? exports.HYPERLIQUID_TESTNET_WS_URL : exports.HYPERLIQUID_WS_URL,
26
+ testnet: isTestnet,
27
+ };
28
+ }
@@ -0,0 +1,18 @@
1
+ import { ErrorMapper } from '../../utils/error-mapper';
2
+ import { BadRequest } from '../../errors';
3
+ /**
4
+ * Maps Hyperliquid API errors to PMXT unified error classes.
5
+ *
6
+ * Hyperliquid returns errors as plain strings or JSON objects in
7
+ * the response body. Common patterns:
8
+ * - "User has no account" -> AuthenticationError
9
+ * - "Insufficient margin" -> InsufficientFunds
10
+ * - "Invalid order" -> InvalidOrder
11
+ */
12
+ export declare class HyperliquidErrorMapper extends ErrorMapper {
13
+ constructor();
14
+ protected extractErrorMessage(error: any): string;
15
+ protected mapBadRequestError(message: string, data: any): BadRequest;
16
+ mapError(error: any): ReturnType<ErrorMapper['mapError']>;
17
+ }
18
+ export declare const hyperliquidErrorMapper: HyperliquidErrorMapper;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hyperliquidErrorMapper = exports.HyperliquidErrorMapper = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const error_mapper_1 = require("../../utils/error-mapper");
9
+ const errors_1 = require("../../errors");
10
+ /**
11
+ * Maps Hyperliquid API errors to PMXT unified error classes.
12
+ *
13
+ * Hyperliquid returns errors as plain strings or JSON objects in
14
+ * the response body. Common patterns:
15
+ * - "User has no account" -> AuthenticationError
16
+ * - "Insufficient margin" -> InsufficientFunds
17
+ * - "Invalid order" -> InvalidOrder
18
+ */
19
+ class HyperliquidErrorMapper extends error_mapper_1.ErrorMapper {
20
+ constructor() {
21
+ super('Hyperliquid');
22
+ }
23
+ extractErrorMessage(error) {
24
+ if (axios_1.default.isAxiosError(error) && error.response?.data) {
25
+ const data = error.response.data;
26
+ if (typeof data === 'string') {
27
+ return `[${error.response.status}] ${data}`;
28
+ }
29
+ if (data.status === 'err' && data.response) {
30
+ return `[${error.response.status}] ${data.response}`;
31
+ }
32
+ }
33
+ return super.extractErrorMessage(error);
34
+ }
35
+ mapBadRequestError(message, data) {
36
+ const lowerMessage = message.toLowerCase();
37
+ const responseStr = typeof data === 'object' && data?.response
38
+ ? String(data.response).toLowerCase()
39
+ : lowerMessage;
40
+ if (responseStr.includes('insufficient margin') || responseStr.includes('not enough')) {
41
+ return new errors_1.InsufficientFunds(message, this.exchangeName);
42
+ }
43
+ if (responseStr.includes('invalid order') || responseStr.includes('price out of range')) {
44
+ return new errors_1.InvalidOrder(message, this.exchangeName);
45
+ }
46
+ if (responseStr.includes('no account') || responseStr.includes('not authorized')) {
47
+ return new errors_1.AuthenticationError(message, this.exchangeName);
48
+ }
49
+ return super.mapBadRequestError(message, data);
50
+ }
51
+ mapError(error) {
52
+ if (axios_1.default.isAxiosError(error) && error.response?.status === 429) {
53
+ const retryAfter = error.response.headers?.['retry-after'];
54
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : undefined;
55
+ return new errors_1.RateLimitExceeded(this.extractErrorMessage(error), retryAfterSeconds, this.exchangeName);
56
+ }
57
+ return super.mapError(error);
58
+ }
59
+ }
60
+ exports.HyperliquidErrorMapper = HyperliquidErrorMapper;
61
+ exports.hyperliquidErrorMapper = new HyperliquidErrorMapper();