pmxt-core 2.37.14 → 2.38.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 (35) hide show
  1. package/dist/exchanges/hyperliquid/auth.d.ts +30 -0
  2. package/dist/exchanges/hyperliquid/auth.js +145 -0
  3. package/dist/exchanges/hyperliquid/config.d.ts +17 -0
  4. package/dist/exchanges/hyperliquid/config.js +28 -0
  5. package/dist/exchanges/hyperliquid/errors.d.ts +18 -0
  6. package/dist/exchanges/hyperliquid/errors.js +61 -0
  7. package/dist/exchanges/hyperliquid/fetcher.d.ts +140 -0
  8. package/dist/exchanges/hyperliquid/fetcher.js +137 -0
  9. package/dist/exchanges/hyperliquid/index.d.ts +31 -0
  10. package/dist/exchanges/hyperliquid/index.js +219 -0
  11. package/dist/exchanges/hyperliquid/normalizer.d.ts +18 -0
  12. package/dist/exchanges/hyperliquid/normalizer.js +339 -0
  13. package/dist/exchanges/hyperliquid/utils.d.ts +41 -0
  14. package/dist/exchanges/hyperliquid/utils.js +76 -0
  15. package/dist/exchanges/kalshi/api.d.ts +1 -1
  16. package/dist/exchanges/kalshi/api.js +1 -1
  17. package/dist/exchanges/limitless/api.d.ts +1 -1
  18. package/dist/exchanges/limitless/api.js +1 -1
  19. package/dist/exchanges/myriad/api.d.ts +1 -1
  20. package/dist/exchanges/myriad/api.js +1 -1
  21. package/dist/exchanges/opinion/api.d.ts +1 -1
  22. package/dist/exchanges/opinion/api.js +1 -1
  23. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  24. package/dist/exchanges/polymarket/api-clob.js +1 -1
  25. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  26. package/dist/exchanges/polymarket/api-data.js +1 -1
  27. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  28. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  29. package/dist/exchanges/probable/api.d.ts +1 -1
  30. package/dist/exchanges/probable/api.js +1 -1
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +5 -1
  33. package/dist/server/exchange-factory.js +6 -0
  34. package/dist/server/openapi.yaml +15 -0
  35. package/package.json +4 -3
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HyperliquidExchange = void 0;
4
+ const BaseExchange_1 = require("../../BaseExchange");
5
+ const errors_1 = require("../../errors");
6
+ const config_1 = require("./config");
7
+ const fetcher_1 = require("./fetcher");
8
+ const normalizer_1 = require("./normalizer");
9
+ const auth_1 = require("./auth");
10
+ const errors_2 = require("./errors");
11
+ class HyperliquidExchange extends BaseExchange_1.PredictionMarketExchange {
12
+ config;
13
+ fetcher;
14
+ normalizer;
15
+ walletAddress;
16
+ auth;
17
+ constructor(credentials) {
18
+ const opts = credentials && 'credentials' in credentials
19
+ ? credentials
20
+ : { credentials: credentials };
21
+ super(opts.credentials);
22
+ this.rateLimit = 200;
23
+ const testnet = 'testnet' in opts ? opts.testnet : false;
24
+ this.config = (0, config_1.getHyperliquidConfig)(opts.credentials?.baseUrl, testnet);
25
+ // Initialize auth if privateKey is provided (needed for trading)
26
+ if (opts.credentials?.privateKey) {
27
+ this.auth = new auth_1.HyperliquidAuth(opts.credentials, this.config.testnet);
28
+ this.walletAddress = this.auth.getAddress();
29
+ }
30
+ else {
31
+ // For read-only usage, users can pass walletAddress as apiKey
32
+ this.walletAddress = opts.credentials?.apiKey || undefined;
33
+ }
34
+ const ctx = {
35
+ http: this.http,
36
+ callApi: this.callApi.bind(this),
37
+ getHeaders: () => ({}),
38
+ };
39
+ this.fetcher = new fetcher_1.HyperliquidFetcher(ctx, this.config.baseUrl);
40
+ this.normalizer = new normalizer_1.HyperliquidNormalizer();
41
+ }
42
+ get name() {
43
+ return 'Hyperliquid';
44
+ }
45
+ // -------------------------------------------------------------------------
46
+ // Auth helpers
47
+ // -------------------------------------------------------------------------
48
+ requireWallet() {
49
+ if (!this.walletAddress) {
50
+ throw new errors_1.AuthenticationError('This operation requires a wallet address. ' +
51
+ 'Initialize HyperliquidExchange with credentials (apiKey = wallet address, or privateKey for trading).', 'Hyperliquid');
52
+ }
53
+ return this.walletAddress;
54
+ }
55
+ requireAuth() {
56
+ if (!this.auth) {
57
+ throw new errors_1.AuthenticationError('Trading requires a privateKey for EIP-712 signing. ' +
58
+ 'Initialize HyperliquidExchange with credentials including privateKey.', 'Hyperliquid');
59
+ }
60
+ return this.auth;
61
+ }
62
+ // -------------------------------------------------------------------------
63
+ // Market Data
64
+ // -------------------------------------------------------------------------
65
+ async fetchMarketsImpl(params) {
66
+ const rawOutcomes = await this.fetcher.fetchRawMarkets(params);
67
+ return rawOutcomes
68
+ .map(r => this.normalizer.normalizeMarket(r))
69
+ .filter((m) => m !== null);
70
+ }
71
+ async fetchEventsImpl(params) {
72
+ const [rawQuestions, meta, mids] = await Promise.all([
73
+ this.fetcher.fetchRawEvents(params),
74
+ this.fetcher.fetchOutcomeMeta(),
75
+ this.fetcher.fetchAllMids(),
76
+ ]);
77
+ return rawQuestions
78
+ .map(q => this.normalizer.normalizeEventWithMarkets(q, meta, mids))
79
+ .filter((e) => e !== null);
80
+ }
81
+ async fetchOrderBook(outcomeId) {
82
+ const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
83
+ return this.normalizer.normalizeOrderBook(raw, outcomeId);
84
+ }
85
+ async fetchOHLCV(outcomeId, params) {
86
+ const raw = await this.fetcher.fetchRawOHLCV(outcomeId, params);
87
+ return this.normalizer.normalizeOHLCV(raw, params);
88
+ }
89
+ async fetchTrades(outcomeId, params) {
90
+ const raw = await this.fetcher.fetchRawTrades(outcomeId, params || {});
91
+ return raw.map((r, i) => this.normalizer.normalizeTrade(r, i));
92
+ }
93
+ // -------------------------------------------------------------------------
94
+ // User Data
95
+ // -------------------------------------------------------------------------
96
+ async fetchBalance() {
97
+ const wallet = this.requireWallet();
98
+ const raw = await this.fetcher.fetchRawUserState(wallet);
99
+ return this.normalizer.normalizeBalance(raw);
100
+ }
101
+ async fetchPositions() {
102
+ const wallet = this.requireWallet();
103
+ const raw = await this.fetcher.fetchRawUserState(wallet);
104
+ return raw.assetPositions
105
+ .filter(ap => ap.position.coin.startsWith('#'))
106
+ .map(ap => this.normalizer.normalizePosition(ap.position));
107
+ }
108
+ async fetchOpenOrders() {
109
+ const wallet = this.requireWallet();
110
+ const raw = await this.fetcher.fetchRawOpenOrders(wallet);
111
+ return raw
112
+ .filter(o => o.coin.startsWith('#'))
113
+ .map(o => this.normalizer.normalizeOpenOrder(o));
114
+ }
115
+ async fetchMyTrades(params) {
116
+ const wallet = this.requireWallet();
117
+ const raw = await this.fetcher.fetchRawUserFills(wallet);
118
+ return raw
119
+ .filter(f => f.coin.startsWith('#'))
120
+ .map((f, i) => this.normalizer.normalizeUserTrade(f, i));
121
+ }
122
+ // -------------------------------------------------------------------------
123
+ // Trading (EIP-712 signing required)
124
+ // -------------------------------------------------------------------------
125
+ async buildOrder(params) {
126
+ const assetId = parseInt(params.outcomeId, 10);
127
+ // Key order matters for msgpack hash: a, b, p, s, r, t, c
128
+ const orderWire = {
129
+ a: assetId,
130
+ b: params.side === 'buy',
131
+ p: params.price !== undefined ? (0, auth_1.floatToWire)(params.price) : '0.5',
132
+ s: (0, auth_1.floatToWire)(params.amount),
133
+ r: false,
134
+ t: params.type === 'market'
135
+ ? { limit: { tif: 'Ioc' } }
136
+ : { limit: { tif: 'Gtc' } },
137
+ };
138
+ // Key order matters for msgpack hash: type, orders, grouping
139
+ const action = {
140
+ type: 'order',
141
+ orders: [orderWire],
142
+ grouping: 'na',
143
+ };
144
+ return {
145
+ exchange: this.name,
146
+ params,
147
+ raw: action,
148
+ };
149
+ }
150
+ async submitOrder(built) {
151
+ const auth = this.requireAuth();
152
+ const action = built.raw;
153
+ try {
154
+ const requestBody = await auth.signExchangeRequest(action);
155
+ const response = await this.http.post(`${this.config.baseUrl}/exchange`, requestBody);
156
+ const data = response.data;
157
+ if (data.status === 'err') {
158
+ throw errors_2.hyperliquidErrorMapper.mapError(new Error(data.response || 'Order submission failed'));
159
+ }
160
+ const resting = data.response?.data?.statuses?.[0];
161
+ return {
162
+ id: resting?.resting?.oid ? String(resting.resting.oid) : 'unknown',
163
+ marketId: built.params.marketId,
164
+ outcomeId: built.params.outcomeId,
165
+ side: built.params.side,
166
+ type: built.params.type,
167
+ price: built.params.price,
168
+ amount: built.params.amount,
169
+ status: resting?.resting ? 'open' : 'filled',
170
+ filled: resting?.filled?.totalSz ? parseFloat(resting.filled.totalSz) : 0,
171
+ remaining: built.params.amount - (resting?.filled?.totalSz ? parseFloat(resting.filled.totalSz) : 0),
172
+ timestamp: Date.now(),
173
+ };
174
+ }
175
+ catch (error) {
176
+ throw errors_2.hyperliquidErrorMapper.mapError(error);
177
+ }
178
+ }
179
+ async createOrder(params) {
180
+ const built = await this.buildOrder(params);
181
+ return this.submitOrder(built);
182
+ }
183
+ async cancelOrder(orderId) {
184
+ const auth = this.requireAuth();
185
+ // Key order matters for msgpack hash: type, cancels
186
+ // Each cancel entry: a (asset), o (order id)
187
+ const action = {
188
+ type: 'cancel',
189
+ cancels: [{ a: 0, o: parseInt(orderId, 10) }],
190
+ };
191
+ try {
192
+ const requestBody = await auth.signExchangeRequest(action);
193
+ const response = await this.http.post(`${this.config.baseUrl}/exchange`, requestBody);
194
+ const data = response.data;
195
+ if (data.status === 'err') {
196
+ throw new Error(data.response || 'Cancel failed');
197
+ }
198
+ return {
199
+ id: orderId,
200
+ marketId: '',
201
+ outcomeId: '',
202
+ side: 'buy',
203
+ type: 'limit',
204
+ amount: 0,
205
+ status: 'cancelled',
206
+ filled: 0,
207
+ remaining: 0,
208
+ timestamp: Date.now(),
209
+ };
210
+ }
211
+ catch (error) {
212
+ throw errors_2.hyperliquidErrorMapper.mapError(error);
213
+ }
214
+ }
215
+ async close() {
216
+ // No persistent connections to clean up
217
+ }
218
+ }
219
+ exports.HyperliquidExchange = HyperliquidExchange;
@@ -0,0 +1,18 @@
1
+ import { UnifiedMarket, UnifiedEvent, OrderBook, PriceCandle, Trade, UserTrade, Position, Balance, Order } from '../../types';
2
+ import { IExchangeNormalizer } from '../interfaces';
3
+ import { OHLCVParams } from '../../BaseExchange';
4
+ import { HyperliquidRawOutcomeWithQuestion, HyperliquidRawQuestion, HyperliquidRawL2Book, HyperliquidRawCandle, HyperliquidRawTrade, HyperliquidRawFill, HyperliquidRawOpenOrder, HyperliquidRawPosition, HyperliquidRawUserState, HyperliquidRawOutcomeMeta, HyperliquidRawMid } from './fetcher';
5
+ export declare class HyperliquidNormalizer implements IExchangeNormalizer<HyperliquidRawOutcomeWithQuestion, HyperliquidRawQuestion> {
6
+ normalizeMarket(raw: HyperliquidRawOutcomeWithQuestion): UnifiedMarket | null;
7
+ normalizeEvent(raw: HyperliquidRawQuestion): UnifiedEvent | null;
8
+ normalizeEventWithMarkets(raw: HyperliquidRawQuestion, outcomeMeta: HyperliquidRawOutcomeMeta, mids: HyperliquidRawMid): UnifiedEvent | null;
9
+ normalizeOrderBook(raw: HyperliquidRawL2Book, _id: string): OrderBook;
10
+ normalizeOHLCV(raw: HyperliquidRawCandle[], _params: OHLCVParams): PriceCandle[];
11
+ normalizeTrade(raw: HyperliquidRawTrade, _index: number): Trade;
12
+ normalizeUserTrade(raw: HyperliquidRawFill, _index: number): UserTrade;
13
+ normalizeOpenOrder(raw: HyperliquidRawOpenOrder): Order;
14
+ normalizePosition(raw: HyperliquidRawPosition): Position;
15
+ normalizeBalance(raw: HyperliquidRawUserState): Balance[];
16
+ private coinToMarketId;
17
+ private coinToOutcomeId;
18
+ }
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HyperliquidNormalizer = void 0;
4
+ const market_utils_1 = require("../../utils/market-utils");
5
+ const utils_1 = require("./utils");
6
+ const config_1 = require("./config");
7
+ function parseDescription(description) {
8
+ // Hyperliquid outcome descriptions use key:value pairs separated by |
9
+ // e.g. "class:priceBinary|underlying:BTC|expiry:20260509-0600|targetPrice:79583|period:1d"
10
+ // or simple values like "other", "index:0"
11
+ const result = { raw: description };
12
+ const parts = description.split('|');
13
+ for (const part of parts) {
14
+ const colonIdx = part.indexOf(':');
15
+ if (colonIdx === -1)
16
+ continue;
17
+ const key = part.slice(0, colonIdx);
18
+ const value = part.slice(colonIdx + 1);
19
+ switch (key) {
20
+ case 'class':
21
+ result.class = value;
22
+ break;
23
+ case 'underlying':
24
+ result.underlying = value;
25
+ break;
26
+ case 'expiry': {
27
+ result.expiry = value;
28
+ result.expiryDate = parseExpiryDate(value);
29
+ break;
30
+ }
31
+ case 'targetPrice':
32
+ result.targetPrice = value;
33
+ break;
34
+ case 'priceThresholds':
35
+ result.priceThresholds = value.split(',');
36
+ break;
37
+ case 'period':
38
+ result.period = value;
39
+ break;
40
+ case 'index':
41
+ result.index = value;
42
+ break;
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ /**
48
+ * Parse Hyperliquid's expiry format "YYYYMMDD-HHmm" into a Date.
49
+ * Example: "20260509-0600" -> 2026-05-09T06:00:00Z
50
+ */
51
+ function parseExpiryDate(expiry) {
52
+ const match = expiry.match(/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})$/);
53
+ if (!match)
54
+ return undefined;
55
+ const [, year, month, day, hour, minute] = match;
56
+ return new Date(`${year}-${month}-${day}T${hour}:${minute}:00Z`);
57
+ }
58
+ /**
59
+ * Build a human-readable title from the parsed description.
60
+ */
61
+ function buildTitle(rawName, parsed, questionParsed) {
62
+ // priceBinary: "BTC > $79,583 @ May 9, 2026"
63
+ if (parsed.class === 'priceBinary' && parsed.underlying && parsed.targetPrice) {
64
+ const price = Number(parsed.targetPrice).toLocaleString('en-US');
65
+ const dateStr = parsed.expiryDate
66
+ ? parsed.expiryDate.toISOString().replace('T', ' ').replace(':00.000Z', ' UTC')
67
+ : '';
68
+ return `${parsed.underlying} > $${price}${dateStr ? ` @ ${dateStr}` : ''}`;
69
+ }
70
+ // Named outcome in a priceBucket question: use thresholds from question
71
+ if (parsed.index !== undefined && questionParsed?.priceThresholds && questionParsed.underlying) {
72
+ const idx = parseInt(parsed.index, 10);
73
+ const thresholds = questionParsed.priceThresholds;
74
+ const underlying = questionParsed.underlying;
75
+ if (idx === 0 && thresholds.length > 0) {
76
+ return `${underlying} < $${Number(thresholds[0]).toLocaleString('en-US')}`;
77
+ }
78
+ if (idx === thresholds.length) {
79
+ return `${underlying} > $${Number(thresholds[thresholds.length - 1]).toLocaleString('en-US')}`;
80
+ }
81
+ if (idx > 0 && idx <= thresholds.length) {
82
+ const lo = Number(thresholds[idx - 1]).toLocaleString('en-US');
83
+ const hi = Number(thresholds[idx]).toLocaleString('en-US');
84
+ return `${underlying} $${lo} - $${hi}`;
85
+ }
86
+ }
87
+ return rawName;
88
+ }
89
+ function buildMarketUrl(outcomeId) {
90
+ return `https://app.hyperliquid.xyz/trade/${encodeURIComponent(`#${outcomeId * 10}`)}`;
91
+ }
92
+ function buildEventUrl(questionId) {
93
+ return `https://app.hyperliquid.xyz/trade/${encodeURIComponent(`#${questionId}`)}`;
94
+ }
95
+ function isOutcomeCoin(coin) {
96
+ return coin.startsWith('#');
97
+ }
98
+ // ----------------------------------------------------------------------------
99
+ // Normalizer
100
+ // ----------------------------------------------------------------------------
101
+ class HyperliquidNormalizer {
102
+ normalizeMarket(raw) {
103
+ if (!raw || !raw.outcome)
104
+ return null;
105
+ const outcome = raw.outcome;
106
+ const outcomeId = outcome.outcome;
107
+ const parsed = parseDescription(outcome.description);
108
+ // Also parse the question description for context (e.g. priceThresholds)
109
+ const questionParsed = raw.question
110
+ ? parseDescription(raw.question.description)
111
+ : undefined;
112
+ const midPrice = raw.midPrice ? parseFloat(raw.midPrice) : 0.5;
113
+ const yesPrice = Math.max(0, Math.min(1, midPrice));
114
+ const noPrice = Math.max(0, Math.min(1, 1 - midPrice));
115
+ const outcomes = [];
116
+ for (const side of outcome.sideSpecs) {
117
+ const sideKey = side.name.toLowerCase() === 'yes' ? 'yes' : 'no';
118
+ outcomes.push({
119
+ outcomeId: (0, utils_1.toOutcomeId)(outcomeId, sideKey),
120
+ marketId: (0, utils_1.toMarketId)(outcomeId),
121
+ label: side.name,
122
+ price: sideKey === 'yes' ? yesPrice : noPrice,
123
+ });
124
+ }
125
+ // If no sideSpecs provided, default to Yes/No
126
+ if (outcomes.length === 0) {
127
+ outcomes.push({
128
+ outcomeId: (0, utils_1.toOutcomeId)(outcomeId, 'yes'),
129
+ marketId: (0, utils_1.toMarketId)(outcomeId),
130
+ label: 'Yes',
131
+ price: yesPrice,
132
+ }, {
133
+ outcomeId: (0, utils_1.toOutcomeId)(outcomeId, 'no'),
134
+ marketId: (0, utils_1.toMarketId)(outcomeId),
135
+ label: 'No',
136
+ price: noPrice,
137
+ });
138
+ }
139
+ // Resolution date: prefer outcome-level expiry, fall back to question-level
140
+ const expiryDate = parsed.expiryDate
141
+ ?? questionParsed?.expiryDate
142
+ ?? undefined;
143
+ // Build a descriptive title from the parsed description
144
+ const title = buildTitle(outcome.name, parsed, questionParsed);
145
+ // Derive underlying from outcome or question
146
+ const underlying = parsed.underlying ?? questionParsed?.underlying;
147
+ const tags = [];
148
+ if (underlying) {
149
+ tags.push(underlying);
150
+ }
151
+ tags.push('Outcome Markets');
152
+ const category = underlying ? 'Crypto' : undefined;
153
+ const um = {
154
+ marketId: (0, utils_1.toMarketId)(outcomeId),
155
+ eventId: raw.question ? String(raw.question.question) : undefined,
156
+ title,
157
+ description: outcome.description,
158
+ slug: `hl-${outcomeId}`,
159
+ outcomes,
160
+ resolutionDate: expiryDate ?? new Date(0),
161
+ volume24h: 0,
162
+ liquidity: 0,
163
+ url: buildMarketUrl(outcomeId),
164
+ category,
165
+ tags,
166
+ tickSize: 0.001,
167
+ status: 'active',
168
+ };
169
+ (0, market_utils_1.addBinaryOutcomes)(um);
170
+ return um;
171
+ }
172
+ normalizeEvent(raw) {
173
+ if (!raw)
174
+ return null;
175
+ const parsed = parseDescription(raw.description);
176
+ const underlying = parsed.underlying;
177
+ // Build a more descriptive event title
178
+ let title = raw.name;
179
+ if (parsed.class === 'priceBucket' && underlying && parsed.priceThresholds) {
180
+ const thresholds = parsed.priceThresholds.map(t => `$${Number(t).toLocaleString('en-US')}`).join(', ');
181
+ const dateStr = parsed.expiryDate
182
+ ? parsed.expiryDate.toISOString().replace('T', ' ').replace(':00.000Z', ' UTC')
183
+ : '';
184
+ title = `${underlying} Price Bucket [${thresholds}]${dateStr ? ` @ ${dateStr}` : ''}`;
185
+ }
186
+ const tags = ['Outcome Markets'];
187
+ if (underlying)
188
+ tags.push(underlying);
189
+ return {
190
+ id: String(raw.question),
191
+ title,
192
+ description: raw.description,
193
+ slug: `hl-question-${raw.question}`,
194
+ markets: [],
195
+ volume24h: 0,
196
+ url: buildEventUrl(raw.question),
197
+ category: underlying ? 'Crypto' : undefined,
198
+ tags,
199
+ };
200
+ }
201
+ normalizeEventWithMarkets(raw, outcomeMeta, mids) {
202
+ const event = this.normalizeEvent(raw);
203
+ if (!event)
204
+ return null;
205
+ const markets = [];
206
+ for (const outcomeId of raw.namedOutcomes) {
207
+ if (raw.settledNamedOutcomes.includes(outcomeId))
208
+ continue;
209
+ const outcome = outcomeMeta.outcomes.find(o => o.outcome === outcomeId);
210
+ if (!outcome)
211
+ continue;
212
+ const yesCoin = (0, utils_1.toCoinNotation)(outcomeId, 'yes');
213
+ const midPrice = mids[yesCoin];
214
+ const market = this.normalizeMarket({
215
+ outcome,
216
+ question: raw,
217
+ midPrice,
218
+ });
219
+ if (market) {
220
+ markets.push(market);
221
+ }
222
+ }
223
+ return {
224
+ ...event,
225
+ markets,
226
+ volume24h: markets.reduce((sum, m) => sum + m.volume24h, 0),
227
+ };
228
+ }
229
+ normalizeOrderBook(raw, _id) {
230
+ const [rawBids, rawAsks] = raw.levels;
231
+ const bids = rawBids.map(level => ({
232
+ price: parseFloat(level.px),
233
+ size: parseFloat(level.sz),
234
+ }));
235
+ const asks = rawAsks.map(level => ({
236
+ price: parseFloat(level.px),
237
+ size: parseFloat(level.sz),
238
+ }));
239
+ return {
240
+ bids: [...bids].sort((a, b) => b.price - a.price),
241
+ asks: [...asks].sort((a, b) => a.price - b.price),
242
+ timestamp: raw.time,
243
+ };
244
+ }
245
+ normalizeOHLCV(raw, _params) {
246
+ return raw.map(candle => ({
247
+ timestamp: candle.t,
248
+ open: parseFloat(candle.o),
249
+ high: parseFloat(candle.h),
250
+ low: parseFloat(candle.l),
251
+ close: parseFloat(candle.c),
252
+ volume: parseFloat(candle.v),
253
+ }));
254
+ }
255
+ normalizeTrade(raw, _index) {
256
+ return {
257
+ id: String(raw.tid),
258
+ timestamp: raw.time,
259
+ price: parseFloat(raw.px),
260
+ amount: parseFloat(raw.sz),
261
+ side: raw.side === 'B' ? 'buy' : raw.side === 'A' ? 'sell' : 'unknown',
262
+ };
263
+ }
264
+ normalizeUserTrade(raw, _index) {
265
+ return {
266
+ id: String(raw.tid),
267
+ timestamp: raw.time,
268
+ price: parseFloat(raw.px),
269
+ amount: parseFloat(raw.sz),
270
+ side: raw.side === 'B' ? 'buy' : raw.side === 'A' ? 'sell' : 'unknown',
271
+ orderId: String(raw.oid),
272
+ };
273
+ }
274
+ normalizeOpenOrder(raw) {
275
+ const origSz = parseFloat(raw.origSz);
276
+ const currentSz = parseFloat(raw.sz);
277
+ return {
278
+ id: String(raw.oid),
279
+ marketId: this.coinToMarketId(raw.coin),
280
+ outcomeId: this.coinToOutcomeId(raw.coin),
281
+ side: raw.side === 'B' ? 'buy' : 'sell',
282
+ type: 'limit',
283
+ price: parseFloat(raw.limitPx),
284
+ amount: origSz,
285
+ status: currentSz < origSz ? 'open' : 'pending',
286
+ filled: origSz - currentSz,
287
+ remaining: currentSz,
288
+ timestamp: raw.timestamp,
289
+ };
290
+ }
291
+ normalizePosition(raw) {
292
+ if (!isOutcomeCoin(raw.coin)) {
293
+ return {
294
+ marketId: raw.coin,
295
+ outcomeId: raw.coin,
296
+ outcomeLabel: raw.coin,
297
+ size: parseFloat(raw.szi),
298
+ entryPrice: raw.entryPx ? parseFloat(raw.entryPx) : 0,
299
+ currentPrice: 0,
300
+ unrealizedPnL: parseFloat(raw.unrealizedPnl),
301
+ };
302
+ }
303
+ return {
304
+ marketId: this.coinToMarketId(raw.coin),
305
+ outcomeId: this.coinToOutcomeId(raw.coin),
306
+ outcomeLabel: raw.coin,
307
+ size: parseFloat(raw.szi),
308
+ entryPrice: raw.entryPx ? parseFloat(raw.entryPx) : 0,
309
+ currentPrice: 0,
310
+ unrealizedPnL: parseFloat(raw.unrealizedPnl),
311
+ };
312
+ }
313
+ normalizeBalance(raw) {
314
+ const summary = raw.crossMarginSummary;
315
+ const total = parseFloat(summary.accountValue);
316
+ const locked = parseFloat(summary.totalMarginUsed);
317
+ return [{
318
+ currency: 'USDH',
319
+ total,
320
+ available: total - locked,
321
+ locked,
322
+ }];
323
+ }
324
+ // -- Private helpers -------------------------------------------------------
325
+ coinToMarketId(coin) {
326
+ if (!coin.startsWith('#'))
327
+ return coin;
328
+ const encoding = parseInt(coin.slice(1), 10);
329
+ const { outcomeId } = (0, utils_1.decodeAssetId)(config_1.OUTCOME_ASSET_BASE + encoding);
330
+ return (0, utils_1.toMarketId)(outcomeId);
331
+ }
332
+ coinToOutcomeId(coin) {
333
+ if (!coin.startsWith('#'))
334
+ return coin;
335
+ const encoding = parseInt(coin.slice(1), 10);
336
+ return String(config_1.OUTCOME_ASSET_BASE + encoding);
337
+ }
338
+ }
339
+ exports.HyperliquidNormalizer = HyperliquidNormalizer;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Encode an outcome ID and side into a Hyperliquid asset ID.
3
+ *
4
+ * Formula: 100_000_000 + (10 * outcomeId) + side
5
+ * where side = 0 (Yes) or 1 (No).
6
+ */
7
+ export declare function encodeAssetId(outcomeId: number, side: 'yes' | 'no'): number;
8
+ /**
9
+ * Decode a Hyperliquid asset ID back into outcome ID and side.
10
+ */
11
+ export declare function decodeAssetId(assetId: number): {
12
+ outcomeId: number;
13
+ side: 'yes' | 'no';
14
+ };
15
+ /**
16
+ * Convert an outcome ID and side to the Hyperliquid coin notation (#encoding).
17
+ *
18
+ * Example: outcome 1, Yes -> "#10"
19
+ */
20
+ export declare function toCoinNotation(outcomeId: number, side: 'yes' | 'no'): string;
21
+ /**
22
+ * Convert an asset encoding (the number after #) to outcome ID and side.
23
+ */
24
+ export declare function fromCoinEncoding(encoding: number): {
25
+ outcomeId: number;
26
+ side: 'yes' | 'no';
27
+ };
28
+ /**
29
+ * Build a unique market ID string from an outcome.
30
+ * We use "hl-outcome-{outcomeId}" as the market ID.
31
+ */
32
+ export declare function toMarketId(outcomeId: number): string;
33
+ /**
34
+ * Extract the numeric outcome ID from our market ID format.
35
+ */
36
+ export declare function fromMarketId(marketId: string): number;
37
+ /**
38
+ * Build an outcome ID string for the unified type.
39
+ * Format: "{assetId}" which is the full numeric asset ID.
40
+ */
41
+ export declare function toOutcomeId(outcomeId: number, side: 'yes' | 'no'): string;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeAssetId = encodeAssetId;
4
+ exports.decodeAssetId = decodeAssetId;
5
+ exports.toCoinNotation = toCoinNotation;
6
+ exports.fromCoinEncoding = fromCoinEncoding;
7
+ exports.toMarketId = toMarketId;
8
+ exports.fromMarketId = fromMarketId;
9
+ exports.toOutcomeId = toOutcomeId;
10
+ const config_1 = require("./config");
11
+ /**
12
+ * Encode an outcome ID and side into a Hyperliquid asset ID.
13
+ *
14
+ * Formula: 100_000_000 + (10 * outcomeId) + side
15
+ * where side = 0 (Yes) or 1 (No).
16
+ */
17
+ function encodeAssetId(outcomeId, side) {
18
+ const sideValue = side === 'yes' ? config_1.SIDE_YES : config_1.SIDE_NO;
19
+ return config_1.OUTCOME_ASSET_BASE + (config_1.OUTCOME_MULTIPLIER * outcomeId) + sideValue;
20
+ }
21
+ /**
22
+ * Decode a Hyperliquid asset ID back into outcome ID and side.
23
+ */
24
+ function decodeAssetId(assetId) {
25
+ const offset = assetId - config_1.OUTCOME_ASSET_BASE;
26
+ const sideValue = offset % config_1.OUTCOME_MULTIPLIER;
27
+ const outcomeId = (offset - sideValue) / config_1.OUTCOME_MULTIPLIER;
28
+ return {
29
+ outcomeId,
30
+ side: sideValue === config_1.SIDE_YES ? 'yes' : 'no',
31
+ };
32
+ }
33
+ /**
34
+ * Convert an outcome ID and side to the Hyperliquid coin notation (#encoding).
35
+ *
36
+ * Example: outcome 1, Yes -> "#10"
37
+ */
38
+ function toCoinNotation(outcomeId, side) {
39
+ const encoding = config_1.OUTCOME_MULTIPLIER * outcomeId + (side === 'yes' ? config_1.SIDE_YES : config_1.SIDE_NO);
40
+ return `#${encoding}`;
41
+ }
42
+ /**
43
+ * Convert an asset encoding (the number after #) to outcome ID and side.
44
+ */
45
+ function fromCoinEncoding(encoding) {
46
+ const sideValue = encoding % config_1.OUTCOME_MULTIPLIER;
47
+ const outcomeId = (encoding - sideValue) / config_1.OUTCOME_MULTIPLIER;
48
+ return {
49
+ outcomeId,
50
+ side: sideValue === config_1.SIDE_YES ? 'yes' : 'no',
51
+ };
52
+ }
53
+ /**
54
+ * Build a unique market ID string from an outcome.
55
+ * We use "hl-outcome-{outcomeId}" as the market ID.
56
+ */
57
+ function toMarketId(outcomeId) {
58
+ return `hl-outcome-${outcomeId}`;
59
+ }
60
+ /**
61
+ * Extract the numeric outcome ID from our market ID format.
62
+ */
63
+ function fromMarketId(marketId) {
64
+ const match = marketId.match(/^hl-outcome-(\d+)$/);
65
+ if (!match) {
66
+ throw new Error(`Invalid Hyperliquid market ID: ${marketId}`);
67
+ }
68
+ return parseInt(match[1], 10);
69
+ }
70
+ /**
71
+ * Build an outcome ID string for the unified type.
72
+ * Format: "{assetId}" which is the full numeric asset ID.
73
+ */
74
+ function toOutcomeId(outcomeId, side) {
75
+ return String(encodeAssetId(outcomeId, side));
76
+ }