@outcome.xyz/hip4 1.0.0-beta

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,4244 @@
1
+ 'use strict';
2
+
3
+ // src/lib/precision/primitives/_decimal-impl.ts
4
+ var PRECISION = 28;
5
+ var TO_EXP_NEG = -7;
6
+ var TO_EXP_POS = 21;
7
+ var ROUND_UP = 0;
8
+ var ROUND_DOWN = 1;
9
+ var ROUND_HALF_UP = 4;
10
+ var POW10_CACHE = [1n];
11
+ function pow10(n) {
12
+ if (n < 0) throw new Error(`pow10: negative n (${n})`);
13
+ if (n < POW10_CACHE.length) return POW10_CACHE[n];
14
+ let last = POW10_CACHE[POW10_CACHE.length - 1];
15
+ for (let i = POW10_CACHE.length; i <= n; i++) {
16
+ last = last * 10n;
17
+ POW10_CACHE.push(last);
18
+ }
19
+ return POW10_CACHE[n];
20
+ }
21
+ function digitCount(n) {
22
+ if (n === 0n) return 0;
23
+ return n.toString().length;
24
+ }
25
+ function trimTrailingZeros(m, e) {
26
+ if (m === 0n) return { m: 0n, e: 0 };
27
+ let mantissa = m;
28
+ let exp = e;
29
+ while (mantissa % 10n === 0n) {
30
+ mantissa /= 10n;
31
+ exp += 1;
32
+ }
33
+ return { m: mantissa, e: exp };
34
+ }
35
+ function parseString(input) {
36
+ const s = input.trim();
37
+ if (s === "") throw new Error("Invalid decimal: empty string");
38
+ const m = s.match(
39
+ /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?\d+))?$/
40
+ );
41
+ if (!m) throw new Error(`Invalid decimal: "${input}"`);
42
+ const sign = m[1];
43
+ const intPart = m[2] ?? "";
44
+ const fracPart = m[3] ?? m[4] ?? "";
45
+ const expPart = m[5] ?? "0";
46
+ let digits = intPart + fracPart;
47
+ digits = digits.replace(/^0+/, "");
48
+ let exp = parseInt(expPart, 10) - fracPart.length;
49
+ if (digits === "") {
50
+ return { neg: sign === "-", mantissa: 0n, exp: 0 };
51
+ }
52
+ const mantissa0 = BigInt(digits);
53
+ const trimmed = trimTrailingZeros(mantissa0, exp);
54
+ return {
55
+ neg: sign === "-",
56
+ mantissa: trimmed.m,
57
+ exp: trimmed.e
58
+ };
59
+ }
60
+ function fromParts(neg2, mantissa, exp) {
61
+ const t = trimTrailingZeros(mantissa, exp);
62
+ const sign = t.m === 0n ? false : neg2;
63
+ return new Decimal(_INTERNAL, sign, t.m, t.e);
64
+ }
65
+ var _INTERNAL = /* @__PURE__ */ Symbol("DecimalInternal");
66
+ function cmpAbs(a, b) {
67
+ if (a.mantissa === 0n && b.mantissa === 0n) return 0;
68
+ if (a.mantissa === 0n) return -1;
69
+ if (b.mantissa === 0n) return 1;
70
+ const ea = digitCount(a.mantissa) - 1 + a.exp;
71
+ const eb = digitCount(b.mantissa) - 1 + b.exp;
72
+ if (ea !== eb) return ea < eb ? -1 : 1;
73
+ if (a.exp === b.exp) {
74
+ return a.mantissa === b.mantissa ? 0 : a.mantissa < b.mantissa ? -1 : 1;
75
+ }
76
+ if (a.exp > b.exp) {
77
+ const aScaled = a.mantissa * pow10(a.exp - b.exp);
78
+ return aScaled === b.mantissa ? 0 : aScaled < b.mantissa ? -1 : 1;
79
+ }
80
+ const bScaled = b.mantissa * pow10(b.exp - a.exp);
81
+ return a.mantissa === bScaled ? 0 : a.mantissa < bScaled ? -1 : 1;
82
+ }
83
+ function roundToSigFigs(mantissa, exp, sigFigs, hasMoreNonZeroBeyond = false) {
84
+ if (mantissa === 0n) return { m: 0n, e: 0 };
85
+ const d = digitCount(mantissa);
86
+ if (d <= sigFigs) return trimTrailingZeros(mantissa, exp);
87
+ const trim = d - sigFigs;
88
+ const divisor = pow10(trim);
89
+ const trunc = mantissa / divisor;
90
+ const rem = mantissa % divisor;
91
+ const half = pow10(trim - 1) * 5n;
92
+ let rounded;
93
+ if (rem > half || rem === half && hasMoreNonZeroBeyond) {
94
+ rounded = trunc + 1n;
95
+ } else if (rem < half) {
96
+ rounded = trunc;
97
+ } else {
98
+ rounded = trunc + 1n;
99
+ }
100
+ return trimTrailingZeros(rounded, exp + trim);
101
+ }
102
+ var Decimal = class _Decimal {
103
+ static ROUND_UP = ROUND_UP;
104
+ static ROUND_DOWN = ROUND_DOWN;
105
+ static ROUND_HALF_UP = ROUND_HALF_UP;
106
+ neg;
107
+ mantissa;
108
+ exp;
109
+ constructor(a, b, c, d) {
110
+ if (a === _INTERNAL) {
111
+ this.neg = b;
112
+ this.mantissa = c;
113
+ this.exp = d;
114
+ return;
115
+ }
116
+ if (a instanceof _Decimal) {
117
+ this.neg = a.neg;
118
+ this.mantissa = a.mantissa;
119
+ this.exp = a.exp;
120
+ return;
121
+ }
122
+ let s;
123
+ if (typeof a === "number") {
124
+ if (!Number.isFinite(a)) throw new Error(`Invalid number: ${a}`);
125
+ s = String(a);
126
+ } else if (typeof a === "string") {
127
+ s = a;
128
+ } else {
129
+ throw new Error(`Unsupported input type: ${typeof a}`);
130
+ }
131
+ const parsed = parseString(s);
132
+ this.neg = parsed.neg;
133
+ this.mantissa = parsed.mantissa;
134
+ this.exp = parsed.exp;
135
+ }
136
+ // -- predicates -----------------------------------------------------------
137
+ isZero() {
138
+ return this.mantissa === 0n;
139
+ }
140
+ isNegative() {
141
+ return this.neg;
142
+ }
143
+ isNeg() {
144
+ return this.neg;
145
+ }
146
+ isPositive() {
147
+ return !this.neg;
148
+ }
149
+ isPos() {
150
+ return this.isPositive();
151
+ }
152
+ isInteger() {
153
+ if (this.mantissa === 0n) return true;
154
+ return this.exp >= 0;
155
+ }
156
+ // -- comparisons ----------------------------------------------------------
157
+ comparedTo(other) {
158
+ const b = toDec(other);
159
+ if (this.mantissa === 0n && b.mantissa === 0n) return 0;
160
+ if (this.neg && !b.neg) return -1;
161
+ if (!this.neg && b.neg) return 1;
162
+ if (this.mantissa === 0n) return b.neg ? 1 : -1;
163
+ if (b.mantissa === 0n) return this.neg ? -1 : 1;
164
+ const c = cmpAbs(this, b);
165
+ return this.neg ? -c : c;
166
+ }
167
+ cmp(other) {
168
+ return this.comparedTo(other);
169
+ }
170
+ equals(other) {
171
+ return this.comparedTo(other) === 0;
172
+ }
173
+ eq(other) {
174
+ return this.equals(other);
175
+ }
176
+ greaterThan(other) {
177
+ return this.comparedTo(other) > 0;
178
+ }
179
+ gt(other) {
180
+ return this.greaterThan(other);
181
+ }
182
+ greaterThanOrEqualTo(other) {
183
+ return this.comparedTo(other) >= 0;
184
+ }
185
+ gte(other) {
186
+ return this.greaterThanOrEqualTo(other);
187
+ }
188
+ lessThan(other) {
189
+ return this.comparedTo(other) < 0;
190
+ }
191
+ lt(other) {
192
+ return this.lessThan(other);
193
+ }
194
+ lessThanOrEqualTo(other) {
195
+ return this.comparedTo(other) <= 0;
196
+ }
197
+ lte(other) {
198
+ return this.lessThanOrEqualTo(other);
199
+ }
200
+ // -- unary ----------------------------------------------------------------
201
+ abs() {
202
+ if (!this.neg) return this;
203
+ return fromParts(false, this.mantissa, this.exp);
204
+ }
205
+ negated() {
206
+ if (this.mantissa === 0n) return this;
207
+ return fromParts(!this.neg, this.mantissa, this.exp);
208
+ }
209
+ neg_() {
210
+ return this.negated();
211
+ }
212
+ // -- arithmetic -----------------------------------------------------------
213
+ plus(other) {
214
+ const b = toDec(other);
215
+ if (this.mantissa === 0n) {
216
+ const r = roundToSigFigs(b.mantissa, b.exp, PRECISION);
217
+ return fromParts(r.m === 0n ? false : b.neg, r.m, r.e);
218
+ }
219
+ if (b.mantissa === 0n) {
220
+ const r = roundToSigFigs(this.mantissa, this.exp, PRECISION);
221
+ return fromParts(r.m === 0n ? false : this.neg, r.m, r.e);
222
+ }
223
+ const minExp = Math.min(this.exp, b.exp);
224
+ const aM = this.mantissa * pow10(this.exp - minExp);
225
+ const bM = b.mantissa * pow10(b.exp - minExp);
226
+ let resNeg;
227
+ let resM;
228
+ if (this.neg === b.neg) {
229
+ resNeg = this.neg;
230
+ resM = aM + bM;
231
+ } else if (aM === bM) {
232
+ return ZERO;
233
+ } else if (aM > bM) {
234
+ resNeg = this.neg;
235
+ resM = aM - bM;
236
+ } else {
237
+ resNeg = b.neg;
238
+ resM = bM - aM;
239
+ }
240
+ const rounded = roundToSigFigs(resM, minExp, PRECISION);
241
+ return fromParts(resNeg, rounded.m, rounded.e);
242
+ }
243
+ minus(other) {
244
+ const b = toDec(other);
245
+ return this.plus(fromParts(!b.neg, b.mantissa, b.exp));
246
+ }
247
+ times(other) {
248
+ const b = toDec(other);
249
+ if (this.mantissa === 0n || b.mantissa === 0n) return ZERO;
250
+ const m = this.mantissa * b.mantissa;
251
+ const e = this.exp + b.exp;
252
+ const sign = this.neg !== b.neg;
253
+ const rounded = roundToSigFigs(m, e, PRECISION);
254
+ return fromParts(sign, rounded.m, rounded.e);
255
+ }
256
+ dividedBy(other) {
257
+ const b = toDec(other);
258
+ if (b.mantissa === 0n) throw new Error("Division by zero");
259
+ if (this.mantissa === 0n) return ZERO;
260
+ const sign = this.neg !== b.neg;
261
+ const aDigits = digitCount(this.mantissa);
262
+ const bDigits = digitCount(b.mantissa);
263
+ const wantDigits = PRECISION + 2;
264
+ const haveDigits = aDigits - bDigits + (this.mantissa >= b.mantissa ? 1 : 0);
265
+ const shift = Math.max(0, wantDigits - haveDigits);
266
+ const scaledNum = this.mantissa * pow10(shift);
267
+ const q = scaledNum / b.mantissa;
268
+ const rem = scaledNum % b.mantissa;
269
+ const newExp = this.exp - b.exp - shift;
270
+ const rounded = roundToSigFigs(q, newExp, PRECISION, rem !== 0n);
271
+ return fromParts(sign, rounded.m, rounded.e);
272
+ }
273
+ div(other) {
274
+ return this.dividedBy(other);
275
+ }
276
+ // Integer-exponent power. Throws on non-integer exponent.
277
+ pow(exponent) {
278
+ const e = toDec(exponent);
279
+ if (!e.isInteger()) {
280
+ throw new Error("pow: only integer exponents are supported");
281
+ }
282
+ let n;
283
+ if (e.mantissa === 0n) {
284
+ return ONE;
285
+ }
286
+ if (e.exp === 0) {
287
+ if (e.mantissa > BigInt(Number.MAX_SAFE_INTEGER)) {
288
+ throw new Error("pow: exponent too large");
289
+ }
290
+ n = Number(e.mantissa) * (e.neg ? -1 : 1);
291
+ } else {
292
+ const full = e.mantissa * pow10(e.exp);
293
+ if (full > BigInt(Number.MAX_SAFE_INTEGER)) {
294
+ throw new Error("pow: exponent too large");
295
+ }
296
+ n = Number(full) * (e.neg ? -1 : 1);
297
+ }
298
+ if (n === 0) return ONE;
299
+ if (this.mantissa === 0n) {
300
+ if (n < 0)
301
+ throw new Error("pow: 0 cannot be raised to a negative exponent");
302
+ return ZERO;
303
+ }
304
+ const negResult = this.neg && n % 2 !== 0;
305
+ const absExp = Math.abs(n);
306
+ let baseM = this.mantissa;
307
+ let baseE = this.exp;
308
+ let resM = 1n;
309
+ let resE = 0;
310
+ let k = absExp;
311
+ while (k > 0) {
312
+ if (k & 1) {
313
+ resM = resM * baseM;
314
+ resE = resE + baseE;
315
+ const r = roundToSigFigs(resM, resE, PRECISION);
316
+ resM = r.m;
317
+ resE = r.e;
318
+ }
319
+ k >>>= 1;
320
+ if (k > 0) {
321
+ baseM = baseM * baseM;
322
+ baseE = baseE + baseE;
323
+ const r = roundToSigFigs(baseM, baseE, PRECISION);
324
+ baseM = r.m;
325
+ baseE = r.e;
326
+ }
327
+ }
328
+ if (n < 0) {
329
+ return fromParts(negResult, 1n, 0).dividedBy(
330
+ fromParts(false, resM, resE)
331
+ );
332
+ }
333
+ return fromParts(negResult, resM, resE);
334
+ }
335
+ // -- rounding to integer using global mode (HALF_UP) ----------------------
336
+ round() {
337
+ return roundDp(this, 0, ROUND_HALF_UP);
338
+ }
339
+ floor() {
340
+ return roundDp(this, 0, ROUND_FLOOR_INTERNAL);
341
+ }
342
+ ceil() {
343
+ return roundDp(this, 0, ROUND_CEIL_INTERNAL);
344
+ }
345
+ // -- toFixed --------------------------------------------------------------
346
+ toFixed(dp, mode = ROUND_HALF_UP) {
347
+ if (dp === void 0) {
348
+ return this.toFixedFlat();
349
+ }
350
+ if (!Number.isInteger(dp) || dp < 0) {
351
+ throw new RangeError(
352
+ `toFixed: dp must be a non-negative integer (got ${dp})`
353
+ );
354
+ }
355
+ if (this.mantissa === 0n) {
356
+ return dp === 0 ? "0" : "0." + "0".repeat(dp);
357
+ }
358
+ const targetExp = -dp;
359
+ let m;
360
+ if (this.exp >= targetExp) {
361
+ m = this.mantissa * pow10(this.exp - targetExp);
362
+ } else {
363
+ const trim = targetExp - this.exp;
364
+ const divisor = pow10(trim);
365
+ const trunc = this.mantissa / divisor;
366
+ const rem = this.mantissa % divisor;
367
+ const half = pow10(trim - 1) * 5n;
368
+ switch (mode) {
369
+ case ROUND_DOWN:
370
+ m = trunc;
371
+ break;
372
+ case ROUND_UP:
373
+ m = rem === 0n ? trunc : trunc + 1n;
374
+ break;
375
+ case ROUND_HALF_UP:
376
+ if (rem > half) m = trunc + 1n;
377
+ else if (rem < half) m = trunc;
378
+ else m = trunc + 1n;
379
+ break;
380
+ default:
381
+ throw new Error(`toFixed: unsupported rounding mode (${mode})`);
382
+ }
383
+ }
384
+ const sign = this.neg ? "-" : "";
385
+ const s = m.toString();
386
+ if (dp === 0) return sign + s;
387
+ if (s.length <= dp) {
388
+ return sign + "0." + "0".repeat(dp - s.length) + s;
389
+ }
390
+ return sign + s.slice(0, s.length - dp) + "." + s.slice(s.length - dp);
391
+ }
392
+ // toString without exponential notation, full precision (matches decimal.js
393
+ // toFixed() with no args for the values produced in this codebase).
394
+ toFixedFlat() {
395
+ if (this.mantissa === 0n) return "0";
396
+ return formatPlain(this);
397
+ }
398
+ // -- toString -------------------------------------------------------------
399
+ toString() {
400
+ if (this.mantissa === 0n) return "0";
401
+ const valExp = digitCount(this.mantissa) - 1 + this.exp;
402
+ if (valExp <= TO_EXP_NEG || valExp >= TO_EXP_POS) {
403
+ return formatExponential(this, valExp);
404
+ }
405
+ return formatPlain(this);
406
+ }
407
+ valueOf() {
408
+ return this.toString();
409
+ }
410
+ // -- toNumber -------------------------------------------------------------
411
+ toNumber() {
412
+ if (this.mantissa === 0n) return 0;
413
+ return parseFloat(this.toString());
414
+ }
415
+ // -- floor of log10(|this|) - used internally; not on decimal.js's API ----
416
+ //
417
+ // Returns floor(log10(|value|)) as a Decimal integer. Throws on zero.
418
+ // Equivalent to (digitCount(mantissa) - 1 + exp).
419
+ floorLog10() {
420
+ if (this.mantissa === 0n) {
421
+ throw new RangeError("floorLog10: log of zero is undefined");
422
+ }
423
+ const v = digitCount(this.mantissa) - 1 + this.exp;
424
+ return fromInt(v);
425
+ }
426
+ // -- significant digits ---------------------------------------------------
427
+ toSignificantDigits(sigFigs) {
428
+ if (!Number.isInteger(sigFigs) || sigFigs < 1) {
429
+ throw new RangeError(
430
+ `toSignificantDigits: sigFigs must be >= 1 (got ${sigFigs})`
431
+ );
432
+ }
433
+ if (this.mantissa === 0n) return this;
434
+ const r = roundToSigFigs(this.mantissa, this.exp, sigFigs);
435
+ return fromParts(this.neg, r.m, r.e);
436
+ }
437
+ // -- statics --------------------------------------------------------------
438
+ static min(...values) {
439
+ if (values.length === 0)
440
+ throw new Error("min: at least one argument required");
441
+ let best = toDec(values[0]);
442
+ for (let i = 1; i < values.length; i++) {
443
+ const v = toDec(values[i]);
444
+ if (v.lt(best)) best = v;
445
+ }
446
+ return best;
447
+ }
448
+ static max(...values) {
449
+ if (values.length === 0)
450
+ throw new Error("max: at least one argument required");
451
+ let best = toDec(values[0]);
452
+ for (let i = 1; i < values.length; i++) {
453
+ const v = toDec(values[i]);
454
+ if (v.gt(best)) best = v;
455
+ }
456
+ return best;
457
+ }
458
+ static sum(...values) {
459
+ let acc = ZERO;
460
+ for (const v of values) acc = acc.plus(toDec(v));
461
+ return acc;
462
+ }
463
+ };
464
+ var ROUND_FLOOR_INTERNAL = 100;
465
+ var ROUND_CEIL_INTERNAL = 101;
466
+ function roundDp(d, dp, mode) {
467
+ if (d.mantissa === 0n) return ZERO;
468
+ const targetExp = -0;
469
+ if (d.exp >= targetExp) {
470
+ return d;
471
+ }
472
+ const trim = targetExp - d.exp;
473
+ const divisor = pow10(trim);
474
+ const trunc = d.mantissa / divisor;
475
+ const rem = d.mantissa % divisor;
476
+ const half = pow10(trim - 1) * 5n;
477
+ let rounded;
478
+ switch (mode) {
479
+ case ROUND_DOWN:
480
+ rounded = trunc;
481
+ break;
482
+ case ROUND_UP:
483
+ rounded = rem === 0n ? trunc : trunc + 1n;
484
+ break;
485
+ case ROUND_HALF_UP:
486
+ if (rem > half) rounded = trunc + 1n;
487
+ else if (rem < half) rounded = trunc;
488
+ else rounded = trunc + 1n;
489
+ break;
490
+ case ROUND_FLOOR_INTERNAL:
491
+ if (d.neg) rounded = rem === 0n ? trunc : trunc + 1n;
492
+ else rounded = trunc;
493
+ break;
494
+ case ROUND_CEIL_INTERNAL:
495
+ if (d.neg) rounded = trunc;
496
+ else rounded = rem === 0n ? trunc : trunc + 1n;
497
+ break;
498
+ default:
499
+ throw new Error(`Unsupported rounding mode: ${mode}`);
500
+ }
501
+ if (rounded === 0n) return ZERO;
502
+ return fromParts(d.neg, rounded, targetExp);
503
+ }
504
+ function formatPlain(d) {
505
+ if (d.mantissa === 0n) return "0";
506
+ const digits = d.mantissa.toString();
507
+ const sign = d.neg ? "-" : "";
508
+ if (d.exp >= 0) {
509
+ return sign + digits + "0".repeat(d.exp);
510
+ }
511
+ const fracLen = -d.exp;
512
+ if (fracLen >= digits.length) {
513
+ return sign + "0." + "0".repeat(fracLen - digits.length) + digits;
514
+ }
515
+ const intPart = digits.slice(0, digits.length - fracLen);
516
+ const fracPart = digits.slice(digits.length - fracLen);
517
+ return sign + intPart + "." + fracPart;
518
+ }
519
+ function formatExponential(d, valExp) {
520
+ const digits = d.mantissa.toString();
521
+ const sign = d.neg ? "-" : "";
522
+ const head = digits[0];
523
+ const tail = digits.slice(1);
524
+ const mant = tail.length === 0 ? head : `${head}.${tail}`;
525
+ const expPart = valExp >= 0 ? `e+${valExp}` : `e${valExp}`;
526
+ return sign + mant + expPart;
527
+ }
528
+ function toDec(x) {
529
+ return x instanceof Decimal ? x : new Decimal(x);
530
+ }
531
+ function fromInt(n) {
532
+ if (!Number.isInteger(n)) throw new Error("fromInt: not an integer");
533
+ if (n === 0) return ZERO;
534
+ const neg2 = n < 0;
535
+ const abs3 = Math.abs(n);
536
+ return fromParts(neg2, BigInt(abs3), 0);
537
+ }
538
+ var ZERO = new Decimal(_INTERNAL, false, 0n, 0);
539
+ var ONE = new Decimal(_INTERNAL, false, 1n, 0);
540
+
541
+ // src/lib/precision/primitives/core.ts
542
+ function toDecimal(value) {
543
+ if (value instanceof Decimal) return value;
544
+ if (typeof value === "number") {
545
+ if (!Number.isFinite(value)) {
546
+ throw new Error(`Invalid numeric input: ${value}`);
547
+ }
548
+ return new Decimal(value);
549
+ }
550
+ if (typeof value === "string") {
551
+ const trimmed = value.trim();
552
+ if (trimmed === "") {
553
+ throw new Error("Empty string is not a valid decimal input");
554
+ }
555
+ return new Decimal(trimmed);
556
+ }
557
+ throw new Error(`Unsupported input type: ${typeof value}`);
558
+ }
559
+ function toNum(value) {
560
+ return toDecimal(value).toNumber();
561
+ }
562
+ function sub(a, b) {
563
+ return toDecimal(a).minus(toDecimal(b)).toString();
564
+ }
565
+ function mul(a, b) {
566
+ return toDecimal(a).times(toDecimal(b)).toString();
567
+ }
568
+ function div(a, b, dp) {
569
+ const divisor = toDecimal(b);
570
+ if (divisor.isZero()) throw new Error("Division by zero");
571
+ const result = toDecimal(a).dividedBy(divisor);
572
+ return result.toString();
573
+ }
574
+ function pow(base, exp) {
575
+ return toDecimal(base).pow(toDecimal(exp)).toString();
576
+ }
577
+
578
+ // src/lib/precision/primitives/compare.ts
579
+ function lt(a, b) {
580
+ return toDecimal(a).lessThan(toDecimal(b));
581
+ }
582
+ function isZero(value) {
583
+ return toDecimal(value).isZero();
584
+ }
585
+ function min(...values) {
586
+ if (values.length === 0) throw new Error("min requires at least one argument");
587
+ return Decimal.min(...values.map(toDecimal)).toString();
588
+ }
589
+
590
+ // src/lib/precision/primitives/clamp.ts
591
+ function clamp(value, lo, hi) {
592
+ const dLo = toDecimal(lo);
593
+ const dHi = toDecimal(hi);
594
+ if (dLo.gt(dHi))
595
+ throw new RangeError(`clamp: lo (${dLo}) must be <= hi (${dHi})`);
596
+ return Decimal.max(dLo, Decimal.min(dHi, toDecimal(value))).toString();
597
+ }
598
+
599
+ // src/lib/precision/io/format.ts
600
+ new Decimal(1e3);
601
+ new Decimal(1e6);
602
+ new Decimal(1e9);
603
+ function fixed(value, dp) {
604
+ return toDecimal(value).toFixed(dp);
605
+ }
606
+
607
+ // src/adapter/hyperliquid/client.ts
608
+ var HLApiError = class extends Error {
609
+ constructor(status, message) {
610
+ super(message);
611
+ this.status = status;
612
+ this.name = "HLApiError";
613
+ }
614
+ status;
615
+ };
616
+ var MAINNET_INFO_URL = "https://api.hyperliquid.xyz/info";
617
+ var MAINNET_EXCHANGE_URL = "https://api.hyperliquid.xyz/exchange";
618
+ var TESTNET_INFO_URL = "https://api-ui.hyperliquid-testnet.xyz/info";
619
+ var TESTNET_EXCHANGE_URL = "https://api-ui.hyperliquid-testnet.xyz/exchange";
620
+ var TESTNET_WS_URL = "wss://api-ui.hyperliquid-testnet.xyz/ws";
621
+ var MAINNET_WS_URL = "wss://api.hyperliquid.xyz/ws";
622
+ var ALL_DEXS = "ALL_DEXS";
623
+ function isUsdClassTransferRequired(abstraction) {
624
+ return abstraction === "default" || abstraction === "disabled" || abstraction === "dexAbstraction";
625
+ }
626
+ function outcomeCoin(outcomeId) {
627
+ return `@${outcomeId}`;
628
+ }
629
+ function sideCoin(outcomeId, sideIndex) {
630
+ return `#${outcomeId}${sideIndex}`;
631
+ }
632
+ function sideAssetId(outcomeId, sideIndex) {
633
+ return 1e8 + outcomeId * 10 + sideIndex;
634
+ }
635
+ function parseSideCoin(coin) {
636
+ if (!coin.startsWith("#") && !coin.startsWith("+")) return null;
637
+ const num = coin.slice(1);
638
+ if (num.length < 2) return null;
639
+ const sideIndex = parseInt(num.slice(-1), 10);
640
+ const outcomeId = parseInt(num.slice(0, -1), 10);
641
+ if (isNaN(sideIndex) || isNaN(outcomeId)) return null;
642
+ if (sideIndex > 1) return null;
643
+ return { outcomeId, sideIndex };
644
+ }
645
+ function parseOutcomeCoin(coin) {
646
+ if (!coin.startsWith("@")) return null;
647
+ const outcomeId = parseInt(coin.slice(1), 10);
648
+ if (isNaN(outcomeId)) return null;
649
+ return { outcomeId };
650
+ }
651
+ function coinOutcomeId(coin) {
652
+ if (coin.startsWith("#") || coin.startsWith("+")) {
653
+ const parsed = parseSideCoin(coin);
654
+ return parsed ? parsed.outcomeId : null;
655
+ }
656
+ if (coin.startsWith("@")) {
657
+ const parsed = parseOutcomeCoin(coin);
658
+ return parsed ? parsed.outcomeId : null;
659
+ }
660
+ return null;
661
+ }
662
+ function isOutcomeCoin(coin) {
663
+ return coin.startsWith("@") || coin.startsWith("#") || coin.startsWith("+");
664
+ }
665
+ var HIP4Client = class {
666
+ infoUrl;
667
+ exchangeUrl;
668
+ wsUrl;
669
+ testnet;
670
+ log;
671
+ constructor(config = {}) {
672
+ this.testnet = config.testnet ?? true;
673
+ this.infoUrl = config.infoUrl ?? (this.testnet ? TESTNET_INFO_URL : MAINNET_INFO_URL);
674
+ this.exchangeUrl = config.exchangeUrl ?? (this.testnet ? TESTNET_EXCHANGE_URL : MAINNET_EXCHANGE_URL);
675
+ this.wsUrl = this.testnet ? TESTNET_WS_URL : MAINNET_WS_URL;
676
+ this.log = config.logger ?? (() => {
677
+ });
678
+ }
679
+ // -- Info endpoints -------------------------------------------------------
680
+ async fetchOutcomeMeta() {
681
+ return this.infoPost({ type: "outcomeMeta" });
682
+ }
683
+ async fetchSettledOutcome(outcome) {
684
+ return this.infoPost({
685
+ type: "settledOutcome",
686
+ outcome
687
+ });
688
+ }
689
+ async fetchL2Book(coin) {
690
+ return this.infoPost({ type: "l2Book", coin });
691
+ }
692
+ async fetchRecentTrades(coin) {
693
+ return this.infoPost({ type: "recentTrades", coin });
694
+ }
695
+ async fetchAllMids() {
696
+ return this.infoPost({ type: "allMids" });
697
+ }
698
+ async fetchCandleSnapshot(coin, interval, startTime, endTime) {
699
+ return this.infoPost({
700
+ type: "candleSnapshot",
701
+ req: { coin, interval, startTime, endTime }
702
+ });
703
+ }
704
+ async fetchClearinghouseState(user) {
705
+ return this.infoPost({
706
+ type: "clearinghouseState",
707
+ user
708
+ });
709
+ }
710
+ async fetchUserFills(user) {
711
+ return this.infoPost({ type: "userFills", user });
712
+ }
713
+ /** Spot meta + asset contexts (includes markPx / oracle price for each spot asset) */
714
+ async fetchSpotAssetCtx(spotIndex) {
715
+ const data = await this.infoPost({
716
+ type: "spotMetaAndAssetCtxs"
717
+ });
718
+ const ctx = data[1]?.[spotIndex];
719
+ if (!ctx?.markPx) return null;
720
+ return { markPx: ctx.markPx, midPx: ctx.midPx ?? "0" };
721
+ }
722
+ /** Spot balances - HIP-4 prediction market positions live here (USDH, outcome tokens) */
723
+ async fetchSpotClearinghouseState(user) {
724
+ return this.infoPost({
725
+ type: "spotClearinghouseState",
726
+ user
727
+ });
728
+ }
729
+ /** Trade fills with time range filtering */
730
+ async fetchUserFillsByTime(user, startTime, endTime) {
731
+ return this.infoPost({
732
+ type: "userFillsByTime",
733
+ user,
734
+ startTime,
735
+ endTime,
736
+ aggregateByTime: true,
737
+ reversed: true,
738
+ dex: ALL_DEXS
739
+ });
740
+ }
741
+ /** Approved extra agents for a user */
742
+ async fetchExtraAgents(user) {
743
+ return this.infoPost({ type: "extraAgents", user });
744
+ }
745
+ /**
746
+ * Check the maximum builder fee a user has approved for a given builder.
747
+ * Returns the approved fee in tenths of a basis point (e.g. 100 = 0.1%).
748
+ * Returns 0 if no approval exists.
749
+ */
750
+ async fetchMaxBuilderFee(user, builder) {
751
+ const result = await this.infoPost({
752
+ type: "maxBuilderFee",
753
+ user,
754
+ builder: builder.toLowerCase()
755
+ });
756
+ return result ? Number(result) : 0;
757
+ }
758
+ /**
759
+ * Fetch all approved builder addresses for a user.
760
+ * Hyperliquid limits each address to a maximum of 3 approved builders.
761
+ */
762
+ async fetchApprovedBuilders(user) {
763
+ return this.infoPost({ type: "approvedBuilders", user });
764
+ }
765
+ /**
766
+ * Fetch a user's referral state from Hyperliquid.
767
+ * Returns the referral state object, or null if no referrer is set.
768
+ */
769
+ async fetchReferralState(user) {
770
+ return this.infoPost({ type: "referral", user });
771
+ }
772
+ /**
773
+ * Fetch the role assigned to a user by Hyperliquid.
774
+ * `role === "missing"` indicates the wallet has never interacted with HL.
775
+ */
776
+ async fetchUserRole(user) {
777
+ return this.infoPost({ type: "userRole", user });
778
+ }
779
+ /**
780
+ * Fetch the user's effective fee schedule (post-discount).
781
+ * Spot rates (`userSpotCrossRate` / `userSpotAddRate`) are what apply to
782
+ * HIP-4 outcome closes; opens are 0-fee.
783
+ */
784
+ async fetchUserFees(user) {
785
+ return this.infoPost({ type: "userFees", user });
786
+ }
787
+ /**
788
+ * Fetch the user's account abstraction mode.
789
+ *
790
+ * Returns one of `"default" | "disabled" | "dexAbstraction" |
791
+ * "unifiedAccount" | "portfolioMargin"`. Standard accounts return
792
+ * `"default"` (or `"disabled"`); both keep spot and perp as separate
793
+ * silos and require a `usdClassTransfer` before `withdraw3`. Only
794
+ * `"unifiedAccount"` / `"portfolioMargin"` merge the balances and reject
795
+ * `usdClassTransfer`. Use {@link isUsdClassTransferRequired} to gate the
796
+ * spot↔perp transfer step in deposit/withdraw flows.
797
+ *
798
+ * Endpoint: `POST /info` body `{ type: "userAbstraction", user }`.
799
+ */
800
+ async fetchUserAbstraction(user) {
801
+ return this.infoPost({ type: "userAbstraction", user });
802
+ }
803
+ /** Frontend-formatted open orders */
804
+ async fetchFrontendOpenOrders(user) {
805
+ return this.infoPost({
806
+ type: "frontendOpenOrders",
807
+ user,
808
+ dex: ALL_DEXS
809
+ });
810
+ }
811
+ // -- Exchange endpoints ---------------------------------------------------
812
+ async placeOrder(action, nonce, signature, vaultAddress = null) {
813
+ return this.exchangePost({
814
+ action,
815
+ nonce,
816
+ signature,
817
+ vaultAddress
818
+ });
819
+ }
820
+ async cancelOrder(action, nonce, signature, vaultAddress = null) {
821
+ return this.exchangePost({
822
+ action,
823
+ nonce,
824
+ signature,
825
+ vaultAddress
826
+ });
827
+ }
828
+ async modifyOrder(action, nonce, signature, vaultAddress = null) {
829
+ return this.exchangePost({
830
+ action,
831
+ nonce,
832
+ signature,
833
+ vaultAddress
834
+ });
835
+ }
836
+ async batchModifyOrders(action, nonce, signature, vaultAddress = null) {
837
+ return this.exchangePost({
838
+ action,
839
+ nonce,
840
+ signature,
841
+ vaultAddress
842
+ });
843
+ }
844
+ /** Submit a user-signed action (withdraw, usdClassTransfer, etc.) */
845
+ async submitUserSignedAction(action, nonce, signature) {
846
+ return this.exchangePost({
847
+ action,
848
+ nonce,
849
+ signature
850
+ });
851
+ }
852
+ // -- WebSocket subscriptions -----------------------------------------------
853
+ ws = null;
854
+ wsPendingMessages = [];
855
+ /** Callbacks keyed by response channel (for message routing). */
856
+ wsCallbacks = /* @__PURE__ */ new Map();
857
+ /** Active subscribe messages as JSON strings (for reconnection). */
858
+ wsActiveSubs = /* @__PURE__ */ new Set();
859
+ /**
860
+ * Subscribe to a Hyperliquid WebSocket channel.
861
+ * Returns an unsubscribe function.
862
+ *
863
+ * @param options.responseChannel Channel name HL uses in response messages
864
+ * when it differs from `subscription.type` (e.g. subscribe as
865
+ * "activeAssetCtx" but receive on "activeSpotAssetCtx").
866
+ */
867
+ subscribe(subscription, onData, options) {
868
+ const responseChannel = options?.responseChannel ?? subscription.type;
869
+ this.ensureWs();
870
+ const subMsg = JSON.stringify({ method: "subscribe", subscription });
871
+ if (this.ws?.readyState === WebSocket.OPEN) {
872
+ this.ws.send(subMsg);
873
+ } else {
874
+ this.wsPendingMessages.push(subMsg);
875
+ }
876
+ this.wsActiveSubs.add(subMsg);
877
+ if (!this.wsCallbacks.has(responseChannel)) {
878
+ this.wsCallbacks.set(responseChannel, /* @__PURE__ */ new Set());
879
+ }
880
+ this.wsCallbacks.get(responseChannel)?.add(onData);
881
+ return () => {
882
+ if (this.ws?.readyState === WebSocket.OPEN) {
883
+ this.ws.send(JSON.stringify({ method: "unsubscribe", subscription }));
884
+ }
885
+ this.wsActiveSubs.delete(subMsg);
886
+ const cbs = this.wsCallbacks.get(responseChannel);
887
+ if (cbs) {
888
+ cbs.delete(onData);
889
+ if (cbs.size === 0) this.wsCallbacks.delete(responseChannel);
890
+ }
891
+ if (this.wsCallbacks.size === 0 && this.ws) {
892
+ this.ws.close();
893
+ this.ws = null;
894
+ }
895
+ };
896
+ }
897
+ /** Tear down WebSocket and clear all subscriptions. */
898
+ closeWs() {
899
+ if (this.wsReconnectTimer) {
900
+ clearTimeout(this.wsReconnectTimer);
901
+ this.wsReconnectTimer = null;
902
+ }
903
+ this.wsReconnectAttempts = 0;
904
+ if (this.ws) {
905
+ this.ws.close();
906
+ this.ws = null;
907
+ }
908
+ this.wsCallbacks.clear();
909
+ this.wsActiveSubs.clear();
910
+ this.wsPendingMessages = [];
911
+ }
912
+ ensureWs() {
913
+ if (this.ws) return;
914
+ const ws = new WebSocket(this.wsUrl);
915
+ this.ws = ws;
916
+ ws.onopen = () => {
917
+ this.wsReconnectAttempts = 0;
918
+ for (const msg of this.wsPendingMessages) {
919
+ if (ws.readyState === WebSocket.OPEN) {
920
+ ws.send(msg);
921
+ }
922
+ }
923
+ this.wsPendingMessages = [];
924
+ };
925
+ ws.onmessage = (event) => {
926
+ try {
927
+ const parsed = JSON.parse(event.data);
928
+ if (!parsed.channel) return;
929
+ const cbs = this.wsCallbacks.get(parsed.channel);
930
+ if (cbs) {
931
+ for (const cb of cbs) cb(parsed.data);
932
+ }
933
+ } catch {
934
+ }
935
+ };
936
+ ws.onclose = () => {
937
+ if (this.ws === ws) {
938
+ this.ws = null;
939
+ if (this.wsActiveSubs.size > 0) {
940
+ this.scheduleReconnect();
941
+ }
942
+ }
943
+ };
944
+ }
945
+ wsReconnectAttempts = 0;
946
+ wsReconnectTimer = null;
947
+ wsDestroyed = false;
948
+ scheduleReconnect() {
949
+ if (this.wsDestroyed) return;
950
+ if (this.wsReconnectAttempts >= 10) {
951
+ this.log("warn", "WS max reconnect attempts reached");
952
+ return;
953
+ }
954
+ const delay = Math.min(1e3 * 2 ** this.wsReconnectAttempts, 3e4);
955
+ this.wsReconnectAttempts++;
956
+ this.wsReconnectTimer = setTimeout(() => {
957
+ if (this.wsDestroyed || this.wsActiveSubs.size === 0) return;
958
+ this.ensureWs();
959
+ for (const msg of this.wsActiveSubs) {
960
+ if (this.ws?.readyState === WebSocket.OPEN) {
961
+ this.ws.send(msg);
962
+ } else {
963
+ this.wsPendingMessages.push(msg);
964
+ }
965
+ }
966
+ }, delay);
967
+ }
968
+ // -- Internal -------------------------------------------------------------
969
+ async infoPost(body) {
970
+ try {
971
+ return await this.doInfoPost(body);
972
+ } catch (err) {
973
+ if (err instanceof HLApiError && err.status >= 400 && err.status < 500)
974
+ throw err;
975
+ this.log("warn", "Info request failed, retrying once", {
976
+ type: body.type,
977
+ error: err instanceof Error ? err.message : String(err)
978
+ });
979
+ await new Promise((r) => setTimeout(r, 1e3));
980
+ return this.doInfoPost(body);
981
+ }
982
+ }
983
+ async doInfoPost(body) {
984
+ const res = await fetch(this.infoUrl, {
985
+ method: "POST",
986
+ headers: { "Content-Type": "application/json" },
987
+ body: JSON.stringify(body),
988
+ signal: AbortSignal.timeout(15e3)
989
+ });
990
+ if (!res.ok) {
991
+ throw new HLApiError(
992
+ res.status,
993
+ `HL info API responded with ${res.status}: ${res.statusText}`
994
+ );
995
+ }
996
+ try {
997
+ return await res.json();
998
+ } catch {
999
+ throw new HLApiError(res.status, "Exchange returned non-JSON response");
1000
+ }
1001
+ }
1002
+ async exchangePost(body) {
1003
+ const res = await fetch(this.exchangeUrl, {
1004
+ method: "POST",
1005
+ headers: { "Content-Type": "application/json" },
1006
+ body: JSON.stringify(body),
1007
+ signal: AbortSignal.timeout(15e3)
1008
+ });
1009
+ if (!res.ok) {
1010
+ throw new HLApiError(
1011
+ res.status,
1012
+ `HL exchange API responded with ${res.status}: ${res.statusText}`
1013
+ );
1014
+ }
1015
+ try {
1016
+ return await res.json();
1017
+ } catch {
1018
+ throw new HLApiError(res.status, "Exchange returned non-JSON response");
1019
+ }
1020
+ }
1021
+ };
1022
+
1023
+ // src/adapter/hyperliquid/account.ts
1024
+ var POLL_INTERVAL_MS = 1e4;
1025
+ function mapSpotBalance(bal, allMids, nameMap, resolveSideNames) {
1026
+ const coin = bal.coin;
1027
+ if (!isOutcomeCoin(coin)) return null;
1028
+ const total = toDecimal(bal.total);
1029
+ if (total.isZero()) return null;
1030
+ const outcomeId = coinOutcomeId(coin);
1031
+ const marketId = outcomeId !== null ? String(outcomeId) : coin;
1032
+ const outcome = coin;
1033
+ const parsed = parseSideCoin(coin);
1034
+ let outcomeName;
1035
+ if (parsed && resolveSideNames) {
1036
+ const names2 = resolveSideNames(parsed.outcomeId);
1037
+ outcomeName = names2 ? names2[parsed.sideIndex] : `Side ${parsed.sideIndex}`;
1038
+ } else {
1039
+ outcomeName = parsed ? `Side ${parsed.sideIndex}` : coin;
1040
+ }
1041
+ toDecimal(bal.entryNtl);
1042
+ const avgCost = isZero(bal.total) ? "0" : div(bal.entryNtl, bal.total);
1043
+ const mid = allMids[coin];
1044
+ const currentPrice = mid ?? "0";
1045
+ const unrealizedPnl = mul(sub(currentPrice, avgCost), bal.total);
1046
+ const potentialPayout = bal.total;
1047
+ const names = nameMap.get(marketId);
1048
+ return {
1049
+ marketId,
1050
+ eventTitle: names?.eventTitle ?? "",
1051
+ marketQuestion: names?.marketQuestion ?? "",
1052
+ outcome,
1053
+ outcomeName,
1054
+ shares: fixed(bal.total, 6),
1055
+ avgCost: fixed(avgCost, 6),
1056
+ currentPrice,
1057
+ unrealizedPnl: fixed(unrealizedPnl, 6),
1058
+ potentialPayout: fixed(potentialPayout, 6),
1059
+ eventStatus: "active"
1060
+ };
1061
+ }
1062
+ function mapFill(raw) {
1063
+ if (!isOutcomeCoin(raw.coin)) return null;
1064
+ const outcomeId = coinOutcomeId(raw.coin);
1065
+ const marketId = outcomeId !== null ? String(outcomeId) : raw.coin;
1066
+ return {
1067
+ id: String(raw.tid),
1068
+ type: "trade",
1069
+ marketId,
1070
+ outcome: raw.coin,
1071
+ side: raw.side === "B" ? "buy" : "sell",
1072
+ price: raw.px,
1073
+ size: raw.sz,
1074
+ timestamp: raw.time
1075
+ };
1076
+ }
1077
+ var HIP4AccountAdapter = class {
1078
+ constructor(client, events, resolveSideNames) {
1079
+ this.client = client;
1080
+ this.events = events;
1081
+ this.resolveSideNames = resolveSideNames;
1082
+ }
1083
+ client;
1084
+ events;
1085
+ resolveSideNames;
1086
+ async fetchPositions(address) {
1087
+ if (this.events?.ensureSideNames) {
1088
+ await this.events.ensureSideNames();
1089
+ }
1090
+ const [state, allMids, eventList] = await Promise.all([
1091
+ this.client.fetchSpotClearinghouseState(address),
1092
+ this.client.fetchAllMids(),
1093
+ this.events?.fetchEvents({ limit: 200 }).catch(() => []) ?? Promise.resolve([])
1094
+ ]);
1095
+ const nameMap = /* @__PURE__ */ new Map();
1096
+ for (const event of eventList) {
1097
+ for (const market of event.markets) {
1098
+ nameMap.set(market.id, { eventTitle: event.title, marketQuestion: market.question });
1099
+ }
1100
+ }
1101
+ const positions = [];
1102
+ for (const bal of state.balances) {
1103
+ if (!isOutcomeCoin(bal.coin)) continue;
1104
+ if (isZero(bal.total)) continue;
1105
+ const mapped = mapSpotBalance(bal, allMids, nameMap, this.resolveSideNames);
1106
+ if (mapped) positions.push(mapped);
1107
+ }
1108
+ return positions;
1109
+ }
1110
+ async fetchActivity(address) {
1111
+ const now = Date.now();
1112
+ const thirtyDaysMs = 30 * 24 * 60 * 60 * 1e3;
1113
+ const startTime = now - thirtyDaysMs;
1114
+ const fills = await this.client.fetchUserFillsByTime(
1115
+ address,
1116
+ startTime,
1117
+ now
1118
+ );
1119
+ const activities = [];
1120
+ for (const fill of fills) {
1121
+ const mapped = mapFill(fill);
1122
+ if (mapped) activities.push(mapped);
1123
+ }
1124
+ return activities;
1125
+ }
1126
+ async fetchBalance(address) {
1127
+ const state = await this.client.fetchSpotClearinghouseState(address);
1128
+ return state.balances.map((b) => ({
1129
+ coin: b.coin,
1130
+ total: b.total,
1131
+ hold: b.hold
1132
+ }));
1133
+ }
1134
+ async fetchOpenOrders(address) {
1135
+ const orders = await this.client.fetchFrontendOpenOrders(address);
1136
+ return orders.map((o) => ({
1137
+ coin: o.coin,
1138
+ side: o.side,
1139
+ limitPx: o.limitPx,
1140
+ sz: o.sz,
1141
+ oid: o.oid,
1142
+ timestamp: o.timestamp
1143
+ }));
1144
+ }
1145
+ subscribePositions(address, onData) {
1146
+ let active = true;
1147
+ const poll = async () => {
1148
+ while (active) {
1149
+ try {
1150
+ const positions = await this.fetchPositions(address);
1151
+ if (active) onData(positions);
1152
+ } catch {
1153
+ }
1154
+ if (active) {
1155
+ await sleep(POLL_INTERVAL_MS);
1156
+ }
1157
+ }
1158
+ };
1159
+ void poll();
1160
+ return () => {
1161
+ active = false;
1162
+ };
1163
+ }
1164
+ };
1165
+ function sleep(ms) {
1166
+ return new Promise((resolve) => setTimeout(resolve, ms));
1167
+ }
1168
+
1169
+ // src/adapter/hyperliquid/auth.ts
1170
+ function isEthersSigner(val) {
1171
+ return typeof val.getAddress === "function" && typeof val.signTypedData === "function";
1172
+ }
1173
+ function isViemAccount(val) {
1174
+ return typeof val.address === "string" && typeof val.signTypedData === "function";
1175
+ }
1176
+ function wrapViemAccount(account) {
1177
+ return {
1178
+ getAddress: () => account.address,
1179
+ signTypedData: (domain, types, value) => account.signTypedData({
1180
+ domain,
1181
+ types: { ...types },
1182
+ primaryType: Object.keys(types)[0],
1183
+ message: value
1184
+ })
1185
+ };
1186
+ }
1187
+ var HIP4Auth = class {
1188
+ state = { status: "disconnected" };
1189
+ signer = null;
1190
+ async initAuth(walletAddress, signer) {
1191
+ if (typeof signer !== "object" || signer === null) {
1192
+ this.state = { status: "disconnected" };
1193
+ throw new Error(
1194
+ "HIP-4 auth requires a signer. Pass a viem PrivateKeyAccount, ethers Wallet, or compatible signer."
1195
+ );
1196
+ }
1197
+ const obj = signer;
1198
+ let resolved;
1199
+ if (isEthersSigner(obj)) {
1200
+ resolved = signer;
1201
+ } else if (isViemAccount(obj)) {
1202
+ resolved = wrapViemAccount(obj);
1203
+ } else {
1204
+ this.state = { status: "disconnected" };
1205
+ throw new Error(
1206
+ "HIP-4 auth requires a signer with signTypedData(). Pass a viem PrivateKeyAccount, ethers Wallet, or compatible signer."
1207
+ );
1208
+ }
1209
+ this.state = { status: "pending_approval", address: walletAddress };
1210
+ this.signer = resolved;
1211
+ this.state = {
1212
+ status: "ready",
1213
+ address: walletAddress
1214
+ };
1215
+ return this.state;
1216
+ }
1217
+ getAuthStatus() {
1218
+ return this.state;
1219
+ }
1220
+ clearAuth() {
1221
+ this.signer = null;
1222
+ this.state = { status: "disconnected" };
1223
+ }
1224
+ /** Internal - used by the trading adapter to get the active signer */
1225
+ getSigner() {
1226
+ return this.signer;
1227
+ }
1228
+ };
1229
+
1230
+ // src/adapter/hyperliquid/market-discovery.ts
1231
+ var PREDICTION_ASSET_OFFSET = 1e8;
1232
+ function parseDescription(desc) {
1233
+ if (!desc || !desc.includes("|")) return null;
1234
+ const fields = {};
1235
+ for (const pair of desc.split("|")) {
1236
+ const idx = pair.indexOf(":");
1237
+ if (idx > 0) {
1238
+ fields[pair.slice(0, idx)] = pair.slice(idx + 1);
1239
+ }
1240
+ }
1241
+ if (fields.class !== "priceBinary") return null;
1242
+ if (!fields.underlying || !fields.expiry || !fields.targetPrice || !fields.period) {
1243
+ return null;
1244
+ }
1245
+ return {
1246
+ class: "priceBinary",
1247
+ underlying: fields.underlying,
1248
+ expiry: parseExpiry(fields.expiry),
1249
+ targetPrice: parseFloat(fields.targetPrice),
1250
+ period: fields.period
1251
+ };
1252
+ }
1253
+ function parsePriceBucketDescription(desc) {
1254
+ if (!desc || !desc.includes("|")) return null;
1255
+ const fields = {};
1256
+ for (const pair of desc.split("|")) {
1257
+ const idx = pair.indexOf(":");
1258
+ if (idx > 0) {
1259
+ fields[pair.slice(0, idx)] = pair.slice(idx + 1);
1260
+ }
1261
+ }
1262
+ if (fields.class !== "priceBucket") return null;
1263
+ if (!fields.underlying || !fields.expiry || !fields.priceThresholds || !fields.period) {
1264
+ return null;
1265
+ }
1266
+ const priceThresholds = fields.priceThresholds.split(",").map((s) => parseFloat(s.trim())).filter((n) => Number.isFinite(n)).sort((a, b) => a - b);
1267
+ if (priceThresholds.length === 0) return null;
1268
+ return {
1269
+ class: "priceBucket",
1270
+ underlying: fields.underlying,
1271
+ expiry: parseExpiry(fields.expiry),
1272
+ priceThresholds,
1273
+ period: fields.period
1274
+ };
1275
+ }
1276
+ function parseExpiry(s) {
1277
+ const year = parseInt(s.slice(0, 4));
1278
+ const month = parseInt(s.slice(4, 6)) - 1;
1279
+ const day = parseInt(s.slice(6, 8));
1280
+ const hour = parseInt(s.slice(9, 11));
1281
+ const min2 = parseInt(s.slice(11, 13));
1282
+ return new Date(Date.UTC(year, month, day, hour, min2));
1283
+ }
1284
+ function discoverPriceBinaryMarkets(meta, mids) {
1285
+ const markets = [];
1286
+ for (const outcome of meta.outcomes) {
1287
+ const parsed = parseDescription(outcome.description);
1288
+ if (!parsed) continue;
1289
+ if (!mids[parsed.underlying]) continue;
1290
+ if (parsed.expiry.getTime() <= Date.now()) continue;
1291
+ const yesCoinNum = outcome.outcome * 10;
1292
+ const noCoinNum = outcome.outcome * 10 + 1;
1293
+ markets.push({
1294
+ outcomeId: outcome.outcome,
1295
+ underlying: parsed.underlying,
1296
+ targetPrice: parsed.targetPrice,
1297
+ expiry: parsed.expiry,
1298
+ period: parsed.period,
1299
+ yesCoinNum,
1300
+ noCoinNum,
1301
+ yesCoin: `#${yesCoinNum}`,
1302
+ noCoin: `#${noCoinNum}`,
1303
+ yesAsset: PREDICTION_ASSET_OFFSET + yesCoinNum,
1304
+ noAsset: PREDICTION_ASSET_OFFSET + noCoinNum
1305
+ });
1306
+ }
1307
+ return markets;
1308
+ }
1309
+ function timeToExpiry(market) {
1310
+ return (market.expiry.getTime() - Date.now()) / 6e4;
1311
+ }
1312
+ function periodMinutes(period) {
1313
+ const match = period.match(/^(\d+)(m|h|d)$/);
1314
+ if (!match) return 15;
1315
+ const value = parseInt(match[1]);
1316
+ switch (match[2]) {
1317
+ case "m":
1318
+ return value;
1319
+ case "h":
1320
+ return value * 60;
1321
+ case "d":
1322
+ return value * 1440;
1323
+ default:
1324
+ return 15;
1325
+ }
1326
+ }
1327
+ function formatMarketLabel(market) {
1328
+ return `${market.underlying}-${market.period}`;
1329
+ }
1330
+
1331
+ // src/adapter/hyperliquid/market-classification.ts
1332
+ var PREDICTION_ASSET_OFFSET2 = 1e8;
1333
+ function buildSides(outcome) {
1334
+ return [
1335
+ {
1336
+ name: outcome.sideSpecs[0]?.name ?? "Side 0",
1337
+ coinNum: outcome.outcome * 10,
1338
+ coin: `#${outcome.outcome * 10}`,
1339
+ asset: PREDICTION_ASSET_OFFSET2 + outcome.outcome * 10
1340
+ },
1341
+ {
1342
+ name: outcome.sideSpecs[1]?.name ?? "Side 1",
1343
+ coinNum: outcome.outcome * 10 + 1,
1344
+ coin: `#${outcome.outcome * 10 + 1}`,
1345
+ asset: PREDICTION_ASSET_OFFSET2 + outcome.outcome * 10 + 1
1346
+ }
1347
+ ];
1348
+ }
1349
+ function buildQuestionIndex(questions) {
1350
+ const byOutcome = /* @__PURE__ */ new Map();
1351
+ for (const q of questions) {
1352
+ const bucket = parsePriceBucketDescription(q.description);
1353
+ q.namedOutcomes.forEach((id, bucketIndex) => {
1354
+ byOutcome.set(id, {
1355
+ question: q,
1356
+ isFallback: false,
1357
+ bucketIndex,
1358
+ bucket
1359
+ });
1360
+ });
1361
+ byOutcome.set(q.fallbackOutcome, {
1362
+ question: q,
1363
+ isFallback: true,
1364
+ bucketIndex: -1,
1365
+ bucket
1366
+ });
1367
+ }
1368
+ return { byOutcome };
1369
+ }
1370
+ function getPriceBucketBounds(thresholds, bucketIndex) {
1371
+ if (bucketIndex < 0) return { lowerBound: null, upperBound: null };
1372
+ const lowerBound = bucketIndex === 0 ? null : thresholds[bucketIndex - 1] ?? null;
1373
+ const upperBound = bucketIndex >= thresholds.length ? null : thresholds[bucketIndex] ?? null;
1374
+ return { lowerBound, upperBound };
1375
+ }
1376
+ function buildPriceBucketMarket(outcome, sides, entry, bucket) {
1377
+ const { lowerBound, upperBound } = getPriceBucketBounds(
1378
+ bucket.priceThresholds,
1379
+ entry.bucketIndex
1380
+ );
1381
+ return {
1382
+ type: "priceBucket",
1383
+ outcomeId: outcome.outcome,
1384
+ name: outcome.name,
1385
+ description: outcome.description,
1386
+ sides,
1387
+ raw: outcome,
1388
+ underlying: bucket.underlying,
1389
+ expiry: bucket.expiry,
1390
+ priceThresholds: bucket.priceThresholds,
1391
+ period: bucket.period,
1392
+ questionId: entry.question.question,
1393
+ questionName: entry.question.name,
1394
+ questionDescription: entry.question.description,
1395
+ isFallback: entry.isFallback,
1396
+ bucketIndex: entry.bucketIndex,
1397
+ lowerBound,
1398
+ upperBound,
1399
+ rawQuestion: entry.question
1400
+ };
1401
+ }
1402
+ function classifyOutcome(outcome, questions, precomputedIndex) {
1403
+ const sides = buildSides(outcome);
1404
+ const index = precomputedIndex ?? buildQuestionIndex(questions);
1405
+ const parsed = parseDescription(outcome.description);
1406
+ if (parsed) {
1407
+ const market2 = {
1408
+ type: "defaultBinary",
1409
+ outcomeId: outcome.outcome,
1410
+ name: `${parsed.underlying} > $${parsed.targetPrice} (${parsed.period})`,
1411
+ description: `Will ${parsed.underlying} be above $${parsed.targetPrice} by expiry?`,
1412
+ sides,
1413
+ raw: outcome,
1414
+ underlying: parsed.underlying,
1415
+ targetPrice: parsed.targetPrice,
1416
+ expiry: parsed.expiry,
1417
+ period: parsed.period
1418
+ };
1419
+ return market2;
1420
+ }
1421
+ const questionEntry = index.byOutcome.get(outcome.outcome);
1422
+ if (questionEntry) {
1423
+ if (questionEntry.bucket) {
1424
+ return buildPriceBucketMarket(
1425
+ outcome,
1426
+ sides,
1427
+ questionEntry,
1428
+ questionEntry.bucket
1429
+ );
1430
+ }
1431
+ const market2 = {
1432
+ type: "multiOutcome",
1433
+ outcomeId: outcome.outcome,
1434
+ name: outcome.name,
1435
+ description: outcome.description,
1436
+ sides,
1437
+ raw: outcome,
1438
+ questionId: questionEntry.question.question,
1439
+ questionName: questionEntry.question.name,
1440
+ questionDescription: questionEntry.question.description,
1441
+ isFallback: questionEntry.isFallback,
1442
+ rawQuestion: questionEntry.question
1443
+ };
1444
+ return market2;
1445
+ }
1446
+ const market = {
1447
+ type: "labelledBinary",
1448
+ outcomeId: outcome.outcome,
1449
+ name: outcome.name,
1450
+ description: outcome.description,
1451
+ sides,
1452
+ raw: outcome
1453
+ };
1454
+ return market;
1455
+ }
1456
+ function classifyAllOutcomes(outcomes, questions) {
1457
+ const index = buildQuestionIndex(questions);
1458
+ return outcomes.map((outcome) => {
1459
+ const sides = buildSides(outcome);
1460
+ const parsed = parseDescription(outcome.description);
1461
+ if (parsed) {
1462
+ return {
1463
+ type: "defaultBinary",
1464
+ outcomeId: outcome.outcome,
1465
+ name: `${parsed.underlying} > $${parsed.targetPrice} (${parsed.period})`,
1466
+ description: `Will ${parsed.underlying} be above $${parsed.targetPrice} by expiry?`,
1467
+ sides,
1468
+ raw: outcome,
1469
+ underlying: parsed.underlying,
1470
+ targetPrice: parsed.targetPrice,
1471
+ expiry: parsed.expiry,
1472
+ period: parsed.period
1473
+ };
1474
+ }
1475
+ const questionEntry = index.byOutcome.get(outcome.outcome);
1476
+ if (questionEntry) {
1477
+ if (questionEntry.bucket) {
1478
+ return buildPriceBucketMarket(
1479
+ outcome,
1480
+ sides,
1481
+ questionEntry,
1482
+ questionEntry.bucket
1483
+ );
1484
+ }
1485
+ return {
1486
+ type: "multiOutcome",
1487
+ outcomeId: outcome.outcome,
1488
+ name: outcome.name,
1489
+ description: outcome.description,
1490
+ sides,
1491
+ raw: outcome,
1492
+ questionId: questionEntry.question.question,
1493
+ questionName: questionEntry.question.name,
1494
+ questionDescription: questionEntry.question.description,
1495
+ isFallback: questionEntry.isFallback,
1496
+ rawQuestion: questionEntry.question
1497
+ };
1498
+ }
1499
+ return {
1500
+ type: "labelledBinary",
1501
+ outcomeId: outcome.outcome,
1502
+ name: outcome.name,
1503
+ description: outcome.description,
1504
+ sides,
1505
+ raw: outcome
1506
+ };
1507
+ });
1508
+ }
1509
+
1510
+ // src/adapter/hyperliquid/events.ts
1511
+ var CATEGORIES = [
1512
+ { id: "custom", name: "Custom", slug: "custom" },
1513
+ { id: "recurring", name: "Recurring", slug: "recurring" }
1514
+ ];
1515
+ function isRecurring(outcome) {
1516
+ return outcome.name === "Recurring";
1517
+ }
1518
+ function parseRecurringDescription(desc) {
1519
+ if (!desc.includes("|")) return null;
1520
+ const result = {};
1521
+ for (const segment of desc.split("|")) {
1522
+ const [key, ...rest] = segment.split(":");
1523
+ if (key && rest.length > 0) {
1524
+ result[key] = rest.join(":");
1525
+ }
1526
+ }
1527
+ return Object.keys(result).length > 0 ? result : null;
1528
+ }
1529
+ function recurringTitle(outcome) {
1530
+ const parsed = parseRecurringDescription(outcome.description);
1531
+ if (!parsed) return `Outcome #${outcome.outcome}`;
1532
+ const underlying = parsed.underlying ?? "???";
1533
+ const target = parsed.targetPrice ?? "???";
1534
+ const period = parsed.period ?? "";
1535
+ if (parsed.class === "priceBinary") {
1536
+ return `${underlying} > $${target} (${period})`;
1537
+ }
1538
+ return `${underlying} ${parsed.class ?? "outcome"} (${period})`;
1539
+ }
1540
+ function recurringDescription(outcome) {
1541
+ const parsed = parseRecurringDescription(outcome.description);
1542
+ if (!parsed) return outcome.description;
1543
+ const expiry = parsed.expiry ?? "unknown";
1544
+ return `Will ${parsed.underlying ?? "asset"} be above $${parsed.targetPrice ?? "?"} by ${expiry}?`;
1545
+ }
1546
+ function mapOutcomeToMarket(outcome, eventId) {
1547
+ const outcomes = outcome.sideSpecs.map(
1548
+ (spec, sideIndex) => ({
1549
+ name: spec.name,
1550
+ tokenId: sideCoin(outcome.outcome, sideIndex),
1551
+ price: "0"
1552
+ })
1553
+ );
1554
+ return {
1555
+ id: String(outcome.outcome),
1556
+ eventId,
1557
+ question: isRecurring(outcome) ? recurringDescription(outcome) : outcome.name,
1558
+ outcomes,
1559
+ volume: "0",
1560
+ liquidity: "0"
1561
+ };
1562
+ }
1563
+ function mapQuestionToEvent(question, outcomeMap) {
1564
+ const eventId = `q${question.question}`;
1565
+ const allOutcomeIds = [
1566
+ ...question.namedOutcomes,
1567
+ question.fallbackOutcome
1568
+ ].filter((id) => outcomeMap.has(id));
1569
+ const markets = allOutcomeIds.map((id) => outcomeMap.get(id)).map((o) => mapOutcomeToMarket(o, eventId));
1570
+ const settled = new Set(question.settledNamedOutcomes);
1571
+ const hasUnsettled = question.namedOutcomes.some((id) => !settled.has(id));
1572
+ return {
1573
+ id: eventId,
1574
+ title: question.name,
1575
+ description: question.description,
1576
+ category: "custom",
1577
+ markets,
1578
+ totalVolume: "0",
1579
+ endDate: "",
1580
+ status: hasUnsettled ? "active" : "resolved"
1581
+ };
1582
+ }
1583
+ function mapStandaloneOutcomeToEvent(outcome) {
1584
+ const eventId = `o${outcome.outcome}`;
1585
+ const recurring = isRecurring(outcome);
1586
+ return {
1587
+ id: eventId,
1588
+ title: recurring ? recurringTitle(outcome) : outcome.name,
1589
+ description: recurring ? recurringDescription(outcome) : outcome.description,
1590
+ category: recurring ? "recurring" : "custom",
1591
+ markets: [mapOutcomeToMarket(outcome, eventId)],
1592
+ totalVolume: "0",
1593
+ endDate: recurring ? parseRecurringDescription(outcome.description)?.expiry ?? "" : "",
1594
+ status: "active"
1595
+ };
1596
+ }
1597
+ var HIP4EventAdapter = class _HIP4EventAdapter {
1598
+ constructor(client) {
1599
+ this.client = client;
1600
+ }
1601
+ client;
1602
+ cache = null;
1603
+ metaCache = null;
1604
+ static CACHE_TTL_MS = 3e4;
1605
+ /** Side names from outcomeMeta. Populated once, never cleared (sideSpecs don't change). */
1606
+ sideNames = null;
1607
+ /** Returns a resolver function that looks up side names by outcome ID. */
1608
+ getSideNameResolver() {
1609
+ return (outcomeId) => this.sideNames?.get(outcomeId) ?? null;
1610
+ }
1611
+ /** Ensure sideNames are loaded. Call before using the resolver if data may not be cached yet. */
1612
+ async ensureSideNames() {
1613
+ if (this.sideNames) return;
1614
+ const meta = await this.client.fetchOutcomeMeta();
1615
+ this.populateSideNames(meta);
1616
+ }
1617
+ /**
1618
+ * Subscribe to live outcome-meta updates (HIP-4 catalog changes).
1619
+ *
1620
+ * Each frame is an array of one or more updates. The handler is invoked
1621
+ * once per update with the discriminated payload. Internally we also:
1622
+ * - extend `sideNames` for newly created outcomes (so `getSideNameResolver`
1623
+ * returns real names instead of falling back to "Side 0/1")
1624
+ * - invalidate the events + markets caches so the next read refetches
1625
+ * and reflects the change
1626
+ *
1627
+ * Returns an unsubscribe callback.
1628
+ */
1629
+ subscribeOutcomeMetaUpdates(onData) {
1630
+ return this.client.subscribe(
1631
+ { type: "outcomeMetaUpdates" },
1632
+ (raw) => {
1633
+ if (!Array.isArray(raw)) return;
1634
+ const updates = raw;
1635
+ for (const update of updates) {
1636
+ this.applyMetaUpdate(update);
1637
+ onData(update);
1638
+ }
1639
+ }
1640
+ );
1641
+ }
1642
+ applyMetaUpdate(update) {
1643
+ if ("outcomeCreated" in update) {
1644
+ const spec = update.outcomeCreated;
1645
+ if (this.sideNames && spec.sideSpecs.length >= 2) {
1646
+ this.sideNames.set(spec.outcome, [
1647
+ spec.sideSpecs[0].name,
1648
+ spec.sideSpecs[1].name
1649
+ ]);
1650
+ }
1651
+ }
1652
+ this.cache = null;
1653
+ this.metaCache = null;
1654
+ }
1655
+ populateSideNames(meta) {
1656
+ if (this.sideNames) return;
1657
+ this.sideNames = /* @__PURE__ */ new Map();
1658
+ for (const o of meta.outcomes) {
1659
+ if (o.sideSpecs.length >= 2) {
1660
+ this.sideNames.set(o.outcome, [
1661
+ o.sideSpecs[0].name,
1662
+ o.sideSpecs[1].name
1663
+ ]);
1664
+ }
1665
+ }
1666
+ }
1667
+ async fetchEvents(params = {}) {
1668
+ let events = await this.loadEvents();
1669
+ if (params.category && params.category !== "all") {
1670
+ events = events.filter((e) => e.category === params.category);
1671
+ }
1672
+ if (params.active) {
1673
+ events = events.filter((e) => e.status === "active");
1674
+ }
1675
+ if (params.query) {
1676
+ const q = params.query.toLowerCase();
1677
+ events = events.filter(
1678
+ (e) => e.title.toLowerCase().includes(q) || e.description.toLowerCase().includes(q)
1679
+ );
1680
+ }
1681
+ const offset = params.offset ?? 0;
1682
+ const limit = params.limit ?? 50;
1683
+ return events.slice(offset, offset + limit);
1684
+ }
1685
+ async fetchEvent(eventId) {
1686
+ const events = await this.loadEvents();
1687
+ const event = events.find((e) => e.id === eventId);
1688
+ if (!event) {
1689
+ throw new Error(`HIP-4 event not found: ${eventId}`);
1690
+ }
1691
+ return event;
1692
+ }
1693
+ async fetchCategories() {
1694
+ return CATEGORIES;
1695
+ }
1696
+ async fetchMarkets(params = {}) {
1697
+ const allMarkets = await this.loadMarkets();
1698
+ let filtered = params.type ? allMarkets.filter((m) => m.type === params.type) : allMarkets;
1699
+ if (params.groupBy === "type") {
1700
+ const grouped = {};
1701
+ for (const m of filtered) {
1702
+ (grouped[m.type] ??= []).push(m);
1703
+ }
1704
+ return grouped;
1705
+ }
1706
+ if (params.groupBy === "question") {
1707
+ const grouped = {};
1708
+ for (const m of filtered) {
1709
+ const key = m.type === "multiOutcome" ? String(m.questionId) : "standalone";
1710
+ (grouped[key] ??= []).push(m);
1711
+ }
1712
+ return grouped;
1713
+ }
1714
+ const offset = params.offset ?? 0;
1715
+ const limit = params.limit ?? filtered.length;
1716
+ return filtered.slice(offset, offset + limit);
1717
+ }
1718
+ async loadMarkets() {
1719
+ const now = Date.now();
1720
+ if (this.metaCache && now - this.metaCache.timestamp < _HIP4EventAdapter.CACHE_TTL_MS) {
1721
+ return this.metaCache.markets;
1722
+ }
1723
+ const [meta, mids] = await Promise.all([
1724
+ this.client.fetchOutcomeMeta(),
1725
+ this.client.fetchAllMids().catch(() => ({}))
1726
+ ]);
1727
+ this.populateSideNames(meta);
1728
+ const markets = classifyAllOutcomes(meta.outcomes, meta.questions);
1729
+ this.metaCache = { meta, mids, markets, timestamp: now };
1730
+ return markets;
1731
+ }
1732
+ // -------------------------------------------------------------------------
1733
+ // loadEvents - legacy PredictionEvent API
1734
+ // -------------------------------------------------------------------------
1735
+ async loadEvents() {
1736
+ const now = Date.now();
1737
+ if (this.cache && now - this.cache.timestamp < _HIP4EventAdapter.CACHE_TTL_MS) {
1738
+ return this.cache.events;
1739
+ }
1740
+ const [meta, mids] = await Promise.all([
1741
+ this.client.fetchOutcomeMeta(),
1742
+ this.client.fetchAllMids().catch(() => ({}))
1743
+ ]);
1744
+ this.populateSideNames(meta);
1745
+ const events = buildEventsFromMeta(meta);
1746
+ for (const event of events) {
1747
+ for (const market of event.markets) {
1748
+ for (const outcome of market.outcomes) {
1749
+ const mid = mids[outcome.tokenId];
1750
+ if (mid) {
1751
+ outcome.price = mid;
1752
+ }
1753
+ }
1754
+ }
1755
+ }
1756
+ this.cache = { events, timestamp: now };
1757
+ return events;
1758
+ }
1759
+ };
1760
+ function buildEventsFromMeta(meta) {
1761
+ const outcomeMap = /* @__PURE__ */ new Map();
1762
+ for (const o of meta.outcomes) {
1763
+ outcomeMap.set(o.outcome, o);
1764
+ }
1765
+ const claimedOutcomes = /* @__PURE__ */ new Set();
1766
+ const events = [];
1767
+ for (const q of meta.questions) {
1768
+ for (const id of q.namedOutcomes) claimedOutcomes.add(id);
1769
+ claimedOutcomes.add(q.fallbackOutcome);
1770
+ events.push(mapQuestionToEvent(q, outcomeMap));
1771
+ }
1772
+ for (const o of meta.outcomes) {
1773
+ if (!claimedOutcomes.has(o.outcome)) {
1774
+ events.push(mapStandaloneOutcomeToEvent(o));
1775
+ }
1776
+ }
1777
+ return events;
1778
+ }
1779
+
1780
+ // src/adapter/hyperliquid/market-data.ts
1781
+ function mapBook(raw, marketId) {
1782
+ const [bids, asks] = raw.levels;
1783
+ return {
1784
+ marketId,
1785
+ bids: bids.map((l) => ({ price: l.px, size: l.sz })),
1786
+ asks: asks.map((l) => ({ price: l.px, size: l.sz })),
1787
+ timestamp: raw.time
1788
+ };
1789
+ }
1790
+ function mapWsBook(raw, marketId) {
1791
+ const [bids, asks] = raw.levels;
1792
+ return {
1793
+ marketId,
1794
+ bids: bids.map((l) => ({ price: l.px, size: l.sz })),
1795
+ asks: asks.map((l) => ({ price: l.px, size: l.sz })),
1796
+ timestamp: raw.time
1797
+ };
1798
+ }
1799
+ function mapTrade(raw, marketId) {
1800
+ return {
1801
+ id: String(raw.tid),
1802
+ marketId,
1803
+ outcome: raw.coin,
1804
+ side: raw.side === "B" ? "buy" : "sell",
1805
+ price: raw.px,
1806
+ size: raw.sz,
1807
+ timestamp: raw.time
1808
+ };
1809
+ }
1810
+ var HIP4MarketDataAdapter = class _HIP4MarketDataAdapter {
1811
+ constructor(client, resolveSideNames, ensureSideNames) {
1812
+ this.client = client;
1813
+ this.resolveSideNames = resolveSideNames ?? (() => null);
1814
+ this.ensureSideNames = ensureSideNames;
1815
+ }
1816
+ client;
1817
+ midsCache = null;
1818
+ static MIDS_CACHE_TTL = 5e3;
1819
+ resolveSideNames;
1820
+ ensureSideNames;
1821
+ async fetchOrderBook(marketId, sideIndex = 0) {
1822
+ const outcomeId = parseInt(marketId, 10);
1823
+ const coin = sideCoin(outcomeId, sideIndex);
1824
+ const raw = await this.client.fetchL2Book(coin);
1825
+ return mapBook(raw, marketId);
1826
+ }
1827
+ sideNamesFor(marketId) {
1828
+ const outcomeId = parseInt(marketId, 10);
1829
+ return this.resolveSideNames(outcomeId) ?? ["Side 0", "Side 1"];
1830
+ }
1831
+ async fetchPrice(marketId) {
1832
+ await this.ensureSideNames?.();
1833
+ const outcomeId = parseInt(marketId, 10);
1834
+ const mids = await this.getMids();
1835
+ const [name0, name1] = this.sideNamesFor(marketId);
1836
+ const side0Coin = sideCoin(outcomeId, 0);
1837
+ const side1Coin = sideCoin(outcomeId, 1);
1838
+ const side0Mid = mids[side0Coin] ?? "0";
1839
+ const side1Mid = mids[side1Coin] ?? "0";
1840
+ return {
1841
+ marketId,
1842
+ outcomes: [
1843
+ { name: name0, price: side0Mid, midpoint: side0Mid },
1844
+ { name: name1, price: side1Mid, midpoint: side1Mid }
1845
+ ],
1846
+ timestamp: Date.now()
1847
+ };
1848
+ }
1849
+ async fetchTrades(marketId, limit = 50, sideIndex = 0) {
1850
+ const outcomeId = parseInt(marketId, 10);
1851
+ const coin = sideCoin(outcomeId, sideIndex);
1852
+ const raw = await this.client.fetchRecentTrades(coin);
1853
+ return raw.slice(0, limit).map((t) => mapTrade(t, marketId));
1854
+ }
1855
+ async fetchCandles(marketId, interval = "1h", startTime, endTime) {
1856
+ const outcomeId = parseInt(marketId, 10);
1857
+ const coin = sideCoin(outcomeId, 0);
1858
+ const now = Date.now();
1859
+ const start = startTime ?? now - 14 * 24 * 60 * 60 * 1e3;
1860
+ const end = endTime ?? now;
1861
+ const raw = await this.client.fetchCandleSnapshot(
1862
+ coin,
1863
+ interval,
1864
+ start,
1865
+ end
1866
+ );
1867
+ return raw.map((c) => ({
1868
+ time: c.t / 1e3,
1869
+ open: parseFloat(c.o),
1870
+ high: parseFloat(c.h),
1871
+ low: parseFloat(c.l),
1872
+ close: parseFloat(c.c),
1873
+ volume: parseFloat(c.v)
1874
+ }));
1875
+ }
1876
+ // -- WebSocket subscriptions --------------------------------------------
1877
+ subscribeOrderBook(marketId, onData) {
1878
+ const outcomeId = parseInt(marketId, 10);
1879
+ const coin = sideCoin(outcomeId, 0);
1880
+ return this.subscribeWs("l2Book", coin, (data) => {
1881
+ if (isL2BookData(data) && data.coin === coin) {
1882
+ onData(mapWsBook(data, marketId));
1883
+ }
1884
+ });
1885
+ }
1886
+ subscribePrice(marketId, onData) {
1887
+ const outcomeId = parseInt(marketId, 10);
1888
+ const coin0 = sideCoin(outcomeId, 0);
1889
+ const coin1 = sideCoin(outcomeId, 1);
1890
+ return this.subscribeWs("allMids", "*", (data) => {
1891
+ if (!isAllMidsData(data)) return;
1892
+ const mids = data.mids;
1893
+ const side0Mid = mids[coin0];
1894
+ const side1Mid = mids[coin1];
1895
+ if (side0Mid === void 0 && side1Mid === void 0) return;
1896
+ const [n0, n1] = this.sideNamesFor(marketId);
1897
+ onData({
1898
+ marketId,
1899
+ outcomes: [
1900
+ {
1901
+ name: n0,
1902
+ price: side0Mid ?? "0",
1903
+ midpoint: side0Mid ?? "0"
1904
+ },
1905
+ {
1906
+ name: n1,
1907
+ price: side1Mid ?? "0",
1908
+ midpoint: side1Mid ?? "0"
1909
+ }
1910
+ ],
1911
+ timestamp: Date.now()
1912
+ });
1913
+ });
1914
+ }
1915
+ subscribeTrades(marketId, onData) {
1916
+ const outcomeId = parseInt(marketId, 10);
1917
+ const coin = sideCoin(outcomeId, 0);
1918
+ return this.subscribeWs("trades", coin, (data) => {
1919
+ if (!isTradesData(data)) return;
1920
+ for (const t of data) {
1921
+ const trade = t;
1922
+ if (trade.coin === coin) {
1923
+ onData(mapTrade(trade, marketId));
1924
+ }
1925
+ }
1926
+ });
1927
+ }
1928
+ subscribeAllMids(onData) {
1929
+ return this.subscribeWs("allMids", "*", (data) => {
1930
+ if (isAllMidsData(data)) {
1931
+ onData(data);
1932
+ }
1933
+ });
1934
+ }
1935
+ /**
1936
+ * Subscribe to real-time asset context (volume, mark price, OI) for a spot coin.
1937
+ *
1938
+ * HL subscription type is "activeAssetCtx" but the response channel for spot
1939
+ * assets is "activeSpotAssetCtx" (perps use "activeAssetCtx" instead).
1940
+ * We only handle spot here since HIP-4 prediction tokens are spot assets.
1941
+ * If perps support is added, a separate method or channel-aware routing is needed.
1942
+ */
1943
+ subscribeActiveAssetCtx(coin, onData) {
1944
+ return this.subscribeWs(
1945
+ "activeSpotAssetCtx",
1946
+ coin,
1947
+ (data) => {
1948
+ if (isActiveAssetCtxData(data) && data.coin === coin) {
1949
+ onData(data);
1950
+ }
1951
+ },
1952
+ "activeAssetCtx"
1953
+ );
1954
+ }
1955
+ /**
1956
+ * Subscribe to bulk spot asset context updates for ALL spot coins.
1957
+ * Undocumented HL subscription type "spotAssetCtxs" - streams an array
1958
+ * of SpotAssetCtx entries on each update.
1959
+ */
1960
+ subscribeSpotAssetCtxs(onData) {
1961
+ return this.subscribeWs("spotAssetCtxs", "*", (data) => {
1962
+ if (isSpotAssetCtxsData(data)) {
1963
+ onData(data);
1964
+ }
1965
+ });
1966
+ }
1967
+ /**
1968
+ * Subscribe to real-time perp asset context (mark price, oracle, funding).
1969
+ * For perps, both subscription type and response channel are "activeAssetCtx".
1970
+ */
1971
+ subscribePerpAssetCtx(coin, onData) {
1972
+ return this.subscribeWs("activeAssetCtx", coin, (data) => {
1973
+ if (isActivePerpAssetCtxData(data) && data.coin === coin) {
1974
+ onData(data);
1975
+ }
1976
+ });
1977
+ }
1978
+ /** No-op - WebSocket lifecycle is managed by the shared HIP4Client. */
1979
+ destroy() {
1980
+ }
1981
+ // -- Internal helpers ---------------------------------------------------
1982
+ async getMids() {
1983
+ const now = Date.now();
1984
+ if (this.midsCache && now - this.midsCache.time < _HIP4MarketDataAdapter.MIDS_CACHE_TTL) {
1985
+ return this.midsCache.data;
1986
+ }
1987
+ const data = await this.client.fetchAllMids();
1988
+ this.midsCache = { data, time: now };
1989
+ return data;
1990
+ }
1991
+ /**
1992
+ * Subscribe to a WebSocket channel via the shared HIP4Client.
1993
+ *
1994
+ * @param channel Response channel name used for message routing (e.g. "l2Book").
1995
+ * @param coin Coin filter ("*" for channel-only subscriptions).
1996
+ * @param onData Callback fired on each matching message.
1997
+ * @param subscriptionType Wire type sent in the subscribe message. Defaults
1998
+ * to `channel`. Needed when HL uses a different name in the subscribe
1999
+ * request vs the response channel (e.g. send "activeAssetCtx" but receive
2000
+ * on "activeSpotAssetCtx" for spot assets).
2001
+ */
2002
+ subscribeWs(channel, coin, onData, subscriptionType) {
2003
+ const wireType = subscriptionType ?? channel;
2004
+ const subscription = { type: wireType };
2005
+ if (coin !== "*") {
2006
+ subscription.coin = coin;
2007
+ }
2008
+ return this.client.subscribe(
2009
+ subscription,
2010
+ onData,
2011
+ wireType !== channel ? { responseChannel: channel } : void 0
2012
+ );
2013
+ }
2014
+ };
2015
+ function isL2BookData(data) {
2016
+ return typeof data === "object" && data !== null && "coin" in data && "levels" in data;
2017
+ }
2018
+ function isAllMidsData(data) {
2019
+ return typeof data === "object" && data !== null && "mids" in data;
2020
+ }
2021
+ function isTradesData(data) {
2022
+ return Array.isArray(data);
2023
+ }
2024
+ function isActiveAssetCtxData(data) {
2025
+ return typeof data === "object" && data !== null && "coin" in data && "ctx" in data;
2026
+ }
2027
+ function isActivePerpAssetCtxData(data) {
2028
+ return typeof data === "object" && data !== null && "coin" in data && "ctx" in data;
2029
+ }
2030
+ function isSpotAssetCtxsData(data) {
2031
+ return Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null && "coin" in data[0] && "markPx" in data[0];
2032
+ }
2033
+
2034
+ // src/adapter/hyperliquid/pricing.ts
2035
+ var MIN_NOTIONAL = 10;
2036
+ function computeTickSize(price) {
2037
+ if (price <= 0) return 1e-5;
2038
+ const d = toDecimal(price);
2039
+ const magnitude = d.floorLog10();
2040
+ return toNum(pow("10", magnitude.minus(4).toString()));
2041
+ }
2042
+ function roundToTick(price) {
2043
+ const tick = computeTickSize(price);
2044
+ const d = toDecimal(price);
2045
+ const t = toDecimal(tick);
2046
+ return toNum(d.dividedBy(t).round().times(t).toString());
2047
+ }
2048
+ function formatPrice(price) {
2049
+ if (price <= 0) return "0";
2050
+ const rounded = roundToTick(price);
2051
+ const tick = computeTickSize(rounded);
2052
+ const decimals = Math.max(0, -toNum(toDecimal(tick).floorLog10().toString()));
2053
+ let s = fixed(rounded, decimals);
2054
+ if (s.includes(".")) {
2055
+ s = s.replace(/\.?0+$/, "");
2056
+ }
2057
+ return s;
2058
+ }
2059
+ function stripZeros(s) {
2060
+ if (!s.includes(".")) return s;
2061
+ return s.replace(/\.?0+$/, "");
2062
+ }
2063
+ function getMinShares(markPx) {
2064
+ const effective = toNum(clamp(
2065
+ toDecimal(Math.min(markPx, 1 - markPx)).toString(),
2066
+ "0.01",
2067
+ "1"
2068
+ ));
2069
+ return Math.ceil(MIN_NOTIONAL / effective);
2070
+ }
2071
+
2072
+ // src/adapter/hyperliquid/types.ts
2073
+ function splitHexSignature(hex) {
2074
+ const raw = hex.startsWith("0x") ? hex.slice(2) : hex;
2075
+ return {
2076
+ r: "0x" + raw.slice(0, 64),
2077
+ s: "0x" + raw.slice(64, 128),
2078
+ v: parseInt(raw.slice(128, 130), 16)
2079
+ };
2080
+ }
2081
+ function normalizeSignature(sig) {
2082
+ if (typeof sig === "string") {
2083
+ return splitHexSignature(sig);
2084
+ }
2085
+ return sig;
2086
+ }
2087
+
2088
+ // src/adapter/hyperliquid/signing.ts
2089
+ function encodeMsgpack(value) {
2090
+ const parts = [];
2091
+ encodeValue(value, parts);
2092
+ let totalLen = 0;
2093
+ for (const p of parts) totalLen += p.length;
2094
+ const result = new Uint8Array(totalLen);
2095
+ let offset = 0;
2096
+ for (const p of parts) {
2097
+ result.set(p, offset);
2098
+ offset += p.length;
2099
+ }
2100
+ return result;
2101
+ }
2102
+ function encodeValue(value, parts) {
2103
+ if (value === null || value === void 0) {
2104
+ parts.push(new Uint8Array([192]));
2105
+ return;
2106
+ }
2107
+ if (typeof value === "boolean") {
2108
+ parts.push(new Uint8Array([value ? 195 : 194]));
2109
+ return;
2110
+ }
2111
+ if (typeof value === "number") {
2112
+ encodeNumber(value, parts);
2113
+ return;
2114
+ }
2115
+ if (typeof value === "string") {
2116
+ encodeString(value, parts);
2117
+ return;
2118
+ }
2119
+ if (Array.isArray(value)) {
2120
+ encodeArray(value, parts);
2121
+ return;
2122
+ }
2123
+ if (typeof value === "object") {
2124
+ encodeMap(value, parts);
2125
+ return;
2126
+ }
2127
+ throw new Error(`msgpack: unsupported type ${typeof value}`);
2128
+ }
2129
+ function encodeNumber(n, parts) {
2130
+ if (!Number.isInteger(n)) {
2131
+ const buf = new ArrayBuffer(9);
2132
+ const view = new DataView(buf);
2133
+ view.setUint8(0, 203);
2134
+ view.setFloat64(1, n);
2135
+ parts.push(new Uint8Array(buf));
2136
+ return;
2137
+ }
2138
+ if (n >= 0) {
2139
+ if (n <= 127) {
2140
+ parts.push(new Uint8Array([n]));
2141
+ } else if (n <= 255) {
2142
+ parts.push(new Uint8Array([204, n]));
2143
+ } else if (n <= 65535) {
2144
+ const buf = new Uint8Array(3);
2145
+ buf[0] = 205;
2146
+ buf[1] = n >> 8 & 255;
2147
+ buf[2] = n & 255;
2148
+ parts.push(buf);
2149
+ } else if (n <= 4294967295) {
2150
+ const buf = new Uint8Array(5);
2151
+ buf[0] = 206;
2152
+ buf[1] = n >> 24 & 255;
2153
+ buf[2] = n >> 16 & 255;
2154
+ buf[3] = n >> 8 & 255;
2155
+ buf[4] = n & 255;
2156
+ parts.push(buf);
2157
+ } else {
2158
+ const buf = new Uint8Array(9);
2159
+ buf[0] = 207;
2160
+ const big = BigInt(n);
2161
+ const view = new DataView(buf.buffer);
2162
+ view.setBigUint64(1, big);
2163
+ parts.push(buf);
2164
+ }
2165
+ } else {
2166
+ if (n >= -32) {
2167
+ parts.push(new Uint8Array([256 + n & 255]));
2168
+ } else if (n >= -128) {
2169
+ const buf = new Uint8Array(2);
2170
+ buf[0] = 208;
2171
+ buf[1] = 256 + n & 255;
2172
+ parts.push(buf);
2173
+ } else if (n >= -32768) {
2174
+ const buf = new Uint8Array(3);
2175
+ buf[0] = 209;
2176
+ const view = new DataView(buf.buffer);
2177
+ view.setInt16(1, n);
2178
+ parts.push(buf);
2179
+ } else if (n >= -2147483648) {
2180
+ const buf = new Uint8Array(5);
2181
+ buf[0] = 210;
2182
+ const view = new DataView(buf.buffer);
2183
+ view.setInt32(1, n);
2184
+ parts.push(buf);
2185
+ } else {
2186
+ const buf = new Uint8Array(9);
2187
+ buf[0] = 211;
2188
+ const view = new DataView(buf.buffer);
2189
+ view.setBigInt64(1, BigInt(n));
2190
+ parts.push(buf);
2191
+ }
2192
+ }
2193
+ }
2194
+ function encodeString(s, parts) {
2195
+ const encoded = new TextEncoder().encode(s);
2196
+ const len = encoded.length;
2197
+ if (len <= 31) {
2198
+ parts.push(new Uint8Array([160 | len]));
2199
+ } else if (len <= 255) {
2200
+ parts.push(new Uint8Array([217, len]));
2201
+ } else if (len <= 65535) {
2202
+ parts.push(new Uint8Array([218, len >> 8 & 255, len & 255]));
2203
+ } else {
2204
+ parts.push(
2205
+ new Uint8Array([
2206
+ 219,
2207
+ len >> 24 & 255,
2208
+ len >> 16 & 255,
2209
+ len >> 8 & 255,
2210
+ len & 255
2211
+ ])
2212
+ );
2213
+ }
2214
+ parts.push(encoded);
2215
+ }
2216
+ function encodeArray(arr, parts) {
2217
+ const len = arr.length;
2218
+ if (len <= 15) {
2219
+ parts.push(new Uint8Array([144 | len]));
2220
+ } else if (len <= 65535) {
2221
+ parts.push(new Uint8Array([220, len >> 8 & 255, len & 255]));
2222
+ } else {
2223
+ parts.push(
2224
+ new Uint8Array([
2225
+ 221,
2226
+ len >> 24 & 255,
2227
+ len >> 16 & 255,
2228
+ len >> 8 & 255,
2229
+ len & 255
2230
+ ])
2231
+ );
2232
+ }
2233
+ for (const item of arr) {
2234
+ encodeValue(item, parts);
2235
+ }
2236
+ }
2237
+ function encodeMap(obj, parts) {
2238
+ const keys = Object.keys(obj);
2239
+ const len = keys.length;
2240
+ if (len <= 15) {
2241
+ parts.push(new Uint8Array([128 | len]));
2242
+ } else if (len <= 65535) {
2243
+ parts.push(new Uint8Array([222, len >> 8 & 255, len & 255]));
2244
+ } else {
2245
+ parts.push(
2246
+ new Uint8Array([
2247
+ 223,
2248
+ len >> 24 & 255,
2249
+ len >> 16 & 255,
2250
+ len >> 8 & 255,
2251
+ len & 255
2252
+ ])
2253
+ );
2254
+ }
2255
+ for (const key of keys) {
2256
+ encodeString(key, parts);
2257
+ encodeValue(obj[key], parts);
2258
+ }
2259
+ }
2260
+ var KECCAK_ROUND_CONSTANTS = [
2261
+ 0x0000000000000001n,
2262
+ 0x0000000000008082n,
2263
+ 0x800000000000808an,
2264
+ 0x8000000080008000n,
2265
+ 0x000000000000808bn,
2266
+ 0x0000000080000001n,
2267
+ 0x8000000080008081n,
2268
+ 0x8000000000008009n,
2269
+ 0x000000000000008an,
2270
+ 0x0000000000000088n,
2271
+ 0x0000000080008009n,
2272
+ 0x000000008000000an,
2273
+ 0x000000008000808bn,
2274
+ 0x800000000000008bn,
2275
+ 0x8000000000008089n,
2276
+ 0x8000000000008003n,
2277
+ 0x8000000000008002n,
2278
+ 0x8000000000000080n,
2279
+ 0x000000000000800an,
2280
+ 0x800000008000000an,
2281
+ 0x8000000080008081n,
2282
+ 0x8000000000008080n,
2283
+ 0x0000000080000001n,
2284
+ 0x8000000080008008n
2285
+ ];
2286
+ var ROTATION_OFFSETS = [
2287
+ 0,
2288
+ 1,
2289
+ 62,
2290
+ 28,
2291
+ 27,
2292
+ 36,
2293
+ 44,
2294
+ 6,
2295
+ 55,
2296
+ 20,
2297
+ 3,
2298
+ 10,
2299
+ 43,
2300
+ 25,
2301
+ 39,
2302
+ 41,
2303
+ 45,
2304
+ 15,
2305
+ 21,
2306
+ 8,
2307
+ 18,
2308
+ 2,
2309
+ 61,
2310
+ 56,
2311
+ 14
2312
+ ];
2313
+ var PI_PERMUTATION = new Array(25);
2314
+ for (let x = 0; x < 5; x++) {
2315
+ for (let y = 0; y < 5; y++) {
2316
+ const fromIdx = x + 5 * y;
2317
+ const newX = y;
2318
+ const newY = (2 * x + 3 * y) % 5;
2319
+ PI_PERMUTATION[fromIdx] = newX + 5 * newY;
2320
+ }
2321
+ }
2322
+ var MASK64 = 0xffffffffffffffffn;
2323
+ function rotl64(x, n) {
2324
+ if (n === 0) return x;
2325
+ return (x << BigInt(n) | x >> BigInt(64 - n)) & MASK64;
2326
+ }
2327
+ function keccakF1600(state) {
2328
+ for (let round = 0; round < 24; round++) {
2329
+ const c = new Array(5);
2330
+ for (let x = 0; x < 5; x++) {
2331
+ c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
2332
+ }
2333
+ for (let x = 0; x < 5; x++) {
2334
+ const d = c[(x + 4) % 5] ^ rotl64(c[(x + 1) % 5], 1);
2335
+ for (let y = 0; y < 5; y++) {
2336
+ state[x + 5 * y] = (state[x + 5 * y] ^ d) & MASK64;
2337
+ }
2338
+ }
2339
+ const temp = new Array(25);
2340
+ for (let i = 0; i < 25; i++) {
2341
+ temp[PI_PERMUTATION[i]] = rotl64(state[i], ROTATION_OFFSETS[i]);
2342
+ }
2343
+ for (let y = 0; y < 5; y++) {
2344
+ const base = 5 * y;
2345
+ const t0 = temp[base];
2346
+ const t1 = temp[base + 1];
2347
+ const t2 = temp[base + 2];
2348
+ const t3 = temp[base + 3];
2349
+ const t4 = temp[base + 4];
2350
+ state[base] = (t0 ^ ~t1 & MASK64 & t2) & MASK64;
2351
+ state[base + 1] = (t1 ^ ~t2 & MASK64 & t3) & MASK64;
2352
+ state[base + 2] = (t2 ^ ~t3 & MASK64 & t4) & MASK64;
2353
+ state[base + 3] = (t3 ^ ~t4 & MASK64 & t0) & MASK64;
2354
+ state[base + 4] = (t4 ^ ~t0 & MASK64 & t1) & MASK64;
2355
+ }
2356
+ state[0] = (state[0] ^ KECCAK_ROUND_CONSTANTS[round]) & MASK64;
2357
+ }
2358
+ }
2359
+ function keccak256(data) {
2360
+ const rate = 136;
2361
+ const state = new Array(25).fill(0n);
2362
+ let offset = 0;
2363
+ while (offset + rate <= data.length) {
2364
+ xorBlock(state, data, offset, rate);
2365
+ keccakF1600(state);
2366
+ offset += rate;
2367
+ }
2368
+ const remaining = data.length - offset;
2369
+ const lastBlock = new Uint8Array(rate);
2370
+ lastBlock.set(data.subarray(offset, offset + remaining));
2371
+ lastBlock[remaining] = 1;
2372
+ lastBlock[rate - 1] |= 128;
2373
+ xorBlock(state, lastBlock, 0, rate);
2374
+ keccakF1600(state);
2375
+ const output = new Uint8Array(32);
2376
+ for (let i = 0; i < 4; i++) {
2377
+ const word = state[i];
2378
+ for (let b = 0; b < 8; b++) {
2379
+ output[i * 8 + b] = Number(word >> BigInt(b * 8) & 0xffn);
2380
+ }
2381
+ }
2382
+ return output;
2383
+ }
2384
+ function xorBlock(state, data, offset, len) {
2385
+ const words = len / 8;
2386
+ for (let i = 0; i < words; i++) {
2387
+ let word = 0n;
2388
+ const base = offset + i * 8;
2389
+ for (let b = 0; b < 8; b++) {
2390
+ word |= BigInt(data[base + b]) << BigInt(b * 8);
2391
+ }
2392
+ state[i] = (state[i] ^ word) & MASK64;
2393
+ }
2394
+ }
2395
+ function bytesToHex(bytes) {
2396
+ let hex = "0x";
2397
+ for (let i = 0; i < bytes.length; i++) {
2398
+ hex += bytes[i].toString(16).padStart(2, "0");
2399
+ }
2400
+ return hex;
2401
+ }
2402
+ function hexToBytes(hex) {
2403
+ const raw = hex.startsWith("0x") ? hex.slice(2) : hex;
2404
+ const bytes = new Uint8Array(raw.length / 2);
2405
+ for (let i = 0; i < bytes.length; i++) {
2406
+ bytes[i] = parseInt(raw.substring(i * 2, i * 2 + 2), 16);
2407
+ }
2408
+ return bytes;
2409
+ }
2410
+ function formatDecimal(numStr) {
2411
+ if (numStr.includes("e") || numStr.includes("E")) {
2412
+ numStr = Number(numStr).toFixed(20);
2413
+ }
2414
+ if (!numStr.includes(".")) return numStr;
2415
+ const [intPart, fracPart] = numStr.split(".");
2416
+ const trimmed = fracPart.replace(/0+$/, "");
2417
+ return trimmed ? `${intPart}.${trimmed}` : intPart;
2418
+ }
2419
+ function sortOrderAction(action) {
2420
+ const sortedOrders = action.orders.map((o) => {
2421
+ let t = o.t;
2422
+ if ("trigger" in t) {
2423
+ t = {
2424
+ trigger: {
2425
+ isMarket: t.trigger.isMarket,
2426
+ triggerPx: formatDecimal(t.trigger.triggerPx),
2427
+ tpsl: t.trigger.tpsl
2428
+ }
2429
+ };
2430
+ }
2431
+ const wire = {
2432
+ a: o.a,
2433
+ b: o.b,
2434
+ p: formatDecimal(o.p),
2435
+ s: formatDecimal(o.s),
2436
+ r: o.r,
2437
+ t
2438
+ };
2439
+ if (o.c !== void 0) {
2440
+ wire.c = o.c;
2441
+ }
2442
+ return wire;
2443
+ });
2444
+ const sorted = {
2445
+ type: action.type,
2446
+ orders: sortedOrders,
2447
+ grouping: action.grouping
2448
+ };
2449
+ if (action.builder !== void 0) {
2450
+ sorted.builder = {
2451
+ b: action.builder.b.toLowerCase(),
2452
+ f: action.builder.f
2453
+ };
2454
+ }
2455
+ return sorted;
2456
+ }
2457
+ function sortCancelAction(action) {
2458
+ const sortedCancels = action.cancels.map((c) => ({
2459
+ a: c.a,
2460
+ o: c.o
2461
+ }));
2462
+ return {
2463
+ type: action.type,
2464
+ cancels: sortedCancels
2465
+ };
2466
+ }
2467
+ function sortOrderWire(o) {
2468
+ let t = o.t;
2469
+ if ("trigger" in t) {
2470
+ t = {
2471
+ trigger: {
2472
+ isMarket: t.trigger.isMarket,
2473
+ triggerPx: formatDecimal(t.trigger.triggerPx),
2474
+ tpsl: t.trigger.tpsl
2475
+ }
2476
+ };
2477
+ }
2478
+ const wire = {
2479
+ a: o.a,
2480
+ b: o.b,
2481
+ p: formatDecimal(o.p),
2482
+ s: formatDecimal(o.s),
2483
+ r: o.r,
2484
+ t
2485
+ };
2486
+ if (o.c !== void 0) {
2487
+ wire.c = o.c;
2488
+ }
2489
+ return wire;
2490
+ }
2491
+ function sortModifyAction(action) {
2492
+ return {
2493
+ type: action.type,
2494
+ oid: action.oid,
2495
+ order: sortOrderWire(action.order)
2496
+ };
2497
+ }
2498
+ function sortUserOutcomeAction(action) {
2499
+ if ("splitOutcome" in action) {
2500
+ const { outcome: outcome2, amount: amount2 } = action.splitOutcome;
2501
+ return {
2502
+ type: "userOutcome",
2503
+ splitOutcome: { outcome: outcome2, amount: formatDecimal(amount2) }
2504
+ };
2505
+ }
2506
+ if ("mergeOutcome" in action) {
2507
+ const { outcome: outcome2, amount: amount2 } = action.mergeOutcome;
2508
+ return {
2509
+ type: "userOutcome",
2510
+ mergeOutcome: {
2511
+ outcome: outcome2,
2512
+ amount: amount2 === null ? null : formatDecimal(amount2)
2513
+ }
2514
+ };
2515
+ }
2516
+ if ("mergeQuestion" in action) {
2517
+ const { question: question2, amount: amount2 } = action.mergeQuestion;
2518
+ return {
2519
+ type: "userOutcome",
2520
+ mergeQuestion: {
2521
+ question: question2,
2522
+ amount: amount2 === null ? null : formatDecimal(amount2)
2523
+ }
2524
+ };
2525
+ }
2526
+ const { question, outcome, amount } = action.negateOutcome;
2527
+ return {
2528
+ type: "userOutcome",
2529
+ negateOutcome: { question, outcome, amount: formatDecimal(amount) }
2530
+ };
2531
+ }
2532
+ function sortScheduleCancelAction(action) {
2533
+ return { type: action.type, time: action.time };
2534
+ }
2535
+ function createL1ActionHash(params) {
2536
+ const { action, nonce, vaultAddress } = params;
2537
+ const msgpackBytes = encodeMsgpack(action);
2538
+ const nonceBytes = new Uint8Array(8);
2539
+ const nonceView = new DataView(nonceBytes.buffer);
2540
+ nonceView.setBigUint64(0, BigInt(nonce));
2541
+ let vaultBytes;
2542
+ if (vaultAddress) {
2543
+ const addrBytes = hexToBytes(vaultAddress);
2544
+ vaultBytes = new Uint8Array(1 + 20);
2545
+ vaultBytes[0] = 1;
2546
+ vaultBytes.set(addrBytes.slice(0, 20), 1);
2547
+ } else {
2548
+ vaultBytes = new Uint8Array([0]);
2549
+ }
2550
+ const totalLen = msgpackBytes.length + nonceBytes.length + vaultBytes.length;
2551
+ const combined = new Uint8Array(totalLen);
2552
+ combined.set(msgpackBytes, 0);
2553
+ combined.set(nonceBytes, msgpackBytes.length);
2554
+ combined.set(vaultBytes, msgpackBytes.length + nonceBytes.length);
2555
+ return keccak256(combined);
2556
+ }
2557
+ var AGENT_DOMAIN = {
2558
+ name: "Exchange",
2559
+ version: "1",
2560
+ chainId: 1337,
2561
+ verifyingContract: "0x0000000000000000000000000000000000000000"
2562
+ };
2563
+ var AGENT_TYPES = {
2564
+ Agent: [
2565
+ { name: "source", type: "string" },
2566
+ { name: "connectionId", type: "bytes32" }
2567
+ ]
2568
+ };
2569
+ async function signL1Action(params) {
2570
+ const { signer, action, nonce, isTestnet, vaultAddress } = params;
2571
+ const actionHash = createL1ActionHash({ action, nonce, vaultAddress });
2572
+ const connectionId = bytesToHex(actionHash);
2573
+ const message = {
2574
+ source: isTestnet ? "b" : "a",
2575
+ connectionId
2576
+ };
2577
+ const rawSig = await signer.signTypedData(
2578
+ AGENT_DOMAIN,
2579
+ AGENT_TYPES,
2580
+ message
2581
+ );
2582
+ return normalizeSignature(rawSig);
2583
+ }
2584
+ var WITHDRAW_TYPES = {
2585
+ "HyperliquidTransaction:Withdraw": [
2586
+ { name: "hyperliquidChain", type: "string" },
2587
+ { name: "destination", type: "string" },
2588
+ { name: "amount", type: "string" },
2589
+ { name: "time", type: "uint64" }
2590
+ ]
2591
+ };
2592
+ var USD_CLASS_TRANSFER_TYPES = {
2593
+ "HyperliquidTransaction:UsdClassTransfer": [
2594
+ { name: "hyperliquidChain", type: "string" },
2595
+ { name: "amount", type: "string" },
2596
+ { name: "toPerp", type: "bool" },
2597
+ { name: "nonce", type: "uint64" }
2598
+ ]
2599
+ };
2600
+ var USD_SEND_TYPES = {
2601
+ "HyperliquidTransaction:UsdSend": [
2602
+ { name: "hyperliquidChain", type: "string" },
2603
+ { name: "destination", type: "string" },
2604
+ { name: "amount", type: "string" },
2605
+ { name: "time", type: "uint64" }
2606
+ ]
2607
+ };
2608
+ var SPOT_SEND_TYPES = {
2609
+ "HyperliquidTransaction:SpotSend": [
2610
+ { name: "hyperliquidChain", type: "string" },
2611
+ { name: "destination", type: "string" },
2612
+ { name: "token", type: "string" },
2613
+ { name: "amount", type: "string" },
2614
+ { name: "time", type: "uint64" }
2615
+ ]
2616
+ };
2617
+ var SEND_ASSET_TYPES = {
2618
+ "HyperliquidTransaction:SendAsset": [
2619
+ { name: "hyperliquidChain", type: "string" },
2620
+ { name: "destination", type: "string" },
2621
+ { name: "sourceDex", type: "string" },
2622
+ { name: "destinationDex", type: "string" },
2623
+ { name: "token", type: "string" },
2624
+ { name: "amount", type: "string" },
2625
+ { name: "fromSubAccount", type: "string" },
2626
+ { name: "nonce", type: "uint64" }
2627
+ ]
2628
+ };
2629
+ var SEND_TO_EVM_WITH_DATA_TYPES = {
2630
+ "HyperliquidTransaction:SendToEvmWithData": [
2631
+ { name: "hyperliquidChain", type: "string" },
2632
+ { name: "token", type: "string" },
2633
+ { name: "amount", type: "string" },
2634
+ { name: "sourceDex", type: "string" },
2635
+ { name: "destinationRecipient", type: "string" },
2636
+ { name: "addressEncoding", type: "string" },
2637
+ { name: "destinationChainId", type: "uint32" },
2638
+ { name: "gasLimit", type: "uint64" },
2639
+ { name: "data", type: "bytes" },
2640
+ { name: "nonce", type: "uint64" }
2641
+ ]
2642
+ };
2643
+ async function signUserSignedAction(params) {
2644
+ const { signer, action, types } = params;
2645
+ const primaryType = Object.keys(types)[0];
2646
+ if (!primaryType || !types[primaryType]) {
2647
+ throw new Error("EIP-712 types object is empty");
2648
+ }
2649
+ const knownKeys = new Set(types[primaryType].map((f) => f.name));
2650
+ const message = {};
2651
+ for (const [k, v] of Object.entries(action)) {
2652
+ if (knownKeys.has(k)) message[k] = v;
2653
+ }
2654
+ const chainId = parseInt(action.signatureChainId, 16);
2655
+ if (isNaN(chainId)) {
2656
+ throw new Error(`Invalid signatureChainId: "${action.signatureChainId}"`);
2657
+ }
2658
+ const domain = {
2659
+ name: "HyperliquidSignTransaction",
2660
+ version: "1",
2661
+ chainId,
2662
+ verifyingContract: "0x0000000000000000000000000000000000000000"
2663
+ };
2664
+ const rawSig = await signer.signTypedData(
2665
+ domain,
2666
+ types,
2667
+ message
2668
+ );
2669
+ return normalizeSignature(rawSig);
2670
+ }
2671
+
2672
+ // src/adapter/hyperliquid/trading.ts
2673
+ function formatPredictionPrice(price) {
2674
+ if (price <= 0) return "0";
2675
+ let formatted;
2676
+ if (price >= 1e3) {
2677
+ formatted = Math.round(price).toString();
2678
+ } else if (price >= 10) {
2679
+ formatted = price.toFixed(1);
2680
+ } else if (price >= 1) {
2681
+ formatted = price.toFixed(2);
2682
+ } else {
2683
+ formatted = price.toFixed(4);
2684
+ }
2685
+ return formatted.replace(/\.?0+$/, "");
2686
+ }
2687
+ function mapTif(type, tif) {
2688
+ if (type === "market") {
2689
+ return { limit: { tif: "FrontendMarket" } };
2690
+ }
2691
+ switch (tif) {
2692
+ case "FOK":
2693
+ throw new Error(
2694
+ "FOK (Fill-or-Kill) is not supported by Hyperliquid. Use FAK (Fill-and-Kill / IoC) for immediate partial fills, or GTC for resting orders."
2695
+ );
2696
+ case "FAK":
2697
+ return { limit: { tif: "Ioc" } };
2698
+ case "ALO":
2699
+ return { limit: { tif: "Alo" } };
2700
+ case "GTD":
2701
+ throw new Error(
2702
+ "GTD (Good-til-Date) is not supported for prediction markets on Hyperliquid. Use GTC for resting orders."
2703
+ );
2704
+ case "GTC":
2705
+ case void 0:
2706
+ default:
2707
+ return { limit: { tif: "Gtc" } };
2708
+ }
2709
+ }
2710
+ function resolveAssetId(marketId, outcome) {
2711
+ const outcomeId = parseInt(marketId, 10);
2712
+ if (outcome.startsWith("#") || outcome.startsWith("+")) {
2713
+ const num = outcome.slice(1);
2714
+ const sideIndex = parseInt(num.slice(-1), 10);
2715
+ if (sideIndex !== 0 && sideIndex !== 1) {
2716
+ throw new Error(
2717
+ `Invalid sideIndex ${sideIndex} in outcome "${outcome}". Must be 0 or 1.`
2718
+ );
2719
+ }
2720
+ return sideAssetId(outcomeId, sideIndex);
2721
+ }
2722
+ const sideMatch = /(\d)$/.exec(outcome);
2723
+ if (sideMatch?.[1]) {
2724
+ const sideIndex = parseInt(sideMatch[1], 10);
2725
+ if (sideIndex > 1) {
2726
+ return sideAssetId(outcomeId, 0);
2727
+ }
2728
+ return sideAssetId(outcomeId, sideIndex);
2729
+ }
2730
+ return sideAssetId(outcomeId, 0);
2731
+ }
2732
+ function interpretStatus(status) {
2733
+ if ("filled" in status) {
2734
+ return {
2735
+ orderId: String(status.filled.oid),
2736
+ status: "filled",
2737
+ shares: status.filled.totalSz
2738
+ };
2739
+ }
2740
+ if ("resting" in status) {
2741
+ return {
2742
+ orderId: String(status.resting.oid),
2743
+ status: "resting"
2744
+ };
2745
+ }
2746
+ if ("error" in status) {
2747
+ return {
2748
+ error: status.error,
2749
+ status: "error"
2750
+ };
2751
+ }
2752
+ return { status: "unknown" };
2753
+ }
2754
+ var HIP4TradingAdapter = class {
2755
+ constructor(client, auth, config) {
2756
+ this.client = client;
2757
+ this.auth = auth;
2758
+ this.builderAddress = config?.builderAddress;
2759
+ this.builderFee = config?.builderFee;
2760
+ }
2761
+ client;
2762
+ auth;
2763
+ builderAddress;
2764
+ builderFee;
2765
+ buildOrderWire(params) {
2766
+ const assetId = resolveAssetId(params.marketId, params.outcome);
2767
+ const isBuy = params.side === "buy";
2768
+ const amount = stripZeros(params.amount);
2769
+ let price;
2770
+ if (params.type === "market") {
2771
+ price = isBuy ? "0.99999" : "0.00001";
2772
+ this.client.log(
2773
+ "debug",
2774
+ `Market order: side=${isBuy ? "buy" : "sell"}, price=${price} (FrontendMarket best-execution)`
2775
+ );
2776
+ } else {
2777
+ const rawPrice = toNum(toDecimal(params.price ?? "0"));
2778
+ price = formatPrice(rawPrice);
2779
+ this.client.log("debug", `Limit order: price=${price}`);
2780
+ const numericSize = toDecimal(amount);
2781
+ if (params.markPx !== void 0 && !params.skipMinNotionalCheck) {
2782
+ const minShares = getMinShares(params.markPx);
2783
+ if (numericSize.lt(minShares)) {
2784
+ return {
2785
+ error: `Size ${numericSize} below minimum ${minShares} shares (markPx=${params.markPx})`
2786
+ };
2787
+ }
2788
+ }
2789
+ const effectiveNotional = params.markPx !== void 0 ? mul(
2790
+ amount,
2791
+ clamp(
2792
+ min(String(params.markPx), sub("1", String(params.markPx))),
2793
+ "0.01",
2794
+ "1"
2795
+ )
2796
+ ) : mul(price, amount);
2797
+ if (!params.skipMinNotionalCheck && lt(effectiveNotional, String(MIN_NOTIONAL))) {
2798
+ return {
2799
+ error: `Notional $${fixed(effectiveNotional, 2)} below minimum $${MIN_NOTIONAL}`
2800
+ };
2801
+ }
2802
+ }
2803
+ return {
2804
+ wire: {
2805
+ a: assetId,
2806
+ b: isBuy,
2807
+ p: price,
2808
+ s: amount,
2809
+ r: false,
2810
+ t: mapTif(params.type, params.timeInForce)
2811
+ }
2812
+ };
2813
+ }
2814
+ async placeOrder(params) {
2815
+ const signer = this.auth.getSigner();
2816
+ if (!signer) {
2817
+ return {
2818
+ success: false,
2819
+ error: "Not authenticated. Call auth.initAuth() first."
2820
+ };
2821
+ }
2822
+ const wireResult = this.buildOrderWire(params);
2823
+ if ("error" in wireResult) {
2824
+ return { success: false, error: wireResult.error };
2825
+ }
2826
+ const action = {
2827
+ type: "order",
2828
+ orders: [wireResult.wire],
2829
+ grouping: "na"
2830
+ };
2831
+ const effectiveBuilderAddress = params.builderAddress ?? this.builderAddress;
2832
+ const effectiveBuilderFee = params.builderFee ?? this.builderFee;
2833
+ if (effectiveBuilderAddress) {
2834
+ action.builder = {
2835
+ b: effectiveBuilderAddress.toLowerCase(),
2836
+ f: effectiveBuilderFee ?? 0
2837
+ };
2838
+ }
2839
+ const sortedAction = sortOrderAction(action);
2840
+ this.client.log("debug", "Order wire", wireResult.wire);
2841
+ try {
2842
+ const nonce = Date.now();
2843
+ const signature = await signL1Action({
2844
+ signer,
2845
+ action: sortedAction,
2846
+ nonce,
2847
+ isTestnet: this.client.testnet
2848
+ });
2849
+ const res = await this.client.placeOrder(
2850
+ sortedAction,
2851
+ nonce,
2852
+ signature,
2853
+ null
2854
+ );
2855
+ if (res.status !== "ok" || !res.response) {
2856
+ return { success: false, error: "Exchange returned non-ok status" };
2857
+ }
2858
+ const firstStatus = res.response.data.statuses[0];
2859
+ if (!firstStatus) {
2860
+ return { success: false, error: "No order status returned" };
2861
+ }
2862
+ const result = interpretStatus(firstStatus);
2863
+ return {
2864
+ success: !result.error,
2865
+ ...result
2866
+ };
2867
+ } catch (err) {
2868
+ const message = err instanceof Error ? err.message : "Unknown order error";
2869
+ return { success: false, error: message };
2870
+ }
2871
+ }
2872
+ async placeOrders(params) {
2873
+ if (params.length === 0) {
2874
+ return { success: true, results: [] };
2875
+ }
2876
+ const signer = this.auth.getSigner();
2877
+ if (!signer) {
2878
+ return {
2879
+ success: false,
2880
+ results: params.map(() => ({
2881
+ success: false,
2882
+ error: "Not authenticated. Call auth.initAuth() first."
2883
+ }))
2884
+ };
2885
+ }
2886
+ const results = new Array(params.length);
2887
+ const validWires = [];
2888
+ const wireToInputIndex = [];
2889
+ for (let i = 0; i < params.length; i++) {
2890
+ const wireResult = this.buildOrderWire(params[i]);
2891
+ if ("error" in wireResult) {
2892
+ results[i] = { success: false, error: wireResult.error };
2893
+ } else {
2894
+ wireToInputIndex.push(i);
2895
+ validWires.push(wireResult.wire);
2896
+ }
2897
+ }
2898
+ if (validWires.length === 0) {
2899
+ return { success: false, results };
2900
+ }
2901
+ const action = {
2902
+ type: "order",
2903
+ orders: validWires,
2904
+ grouping: "na"
2905
+ };
2906
+ if (this.builderAddress) {
2907
+ action.builder = {
2908
+ b: this.builderAddress.toLowerCase(),
2909
+ f: this.builderFee ?? 0
2910
+ };
2911
+ }
2912
+ const sortedAction = sortOrderAction(action);
2913
+ this.client.log("debug", `Batch order: ${validWires.length} wires`);
2914
+ try {
2915
+ const nonce = Date.now();
2916
+ const signature = await signL1Action({
2917
+ signer,
2918
+ action: sortedAction,
2919
+ nonce,
2920
+ isTestnet: this.client.testnet
2921
+ });
2922
+ const res = await this.client.placeOrder(
2923
+ sortedAction,
2924
+ nonce,
2925
+ signature,
2926
+ null
2927
+ );
2928
+ if (res.status !== "ok" || !res.response) {
2929
+ for (const idx of wireToInputIndex) {
2930
+ results[idx] = {
2931
+ success: false,
2932
+ error: "Exchange returned non-ok status"
2933
+ };
2934
+ }
2935
+ return { success: false, results };
2936
+ }
2937
+ const statuses = res.response.data.statuses;
2938
+ for (let j = 0; j < wireToInputIndex.length; j++) {
2939
+ const inputIdx = wireToInputIndex[j];
2940
+ const status = statuses[j];
2941
+ if (!status) {
2942
+ results[inputIdx] = {
2943
+ success: false,
2944
+ error: "No order status returned"
2945
+ };
2946
+ } else {
2947
+ const interpreted = interpretStatus(status);
2948
+ results[inputIdx] = { success: !interpreted.error, ...interpreted };
2949
+ }
2950
+ }
2951
+ } catch (err) {
2952
+ const message = err instanceof Error ? err.message : "Unknown order error";
2953
+ for (const idx of wireToInputIndex) {
2954
+ results[idx] = { success: false, error: message };
2955
+ }
2956
+ }
2957
+ const allSuccess = results.every((r) => r.success);
2958
+ return { success: allSuccess, results };
2959
+ }
2960
+ async cancelOrder(params) {
2961
+ const signer = this.auth.getSigner();
2962
+ if (!signer) {
2963
+ throw new Error("Not authenticated. Call auth.initAuth() first.");
2964
+ }
2965
+ if (params.length === 0) {
2966
+ throw new Error("cancelOrder requires at least one order to cancel.");
2967
+ }
2968
+ const cancels = params.map((p) => {
2969
+ const assetId = p.outcome ? resolveAssetId(p.marketId, p.outcome) : sideAssetId(parseInt(p.marketId, 10), 0);
2970
+ return { a: assetId, o: parseInt(p.orderId, 10) };
2971
+ });
2972
+ const action = {
2973
+ type: "cancel",
2974
+ cancels
2975
+ };
2976
+ const sortedAction = sortCancelAction(action);
2977
+ const nonce = Date.now();
2978
+ const signature = await signL1Action({
2979
+ signer,
2980
+ action: sortedAction,
2981
+ nonce,
2982
+ isTestnet: this.client.testnet
2983
+ });
2984
+ return this.client.cancelOrder(sortedAction, nonce, signature, null);
2985
+ }
2986
+ async modifyOrder(params) {
2987
+ const signer = this.auth.getSigner();
2988
+ if (!signer) {
2989
+ return {
2990
+ success: false,
2991
+ error: "Not authenticated. Call auth.initAuth() first."
2992
+ };
2993
+ }
2994
+ const wireResult = this.buildOrderWire({
2995
+ marketId: params.marketId,
2996
+ outcome: params.outcome,
2997
+ side: params.side,
2998
+ type: params.type,
2999
+ price: params.price,
3000
+ amount: params.amount,
3001
+ timeInForce: params.timeInForce,
3002
+ markPx: params.markPx
3003
+ });
3004
+ if ("error" in wireResult) {
3005
+ return { success: false, error: wireResult.error };
3006
+ }
3007
+ const oid = parseInt(params.orderId, 10);
3008
+ if (!Number.isFinite(oid)) {
3009
+ return { success: false, error: `Invalid orderId "${params.orderId}"` };
3010
+ }
3011
+ const action = {
3012
+ type: "modify",
3013
+ oid,
3014
+ order: wireResult.wire
3015
+ };
3016
+ const sortedAction = sortModifyAction(action);
3017
+ this.client.log("debug", "Modify wire", {
3018
+ oid,
3019
+ order: wireResult.wire
3020
+ });
3021
+ try {
3022
+ const nonce = Date.now();
3023
+ const signature = await signL1Action({
3024
+ signer,
3025
+ action: sortedAction,
3026
+ nonce,
3027
+ isTestnet: this.client.testnet
3028
+ });
3029
+ const res = await this.client.modifyOrder(
3030
+ sortedAction,
3031
+ nonce,
3032
+ signature,
3033
+ null
3034
+ );
3035
+ if (res.status !== "ok") {
3036
+ const errorMsg = typeof res.response === "string" ? res.response : "Exchange returned non-ok status";
3037
+ return { success: false, error: errorMsg };
3038
+ }
3039
+ const structured = res.response && typeof res.response !== "string" ? res.response : null;
3040
+ const firstStatus = structured?.data?.statuses?.[0];
3041
+ if (!firstStatus) {
3042
+ return {
3043
+ success: true,
3044
+ orderId: String(oid),
3045
+ status: "resting"
3046
+ };
3047
+ }
3048
+ const result = interpretStatus(firstStatus);
3049
+ return {
3050
+ success: !result.error,
3051
+ ...result
3052
+ };
3053
+ } catch (err) {
3054
+ const message = err instanceof Error ? err.message : "Unknown modify error";
3055
+ return { success: false, error: message };
3056
+ }
3057
+ }
3058
+ // -------------------------------------------------------------------------
3059
+ // userOutcome share-conversion actions
3060
+ //
3061
+ // All four variants share the same envelope (`type: "userOutcome"`) and use
3062
+ // L1 agent signing - same authority as orders, so an active agent is
3063
+ // required. They affect the user's spot balances directly (no order book).
3064
+ // -------------------------------------------------------------------------
3065
+ /** Split X quote tokens into X Yes + X No shares of one outcome. */
3066
+ async splitOutcome(params) {
3067
+ return this.submitUserOutcomeAction({
3068
+ type: "userOutcome",
3069
+ splitOutcome: { outcome: params.outcome, amount: params.amount }
3070
+ });
3071
+ }
3072
+ /** Merge X Yes + X No shares of one outcome back into X quote tokens. */
3073
+ async mergeOutcome(params) {
3074
+ return this.submitUserOutcomeAction({
3075
+ type: "userOutcome",
3076
+ mergeOutcome: { outcome: params.outcome, amount: params.amount }
3077
+ });
3078
+ }
3079
+ /** Merge X Yes shares from every outcome of a question into X quote tokens. */
3080
+ async mergeQuestion(params) {
3081
+ return this.submitUserOutcomeAction({
3082
+ type: "userOutcome",
3083
+ mergeQuestion: { question: params.question, amount: params.amount }
3084
+ });
3085
+ }
3086
+ /**
3087
+ * Convert X No shares of one outcome into X Yes shares of every *other*
3088
+ * outcome belonging to the same question (including the fallback). The
3089
+ * wire sub-key is `negateOutcome` - confirmed against HL's own testnet
3090
+ * "Convert Outcomes" UI; their docs body's `negateQuestion` is a typo.
3091
+ */
3092
+ async negateOutcome(params) {
3093
+ return this.submitUserOutcomeAction({
3094
+ type: "userOutcome",
3095
+ negateOutcome: {
3096
+ question: params.question,
3097
+ outcome: params.outcome,
3098
+ amount: params.amount
3099
+ }
3100
+ });
3101
+ }
3102
+ async submitUserOutcomeAction(action) {
3103
+ const signer = this.auth.getSigner();
3104
+ if (!signer) {
3105
+ return {
3106
+ success: false,
3107
+ error: "Not authenticated. Call auth.initAuth() first."
3108
+ };
3109
+ }
3110
+ try {
3111
+ const sorted = sortUserOutcomeAction(action);
3112
+ const nonce = Date.now();
3113
+ this.client.log("debug", "userOutcome action wire", sorted);
3114
+ const signature = await signL1Action({
3115
+ signer,
3116
+ action: sorted,
3117
+ nonce,
3118
+ isTestnet: this.client.testnet
3119
+ });
3120
+ const res = await this.client.submitUserSignedAction(
3121
+ sorted,
3122
+ nonce,
3123
+ signature
3124
+ );
3125
+ if (res.status === "ok") return { success: true };
3126
+ let errorMsg = "User outcome action failed";
3127
+ if (typeof res.response === "string") {
3128
+ errorMsg = res.response;
3129
+ } else if (res.response && typeof res.response === "object" && !Array.isArray(res.response)) {
3130
+ const obj = res.response;
3131
+ if (typeof obj.error === "string") errorMsg = obj.error;
3132
+ }
3133
+ return { success: false, error: errorMsg };
3134
+ } catch (err) {
3135
+ const message = err instanceof Error ? err.message : "Unknown user outcome error";
3136
+ return { success: false, error: message };
3137
+ }
3138
+ }
3139
+ // -------------------------------------------------------------------------
3140
+ // scheduleCancel - HL "dead-man's switch"
3141
+ //
3142
+ // Registers a future timestamp at which HL cancels every open order from
3143
+ // this agent. Per-agent, not per-order: a new schedule replaces the
3144
+ // previous one. Pass `null` to clear an existing schedule.
3145
+ //
3146
+ // Use case: arm 1h before a HIP-4 market's settlement so resting limits
3147
+ // never sit through the auction unprotected.
3148
+ // -------------------------------------------------------------------------
3149
+ async scheduleCancel(time) {
3150
+ const signer = this.auth.getSigner();
3151
+ if (!signer) {
3152
+ return {
3153
+ success: false,
3154
+ error: "Not authenticated. Call auth.initAuth() first."
3155
+ };
3156
+ }
3157
+ try {
3158
+ const action = {
3159
+ type: "scheduleCancel",
3160
+ time
3161
+ };
3162
+ const sorted = sortScheduleCancelAction(action);
3163
+ const nonce = Date.now();
3164
+ this.client.log("debug", "scheduleCancel action wire", sorted);
3165
+ const signature = await signL1Action({
3166
+ signer,
3167
+ action: sorted,
3168
+ nonce,
3169
+ isTestnet: this.client.testnet
3170
+ });
3171
+ const res = await this.client.submitUserSignedAction(
3172
+ sorted,
3173
+ nonce,
3174
+ signature
3175
+ );
3176
+ if (res.status === "ok") return { success: true };
3177
+ let errorMsg = "scheduleCancel failed";
3178
+ if (typeof res.response === "string") {
3179
+ errorMsg = res.response;
3180
+ } else if (res.response && typeof res.response === "object" && !Array.isArray(res.response)) {
3181
+ const obj = res.response;
3182
+ if (typeof obj.error === "string") errorMsg = obj.error;
3183
+ }
3184
+ return { success: false, error: errorMsg };
3185
+ } catch (err) {
3186
+ const message = err instanceof Error ? err.message : "Unknown scheduleCancel error";
3187
+ return { success: false, error: message };
3188
+ }
3189
+ }
3190
+ };
3191
+
3192
+ // src/lib/precision/financial/price.ts
3193
+ function applySlippage(mid, ratio, side) {
3194
+ const m = toDecimal(mid);
3195
+ const r = toDecimal(ratio);
3196
+ return side === "buy" ? m.times(Decimal.sum(1, r)).toString() : m.times(new Decimal(1).minus(r)).toString();
3197
+ }
3198
+
3199
+ // src/adapter/hyperliquid/core-evm-system-address.ts
3200
+ var HYPE_CORE_EVM_SYSTEM_ADDRESS = "0x2222222222222222222222222222222222222222";
3201
+ var MAX_CORE_EVM_TOKEN_INDEX = Number.MAX_SAFE_INTEGER;
3202
+ function deriveCoreEvmSystemAddress(tokenIndex) {
3203
+ if (!Number.isInteger(tokenIndex)) {
3204
+ throw new Error(
3205
+ `Token index must be an integer, got ${String(tokenIndex)}`
3206
+ );
3207
+ }
3208
+ if (tokenIndex < 0) {
3209
+ throw new Error(`Token index must be non-negative, got ${tokenIndex}`);
3210
+ }
3211
+ if (tokenIndex > MAX_CORE_EVM_TOKEN_INDEX) {
3212
+ throw new Error(
3213
+ `Token index ${tokenIndex} exceeds safe-integer ceiling ${MAX_CORE_EVM_TOKEN_INDEX}`
3214
+ );
3215
+ }
3216
+ const hex = BigInt(tokenIndex).toString(16).padStart(38, "0");
3217
+ return `0x20${hex}`;
3218
+ }
3219
+
3220
+ // src/adapter/hyperliquid/wallet.ts
3221
+ var USDH_SPOT_INDEX_TESTNET = 1338;
3222
+ var USDH_SPOT_INDEX_MAINNET = 230;
3223
+ var USDH_ASSET_ID_TESTNET = 1e4 + USDH_SPOT_INDEX_TESTNET;
3224
+ var USDH_ASSET_ID_MAINNET = 1e4 + USDH_SPOT_INDEX_MAINNET;
3225
+ var USDH_SPOT_PAIR_TESTNET = "@1338";
3226
+ var USDH_SPOT_PAIR_MAINNET = "@230";
3227
+ var USDH_TOKEN_INDEX_MAINNET = 360;
3228
+ var USDH_TOKEN_INDEX_TESTNET = 1452;
3229
+ var USDH_TOKEN_HASH_MAINNET = "0x54e00a5988577cb0b0c9ab0cb6ef7f4b";
3230
+ var USDH_TOKEN_HASH_TESTNET = "0x471fd4480bb9943a1fe080ab0d4ff36c";
3231
+ var USDH_HL_TOKEN_MAINNET = `USDH:${USDH_TOKEN_HASH_MAINNET}`;
3232
+ var USDH_HL_TOKEN_TESTNET = `USDH:${USDH_TOKEN_HASH_TESTNET}`;
3233
+ var USDH_EVM_ADDRESS_MAINNET = "0x111111a1a0667d36bD57c0A9f569b98057111111";
3234
+ var USDH_EVM_ADDRESS_TESTNET = "0x22222245c52c817F95b74664Ae8546B490222222";
3235
+ var USDH_CORE_DECIMALS = 8;
3236
+ var USDH_EVM_DECIMALS = 6;
3237
+ var USDC_HL_TOKEN_MAINNET = "USDC:0x6d1e7cde53ba9467b783cb7c530ce054";
3238
+ var USDC_HL_TOKEN_TESTNET = "USDC:0xeb62eee3685fc4c43992febcd9e75443";
3239
+ var USDH_ASSET_ID = USDH_ASSET_ID_TESTNET;
3240
+ var USDH_SPOT_PAIR = USDH_SPOT_PAIR_TESTNET;
3241
+ var SPOT_TOKEN_ID_PATTERN = /^[A-Z0-9]+:0x[0-9a-f]{32}$/;
3242
+ var HIP4WalletAdapter = class {
3243
+ constructor(client, auth) {
3244
+ this.client = client;
3245
+ this.auth = auth;
3246
+ }
3247
+ client;
3248
+ auth;
3249
+ signer = null;
3250
+ setSigner(signer) {
3251
+ const obj = signer;
3252
+ if (typeof obj.getAddress === "function" && typeof obj.signTypedData === "function") {
3253
+ this.signer = signer;
3254
+ return;
3255
+ }
3256
+ if (typeof obj.address === "string" && typeof obj.signTypedData === "function") {
3257
+ const addr = obj.address;
3258
+ const sign = obj.signTypedData;
3259
+ this.signer = {
3260
+ getAddress: () => addr,
3261
+ signTypedData: (domain, types, value) => {
3262
+ const primaryType = Object.keys(types)[0];
3263
+ if (!primaryType) throw new Error("EIP-712 types object is empty");
3264
+ return sign({
3265
+ domain,
3266
+ types: { ...types },
3267
+ primaryType,
3268
+ message: value
3269
+ });
3270
+ }
3271
+ };
3272
+ return;
3273
+ }
3274
+ throw new Error(
3275
+ "Invalid signer: must have getAddress()+signTypedData() or address+signTypedData()"
3276
+ );
3277
+ }
3278
+ async buyUsdh(amount) {
3279
+ return this.executeSpotOrder(true, amount);
3280
+ }
3281
+ async sellUsdh(amount) {
3282
+ return this.executeSpotOrder(false, amount);
3283
+ }
3284
+ async transferToSpot(amount) {
3285
+ return this.usdClassTransfer({ amount, toPerp: false });
3286
+ }
3287
+ async transferToPerps(amount) {
3288
+ return this.usdClassTransfer({ amount, toPerp: true });
3289
+ }
3290
+ async usdClassTransfer(params) {
3291
+ return this.executeUserSigned(
3292
+ "usdClassTransfer",
3293
+ { amount: params.amount, toPerp: params.toPerp },
3294
+ USD_CLASS_TRANSFER_TYPES,
3295
+ "nonce"
3296
+ );
3297
+ }
3298
+ async withdraw(params) {
3299
+ return this.executeUserSigned(
3300
+ "withdraw3",
3301
+ { destination: params.destination, amount: params.amount },
3302
+ WITHDRAW_TYPES,
3303
+ "time"
3304
+ );
3305
+ }
3306
+ async usdSend(params) {
3307
+ return this.executeUserSigned(
3308
+ "usdSend",
3309
+ { destination: params.destination, amount: params.amount },
3310
+ USD_SEND_TYPES,
3311
+ "time"
3312
+ );
3313
+ }
3314
+ async spotSend(params) {
3315
+ return this.executeUserSigned(
3316
+ "spotSend",
3317
+ {
3318
+ destination: params.destination,
3319
+ token: params.token,
3320
+ amount: params.amount
3321
+ },
3322
+ SPOT_SEND_TYPES,
3323
+ "time"
3324
+ );
3325
+ }
3326
+ /**
3327
+ * Unified-account-compatible transfer primitive. Supersedes `spotSend`,
3328
+ * `usdSend`, `usdClassTransfer`, and `subAccountSpotTransfer` when the
3329
+ * account is in `unifiedAccount` or `portfolioMargin` abstraction mode
3330
+ * (where silo-specific actions return "Action disabled when unified
3331
+ * account is active").
3332
+ *
3333
+ * Use cases:
3334
+ * - User → user spot transfer: `sourceDex: "spot", destinationDex: "spot"`
3335
+ * - Core → HyperEVM bridge: `destination = systemAddress`, both dex = `""`
3336
+ * - Spot → perp silo move: `sourceDex: "spot", destinationDex: ""`
3337
+ */
3338
+ async sendAsset(params) {
3339
+ return this.executeUserSigned(
3340
+ "sendAsset",
3341
+ {
3342
+ destination: params.destination,
3343
+ sourceDex: params.sourceDex ?? "spot",
3344
+ destinationDex: params.destinationDex ?? "",
3345
+ token: params.token,
3346
+ amount: params.amount,
3347
+ fromSubAccount: params.fromSubAccount ?? ""
3348
+ },
3349
+ SEND_ASSET_TYPES,
3350
+ "nonce"
3351
+ );
3352
+ }
3353
+ /**
3354
+ * Move a spot token from HyperCore to HyperEVM.
3355
+ *
3356
+ * Mechanism: `sendAsset` to the token's system address, with
3357
+ * `sourceDex = "spot"` and `destinationDex = ""`. HL credits the sender's
3358
+ * HyperEVM address by calling `transfer(sender, amount)` on the token's
3359
+ * linked ERC-20 contract. No hook data, no CCTP - the token stays as the
3360
+ * same token on HyperEVM.
3361
+ *
3362
+ * Why `sendAsset` and not `spotSend`: `spotSend` is rejected under
3363
+ * `unifiedAccount` / `portfolioMargin` abstraction modes with "Action
3364
+ * disabled when unified account is active". Unified is the app default
3365
+ * for new accounts, so `sendAsset` is the only flow that works for both
3366
+ * modes.
3367
+ *
3368
+ * Use this for USDH (and any non-USDC spot token). For USDC destinations
3369
+ * on other EVM chains, use `sendUsdcToEvm` instead (CCTP is strictly
3370
+ * cheaper + faster than sendAsset-to-HyperEVM + bridge).
3371
+ *
3372
+ * HYPE is the documented exception: callers must set `isHype: true` so
3373
+ * the destination resolves to the hardcoded `0x2222…2222` slot rather
3374
+ * than the index-derived address.
3375
+ */
3376
+ async sendSpotTokenToEvm(params) {
3377
+ if (!SPOT_TOKEN_ID_PATTERN.test(params.tokenId)) {
3378
+ return {
3379
+ success: false,
3380
+ error: `Invalid token id ${params.tokenId}: expected "SYMBOL:0x<32-hex>"`
3381
+ };
3382
+ }
3383
+ let destination;
3384
+ try {
3385
+ destination = params.isHype ? HYPE_CORE_EVM_SYSTEM_ADDRESS : deriveCoreEvmSystemAddress(params.tokenIndex);
3386
+ } catch (err) {
3387
+ return {
3388
+ success: false,
3389
+ error: err instanceof Error ? err.message : "Invalid token index"
3390
+ };
3391
+ }
3392
+ return this.sendAsset({
3393
+ destination,
3394
+ token: params.tokenId,
3395
+ amount: params.amount,
3396
+ sourceDex: "spot",
3397
+ destinationDex: "spot"
3398
+ });
3399
+ }
3400
+ /**
3401
+ * Transfer a spot token from HyperCore to the EVM side, invoking the
3402
+ * linked contract's `coreReceiveWithData` hook with user-provided data.
3403
+ *
3404
+ * For Circle-managed tokens (USDC), this bridges via CCTP directly to
3405
+ * `destinationChainId` - `destinationRecipient` receives USDC there. No
3406
+ * secondary `withdraw3` or Across hop needed to land on Arbitrum.
3407
+ *
3408
+ * Fee model: HyperEVM gas (`gasLimit × baseFee × HYPE/USDC_oracle`,
3409
+ * deducted from L1 USDC) + Circle forwarder fee on destination (0.2 USDC
3410
+ * flat, subtracted from amount). CCTP protocol fee is 0.
3411
+ */
3412
+ async sendToEvmWithData(params) {
3413
+ return this.executeUserSigned(
3414
+ "sendToEvmWithData",
3415
+ {
3416
+ token: params.token,
3417
+ amount: params.amount,
3418
+ sourceDex: params.sourceDex ?? "spot",
3419
+ destinationRecipient: params.destinationRecipient,
3420
+ addressEncoding: params.addressEncoding ?? "hex",
3421
+ destinationChainId: params.destinationChainId,
3422
+ gasLimit: params.gasLimit,
3423
+ data: params.data ?? "0x"
3424
+ },
3425
+ SEND_TO_EVM_WITH_DATA_TYPES,
3426
+ "nonce"
3427
+ );
3428
+ }
3429
+ /**
3430
+ * Convenience wrapper: `sendToEvmWithData` with the USDC token string
3431
+ * for the current network auto-filled. Circle's CoreDepositWallet picks
3432
+ * up the call and CCTPs USDC to `destinationChainId`.
3433
+ */
3434
+ async sendUsdcToEvm(params) {
3435
+ const token = this.client.testnet ? USDC_HL_TOKEN_TESTNET : USDC_HL_TOKEN_MAINNET;
3436
+ return this.sendToEvmWithData({ ...params, token });
3437
+ }
3438
+ // -------------------------------------------------------------------------
3439
+ // Internal
3440
+ // -------------------------------------------------------------------------
3441
+ async executeSpotOrder(isBuy, amount) {
3442
+ const agentSigner = this.auth.getSigner();
3443
+ if (!agentSigner) {
3444
+ return {
3445
+ success: false,
3446
+ error: "Not authenticated. Call auth.initAuth() first."
3447
+ };
3448
+ }
3449
+ try {
3450
+ const spotIndex = this.client.testnet ? USDH_SPOT_INDEX_TESTNET : USDH_SPOT_INDEX_MAINNET;
3451
+ const assetId = 1e4 + spotIndex;
3452
+ const ctx = await this.client.fetchSpotAssetCtx(spotIndex);
3453
+ const oracle = ctx ? toDecimal(ctx.markPx) : null;
3454
+ if (!oracle || oracle.lte(0)) {
3455
+ return { success: false, error: "Could not fetch USDH oracle price" };
3456
+ }
3457
+ const slipped = applySlippage(ctx.markPx, "0.1", isBuy ? "buy" : "sell");
3458
+ const price = formatPrice(Number(slipped));
3459
+ const action = {
3460
+ type: "order",
3461
+ orders: [
3462
+ {
3463
+ a: assetId,
3464
+ b: isBuy,
3465
+ p: price,
3466
+ s: amount,
3467
+ r: false,
3468
+ t: { limit: { tif: "Ioc" } }
3469
+ }
3470
+ ],
3471
+ grouping: "na"
3472
+ };
3473
+ const sortedAction = sortOrderAction(action);
3474
+ const nonce = Date.now();
3475
+ const signature = await signL1Action({
3476
+ signer: agentSigner,
3477
+ action: sortedAction,
3478
+ nonce,
3479
+ isTestnet: this.client.testnet
3480
+ });
3481
+ const res = await this.client.placeOrder(
3482
+ sortedAction,
3483
+ nonce,
3484
+ signature,
3485
+ null
3486
+ );
3487
+ if (res.status !== "ok" || !res.response) {
3488
+ return { success: false, error: "Exchange returned non-ok status" };
3489
+ }
3490
+ const firstStatus = res.response.data.statuses[0];
3491
+ if (!firstStatus) {
3492
+ return { success: false, error: "No order status returned" };
3493
+ }
3494
+ if ("error" in firstStatus) {
3495
+ return { success: false, error: firstStatus.error };
3496
+ }
3497
+ const filled = "filled" in firstStatus ? firstStatus.filled : void 0;
3498
+ return {
3499
+ success: true,
3500
+ filledSz: filled?.totalSz,
3501
+ avgPx: filled?.avgPx,
3502
+ oid: filled?.oid
3503
+ };
3504
+ } catch (err) {
3505
+ const message = err instanceof Error ? err.message : "Unknown error";
3506
+ return { success: false, error: message };
3507
+ }
3508
+ }
3509
+ /**
3510
+ * Set a referral code on Hyperliquid.
3511
+ * Uses L1 agent signing (requires auth to be initialized).
3512
+ */
3513
+ async setReferrer(code) {
3514
+ const signer = this.auth.getSigner();
3515
+ if (!signer) {
3516
+ return {
3517
+ success: false,
3518
+ error: "Not authenticated. Call auth.initAuth() first."
3519
+ };
3520
+ }
3521
+ try {
3522
+ const nonce = Date.now();
3523
+ const action = { type: "setReferrer", code };
3524
+ const signature = await signL1Action({
3525
+ signer,
3526
+ action,
3527
+ nonce,
3528
+ isTestnet: this.client.testnet
3529
+ });
3530
+ const res = await this.client.submitUserSignedAction(
3531
+ action,
3532
+ nonce,
3533
+ signature
3534
+ );
3535
+ if (res.status === "ok") {
3536
+ return { success: true };
3537
+ }
3538
+ let errorMsg = "Failed to set referrer";
3539
+ if (typeof res.response === "string") {
3540
+ errorMsg = res.response;
3541
+ } else if (res.response && typeof res.response === "object" && !Array.isArray(res.response)) {
3542
+ const obj = res.response;
3543
+ if (typeof obj.error === "string") errorMsg = obj.error;
3544
+ }
3545
+ return { success: false, error: errorMsg };
3546
+ } catch (err) {
3547
+ const message = err instanceof Error ? err.message : "Unknown error";
3548
+ return { success: false, error: message };
3549
+ }
3550
+ }
3551
+ async executeUserSigned(type, fields, types, nonceFieldName) {
3552
+ if (!this.signer) {
3553
+ return {
3554
+ success: false,
3555
+ error: "No wallet signer set. Call wallet.setSigner() first."
3556
+ };
3557
+ }
3558
+ try {
3559
+ const nonce = Date.now();
3560
+ const action = {
3561
+ type,
3562
+ signatureChainId: this.client.testnet ? "0x66eee" : "0xa4b1",
3563
+ hyperliquidChain: this.client.testnet ? "Testnet" : "Mainnet",
3564
+ ...fields,
3565
+ [nonceFieldName]: nonce
3566
+ };
3567
+ const signature = await signUserSignedAction({
3568
+ signer: this.signer,
3569
+ action,
3570
+ types
3571
+ });
3572
+ const res = await this.client.submitUserSignedAction(
3573
+ action,
3574
+ nonce,
3575
+ signature
3576
+ );
3577
+ if (res.status === "ok") {
3578
+ return { success: true };
3579
+ }
3580
+ let errorMsg = "Action failed";
3581
+ if (typeof res.response === "string") {
3582
+ errorMsg = res.response;
3583
+ } else if (res.response && typeof res.response === "object" && !Array.isArray(res.response)) {
3584
+ const obj = res.response;
3585
+ if (typeof obj.error === "string") errorMsg = obj.error;
3586
+ }
3587
+ return { success: false, error: errorMsg };
3588
+ } catch (err) {
3589
+ const message = err instanceof Error ? err.message : "Unknown error";
3590
+ return { success: false, error: message };
3591
+ }
3592
+ }
3593
+ };
3594
+
3595
+ // src/adapter/hyperliquid/index.ts
3596
+ var HyperliquidHip4Adapter = class {
3597
+ id = "hyperliquid";
3598
+ name;
3599
+ events;
3600
+ marketData;
3601
+ account;
3602
+ trading;
3603
+ auth;
3604
+ wallet;
3605
+ client;
3606
+ _marketData;
3607
+ constructor(config = {}) {
3608
+ const testnet = config.testnet ?? true;
3609
+ this.name = testnet ? "Hyperliquid HIP-4 (Testnet)" : "Hyperliquid HIP-4";
3610
+ this.client = new HIP4Client({
3611
+ testnet,
3612
+ infoUrl: config.infoUrl,
3613
+ exchangeUrl: config.exchangeUrl,
3614
+ logger: config.logger
3615
+ });
3616
+ const auth = new HIP4Auth();
3617
+ const eventAdapter = new HIP4EventAdapter(this.client);
3618
+ this.events = eventAdapter;
3619
+ const sideNameResolver = eventAdapter.getSideNameResolver();
3620
+ const ensureSideNames = () => eventAdapter.ensureSideNames();
3621
+ this._marketData = new HIP4MarketDataAdapter(this.client, sideNameResolver, ensureSideNames);
3622
+ this.marketData = this._marketData;
3623
+ this.account = new HIP4AccountAdapter(this.client, eventAdapter, sideNameResolver);
3624
+ this.trading = new HIP4TradingAdapter(this.client, auth, {
3625
+ builderAddress: config.builderAddress,
3626
+ builderFee: config.builderFee
3627
+ });
3628
+ this.auth = auth;
3629
+ this.wallet = new HIP4WalletAdapter(this.client, auth);
3630
+ }
3631
+ async initialize() {
3632
+ await this.events.ensureSideNames();
3633
+ }
3634
+ destroy() {
3635
+ this.client.closeWs();
3636
+ this.auth.clearAuth();
3637
+ }
3638
+ };
3639
+
3640
+ // src/adapter/factory.ts
3641
+ function createHIP4Adapter(config = {}) {
3642
+ return new HyperliquidHip4Adapter(config);
3643
+ }
3644
+
3645
+ // src/adapter/hyperliquid/agent-wallet.ts
3646
+ var HL_MAINNET_CHAIN_ID = 42161;
3647
+ var HL_TESTNET_CHAIN_ID = 421614;
3648
+ function getEIP712Domain(isMainnet) {
3649
+ return {
3650
+ name: "HyperliquidSignTransaction",
3651
+ version: "1",
3652
+ chainId: isMainnet ? HL_MAINNET_CHAIN_ID : HL_TESTNET_CHAIN_ID,
3653
+ verifyingContract: "0x0000000000000000000000000000000000000000"
3654
+ };
3655
+ }
3656
+ var HL_MAINNET_API = "https://api.hyperliquid.xyz";
3657
+ var HL_TESTNET_API = "https://api.hyperliquid-testnet.xyz";
3658
+ function getAgentApprovalTypedData(agentAddress, agentName, nonce, isMainnet = true) {
3659
+ return {
3660
+ domain: getEIP712Domain(isMainnet),
3661
+ types: {
3662
+ "HyperliquidTransaction:ApproveAgent": [
3663
+ { name: "hyperliquidChain", type: "string" },
3664
+ { name: "agentAddress", type: "address" },
3665
+ { name: "agentName", type: "string" },
3666
+ { name: "nonce", type: "uint64" }
3667
+ ]
3668
+ },
3669
+ primaryType: "HyperliquidTransaction:ApproveAgent",
3670
+ message: {
3671
+ hyperliquidChain: isMainnet ? "Mainnet" : "Testnet",
3672
+ agentAddress: agentAddress.toLowerCase(),
3673
+ agentName,
3674
+ nonce: BigInt(nonce)
3675
+ }
3676
+ };
3677
+ }
3678
+ var APPROVE_BUILDER_FEE_TYPES = {
3679
+ "HyperliquidTransaction:ApproveBuilderFee": [
3680
+ { name: "hyperliquidChain", type: "string" },
3681
+ { name: "maxFeeRate", type: "string" },
3682
+ { name: "builder", type: "address" },
3683
+ { name: "nonce", type: "uint64" }
3684
+ ]
3685
+ };
3686
+ function getBuilderFeeApprovalTypedData(builderAddress, maxFeeRate, nonce, isMainnet = true) {
3687
+ return {
3688
+ domain: getEIP712Domain(isMainnet),
3689
+ types: APPROVE_BUILDER_FEE_TYPES,
3690
+ primaryType: "HyperliquidTransaction:ApproveBuilderFee",
3691
+ message: {
3692
+ hyperliquidChain: isMainnet ? "Mainnet" : "Testnet",
3693
+ maxFeeRate,
3694
+ builder: builderAddress.toLowerCase(),
3695
+ nonce: BigInt(nonce)
3696
+ }
3697
+ };
3698
+ }
3699
+ async function submitBuilderFeeApproval(signature, builderAddress, maxFeeRate, nonce, isMainnet = true, exchangeUrl) {
3700
+ const parsedSig = splitHexSignature(signature);
3701
+ const action = {
3702
+ type: "approveBuilderFee",
3703
+ signatureChainId: isMainnet ? "0xa4b1" : "0x66eee",
3704
+ hyperliquidChain: isMainnet ? "Mainnet" : "Testnet",
3705
+ maxFeeRate,
3706
+ builder: builderAddress.toLowerCase(),
3707
+ nonce
3708
+ };
3709
+ try {
3710
+ const url = exchangeUrl ?? `${isMainnet ? HL_MAINNET_API : HL_TESTNET_API}/exchange`;
3711
+ const response = await fetch(url, {
3712
+ method: "POST",
3713
+ headers: { "Content-Type": "application/json" },
3714
+ body: JSON.stringify({
3715
+ action,
3716
+ nonce,
3717
+ signature: parsedSig
3718
+ })
3719
+ });
3720
+ const result = await response.json();
3721
+ if (result.status === "ok") {
3722
+ return { success: true };
3723
+ }
3724
+ const errorMsg = typeof result.response === "string" ? result.response : result.response?.error ?? "Failed to approve builder fee";
3725
+ return { success: false, error: errorMsg };
3726
+ } catch (error) {
3727
+ return {
3728
+ success: false,
3729
+ error: error instanceof Error ? error.message : "Network error"
3730
+ };
3731
+ }
3732
+ }
3733
+ async function submitAgentApproval(signature, agentAddress, agentName, nonce, isMainnet = true, exchangeUrl) {
3734
+ const parsedSig = splitHexSignature(signature);
3735
+ const action = {
3736
+ type: "approveAgent",
3737
+ signatureChainId: isMainnet ? "0xa4b1" : "0x66eee",
3738
+ hyperliquidChain: isMainnet ? "Mainnet" : "Testnet",
3739
+ agentAddress: agentAddress.toLowerCase(),
3740
+ agentName,
3741
+ nonce
3742
+ };
3743
+ try {
3744
+ const url = exchangeUrl ?? `${isMainnet ? HL_MAINNET_API : HL_TESTNET_API}/exchange`;
3745
+ const response = await fetch(url, {
3746
+ method: "POST",
3747
+ headers: { "Content-Type": "application/json" },
3748
+ body: JSON.stringify({
3749
+ action,
3750
+ nonce,
3751
+ signature: parsedSig
3752
+ })
3753
+ });
3754
+ const result = await response.json();
3755
+ if (result.status === "ok") {
3756
+ return { success: true };
3757
+ }
3758
+ const error = typeof result.response === "string" ? result.response : result.response?.error ?? "Failed to approve agent";
3759
+ return { success: false, error };
3760
+ } catch (error) {
3761
+ return {
3762
+ success: false,
3763
+ error: error instanceof Error ? error.message : "Network error"
3764
+ };
3765
+ }
3766
+ }
3767
+
3768
+ // src/adapter/hyperliquid/core-to-evm-fees.ts
3769
+ var CORE_TO_EVM_GAS_LIMIT = 200000n;
3770
+ function estimateCoreToEvmFee(inputs) {
3771
+ if (inputs.hypeCoreSpotBalance < 0) {
3772
+ throw new Error(
3773
+ `hypeCoreSpotBalance must be non-negative, got ${inputs.hypeCoreSpotBalance}`
3774
+ );
3775
+ }
3776
+ if (inputs.sourceTokenPriceUsd <= 0) {
3777
+ throw new Error(
3778
+ `sourceTokenPriceUsd must be positive, got ${inputs.sourceTokenPriceUsd}`
3779
+ );
3780
+ }
3781
+ if (inputs.hypeSpotMarkPxUsd <= 0) {
3782
+ throw new Error(
3783
+ `hypeSpotMarkPxUsd must be positive, got ${inputs.hypeSpotMarkPxUsd}`
3784
+ );
3785
+ }
3786
+ const zeroBase = {
3787
+ currency: "source",
3788
+ amountHype: 0,
3789
+ amountSourceToken: 0,
3790
+ usd: 0,
3791
+ gasLimit: CORE_TO_EVM_GAS_LIMIT,
3792
+ baseFeeWei: inputs.baseFeeWei
3793
+ };
3794
+ if (inputs.baseFeeWei <= 0n) return zeroBase;
3795
+ const gasWei = CORE_TO_EVM_GAS_LIMIT * inputs.baseFeeWei;
3796
+ const gasGwei = Number(gasWei / 1000000000n);
3797
+ const feeHype = gasGwei / 1e9;
3798
+ const feeUsd = feeHype * inputs.hypeSpotMarkPxUsd;
3799
+ if (inputs.hypeCoreSpotBalance >= feeHype) {
3800
+ return {
3801
+ currency: "HYPE",
3802
+ amountHype: feeHype,
3803
+ amountSourceToken: 0,
3804
+ usd: feeUsd,
3805
+ gasLimit: CORE_TO_EVM_GAS_LIMIT,
3806
+ baseFeeWei: inputs.baseFeeWei
3807
+ };
3808
+ }
3809
+ return {
3810
+ currency: "source",
3811
+ amountHype: 0,
3812
+ amountSourceToken: feeUsd / inputs.sourceTokenPriceUsd,
3813
+ usd: feeUsd,
3814
+ gasLimit: CORE_TO_EVM_GAS_LIMIT,
3815
+ baseFeeWei: inputs.baseFeeWei
3816
+ };
3817
+ }
3818
+ function medianBaseFeeWei(samples) {
3819
+ if (samples.length === 0) {
3820
+ throw new Error("medianBaseFeeWei: empty sample array");
3821
+ }
3822
+ for (const s of samples) {
3823
+ if (s < 0n) {
3824
+ throw new Error(
3825
+ `medianBaseFeeWei: non-negative values required, got ${s}`
3826
+ );
3827
+ }
3828
+ }
3829
+ const sorted = [...samples].sort((a2, b2) => a2 < b2 ? -1 : a2 > b2 ? 1 : 0);
3830
+ const mid = sorted.length >> 1;
3831
+ if (sorted.length % 2 === 1) {
3832
+ return sorted[mid];
3833
+ }
3834
+ const a = sorted[mid - 1];
3835
+ const b = sorted[mid];
3836
+ return (a + b) / 2n;
3837
+ }
3838
+
3839
+ // src/adapter/hyperliquid/hype-spot-mark-px.ts
3840
+ var HYPE_USDC_SPOT_PAIR_MAINNET = "@107";
3841
+ var HYPE_USDC_SPOT_PAIR_TESTNET = "@1035";
3842
+ function selectHypeSpotMarkPx(ctxs, options = {}) {
3843
+ const coin = options.testnet ? HYPE_USDC_SPOT_PAIR_TESTNET : HYPE_USDC_SPOT_PAIR_MAINNET;
3844
+ const ctx = ctxs[coin];
3845
+ if (!ctx) return null;
3846
+ const raw = ctx.markPx;
3847
+ if (!raw) return null;
3848
+ const parsed = Number(raw);
3849
+ if (!Number.isFinite(parsed) || parsed <= 0) return null;
3850
+ return parsed;
3851
+ }
3852
+ function findHypeUsdcSpotPairCoin(meta) {
3853
+ const hype = meta.tokens.find((t) => t.name === "HYPE");
3854
+ if (!hype) return null;
3855
+ const pair = meta.universe.find(
3856
+ (p) => p.tokens[0] === hype.index && p.tokens[1] === 0
3857
+ );
3858
+ if (!pair) return null;
3859
+ return `@${pair.index}`;
3860
+ }
3861
+
3862
+ // src/streams/candle-utils.ts
3863
+ var INTERVAL_REGEX = /^(\d+)([mhd])$/;
3864
+ function intervalToMs(interval) {
3865
+ const match = interval.match(INTERVAL_REGEX);
3866
+ if (!match) return 36e5;
3867
+ const n = Number(match[1]);
3868
+ switch (match[2]) {
3869
+ case "m":
3870
+ return n * 6e4;
3871
+ case "h":
3872
+ return n * 36e5;
3873
+ case "d":
3874
+ return n * 864e5;
3875
+ default:
3876
+ return 36e5;
3877
+ }
3878
+ }
3879
+ function candleBoundaryMs(timestampMs, intervalMs) {
3880
+ return Math.floor(timestampMs / intervalMs) * intervalMs;
3881
+ }
3882
+ function processTick(candles, price, timestampMs, intervalMs) {
3883
+ if (candles.length === 0) {
3884
+ const boundary = candleBoundaryMs(timestampMs, intervalMs);
3885
+ candles.push({
3886
+ time: boundary / 1e3,
3887
+ open: price,
3888
+ high: price,
3889
+ low: price,
3890
+ close: price,
3891
+ volume: 0
3892
+ });
3893
+ return true;
3894
+ }
3895
+ const last = candles[candles.length - 1];
3896
+ const lastBoundaryMs = last.time * 1e3;
3897
+ const tickBoundaryMs = candleBoundaryMs(timestampMs, intervalMs);
3898
+ if (tickBoundaryMs === lastBoundaryMs) {
3899
+ last.close = price;
3900
+ last.high = Math.max(last.high, price);
3901
+ last.low = Math.min(last.low, price);
3902
+ return true;
3903
+ }
3904
+ if (tickBoundaryMs > lastBoundaryMs) {
3905
+ candles.push({
3906
+ time: tickBoundaryMs / 1e3,
3907
+ open: price,
3908
+ high: price,
3909
+ low: price,
3910
+ close: price,
3911
+ volume: 0
3912
+ });
3913
+ return true;
3914
+ }
3915
+ return false;
3916
+ }
3917
+
3918
+ // src/streams/perp-price-feed.ts
3919
+ function parseWsCandle(raw) {
3920
+ return {
3921
+ time: raw.t / 1e3,
3922
+ open: parseFloat(raw.o),
3923
+ high: parseFloat(raw.h),
3924
+ low: parseFloat(raw.l),
3925
+ close: parseFloat(raw.c),
3926
+ volume: parseFloat(raw.v)
3927
+ };
3928
+ }
3929
+ function mergeCandle(candles, incoming) {
3930
+ if (candles.length === 0) {
3931
+ candles.push(incoming);
3932
+ return;
3933
+ }
3934
+ const last = candles[candles.length - 1];
3935
+ if (incoming.time === last.time) {
3936
+ last.open = incoming.open;
3937
+ last.high = incoming.high;
3938
+ last.low = incoming.low;
3939
+ last.close = incoming.close;
3940
+ last.volume = incoming.volume;
3941
+ } else if (incoming.time > last.time) {
3942
+ candles.push(incoming);
3943
+ }
3944
+ }
3945
+ function bridgeToNow(candles, intervalMs) {
3946
+ if (candles.length === 0) return;
3947
+ const last = candles[candles.length - 1];
3948
+ const nowMs = Date.now();
3949
+ const lastMs = last.time * 1e3;
3950
+ if (nowMs - lastMs <= intervalMs * 2) return;
3951
+ const boundary = Math.floor(nowMs / intervalMs) * intervalMs - intervalMs;
3952
+ if (boundary <= lastMs) return;
3953
+ candles.push({
3954
+ time: boundary / 1e3,
3955
+ open: last.close,
3956
+ high: last.close,
3957
+ low: last.close,
3958
+ close: last.close,
3959
+ volume: 0
3960
+ });
3961
+ }
3962
+ function trimToWindow(candles, lookbackMs) {
3963
+ if (candles.length === 0) return candles;
3964
+ const windowStartSec = (Date.now() - lookbackMs) / 1e3;
3965
+ const firstInWindow = candles.findIndex((c) => c.time >= windowStartSec);
3966
+ if (firstInWindow === 0) return candles;
3967
+ if (firstInWindow === -1) return candles.slice(-1);
3968
+ return candles.slice(firstInWindow - 1);
3969
+ }
3970
+ function createPerpPriceFeed(client, coin, onSnapshot, options) {
3971
+ const interval = options?.interval ?? "1h";
3972
+ const lookbackMs = options?.lookbackMs ?? 14 * 24 * 60 * 60 * 1e3;
3973
+ const intervalMs = intervalToMs(interval);
3974
+ let destroyed = false;
3975
+ let candles = [];
3976
+ let currentMid = null;
3977
+ let ready = false;
3978
+ let bufferedCandles = [];
3979
+ function emit() {
3980
+ if (destroyed) return;
3981
+ const trimmed = trimToWindow(candles, lookbackMs);
3982
+ onSnapshot({
3983
+ coin,
3984
+ candles: trimmed.map((c) => ({ ...c })),
3985
+ currentMid,
3986
+ ready
3987
+ });
3988
+ }
3989
+ const unsubMids = client.subscribe({ type: "allMids" }, (data) => {
3990
+ if (destroyed) return;
3991
+ const mids = data?.mids;
3992
+ if (!mids) return;
3993
+ const raw = mids[coin];
3994
+ if (raw === void 0) return;
3995
+ const mid = Number.parseFloat(raw);
3996
+ if (Number.isNaN(mid)) return;
3997
+ if (currentMid === null) {
3998
+ currentMid = mid;
3999
+ if (ready) emit();
4000
+ }
4001
+ });
4002
+ const unsubCandle = client.subscribe(
4003
+ { type: "candle", coin, interval },
4004
+ (data) => {
4005
+ if (destroyed) return;
4006
+ const raw = data;
4007
+ if (!raw?.t || !raw?.o) return;
4008
+ if (raw.s !== void 0 && raw.s !== coin) return;
4009
+ if (raw.i !== void 0 && raw.i !== interval) return;
4010
+ if (!ready) {
4011
+ bufferedCandles.push(raw);
4012
+ return;
4013
+ }
4014
+ mergeCandle(candles, parseWsCandle(raw));
4015
+ emit();
4016
+ }
4017
+ );
4018
+ const unsubMarkPrice = client.subscribe(
4019
+ { type: "activeAssetCtx", coin },
4020
+ (data) => {
4021
+ if (destroyed) return;
4022
+ const msg = data;
4023
+ if (!msg?.ctx?.markPx) return;
4024
+ if (msg.coin !== coin) return;
4025
+ const mark = Number.parseFloat(msg.ctx.markPx);
4026
+ if (Number.isNaN(mark)) return;
4027
+ currentMid = mark;
4028
+ if (ready) emit();
4029
+ }
4030
+ );
4031
+ const MIN_FETCH_LOOKBACK = 7 * 24 * 60 * 60 * 1e3;
4032
+ const fetchLookback = Math.max(lookbackMs, MIN_FETCH_LOOKBACK);
4033
+ const now = Date.now();
4034
+ client.fetchCandleSnapshot(coin, interval, now - fetchLookback, now).then((historical) => {
4035
+ if (destroyed) return;
4036
+ candles = historical.map((c) => ({
4037
+ time: c.t / 1e3,
4038
+ open: parseFloat(c.o),
4039
+ high: parseFloat(c.h),
4040
+ low: parseFloat(c.l),
4041
+ close: parseFloat(c.c),
4042
+ volume: parseFloat(c.v)
4043
+ }));
4044
+ for (const raw of bufferedCandles) {
4045
+ mergeCandle(candles, parseWsCandle(raw));
4046
+ }
4047
+ bufferedCandles = [];
4048
+ bridgeToNow(candles, intervalMs);
4049
+ ready = true;
4050
+ emit();
4051
+ }).catch((err) => {
4052
+ console.warn("[perp-price-feed] candle fetch failed:", err);
4053
+ if (destroyed) return;
4054
+ for (const raw of bufferedCandles) {
4055
+ mergeCandle(candles, parseWsCandle(raw));
4056
+ }
4057
+ bufferedCandles = [];
4058
+ bridgeToNow(candles, intervalMs);
4059
+ ready = true;
4060
+ emit();
4061
+ });
4062
+ return () => {
4063
+ destroyed = true;
4064
+ unsubMids();
4065
+ unsubCandle();
4066
+ unsubMarkPrice();
4067
+ };
4068
+ }
4069
+
4070
+ // src/streams/price-feed.ts
4071
+ function createPriceFeed(marketData, marketId, onSnapshot, options) {
4072
+ const interval = options?.interval ?? "1h";
4073
+ const lookbackMs = options?.lookbackMs ?? 14 * 24 * 60 * 60 * 1e3;
4074
+ const sideIndex = options?.sideIndex ?? 0;
4075
+ const intervalMs = intervalToMs(interval);
4076
+ let destroyed = false;
4077
+ let candles = [];
4078
+ let currentMid = null;
4079
+ let ready = false;
4080
+ let bufferedTicks = [];
4081
+ function emit() {
4082
+ if (destroyed) return;
4083
+ onSnapshot({
4084
+ marketId,
4085
+ candles: candles.map((c) => ({ ...c })),
4086
+ currentMid,
4087
+ ready
4088
+ });
4089
+ }
4090
+ function processTick2(price, timestampMs) {
4091
+ currentMid = price;
4092
+ processTick(candles, price, timestampMs, intervalMs);
4093
+ }
4094
+ const unsubPrice = marketData.subscribePrice(marketId, (priceData) => {
4095
+ if (destroyed) return;
4096
+ const outcome = priceData.outcomes[sideIndex];
4097
+ if (!outcome) return;
4098
+ const mid = parseFloat(outcome.midpoint);
4099
+ if (Number.isNaN(mid)) return;
4100
+ if (!ready) {
4101
+ bufferedTicks.push({ price: mid, timestamp: priceData.timestamp });
4102
+ return;
4103
+ }
4104
+ processTick2(mid, priceData.timestamp);
4105
+ emit();
4106
+ });
4107
+ const now = Date.now();
4108
+ marketData.fetchCandles(marketId, interval, now - lookbackMs, now).then((historical) => {
4109
+ if (destroyed) return;
4110
+ candles = historical.map((c) => ({
4111
+ time: c.time,
4112
+ open: c.open,
4113
+ high: c.high,
4114
+ low: c.low,
4115
+ close: c.close,
4116
+ volume: c.volume
4117
+ }));
4118
+ for (const tick of bufferedTicks) {
4119
+ processTick2(tick.price, tick.timestamp);
4120
+ }
4121
+ bufferedTicks = [];
4122
+ ready = true;
4123
+ emit();
4124
+ }).catch(() => {
4125
+ if (destroyed) return;
4126
+ for (const tick of bufferedTicks) {
4127
+ processTick2(tick.price, tick.timestamp);
4128
+ }
4129
+ bufferedTicks = [];
4130
+ ready = true;
4131
+ emit();
4132
+ });
4133
+ return () => {
4134
+ destroyed = true;
4135
+ unsubPrice();
4136
+ };
4137
+ }
4138
+
4139
+ // src/utils/orderbook.ts
4140
+ function computeEstimatedCost(tokenAmount, orderType, limitPriceCents, marketPriceCents) {
4141
+ if (tokenAmount <= 0) return 0;
4142
+ if (orderType === "market") {
4143
+ if (marketPriceCents !== void 0 && marketPriceCents > 0) {
4144
+ return toNum(
4145
+ toDecimal(tokenAmount).times(toDecimal(marketPriceCents)).dividedBy(100).toString()
4146
+ );
4147
+ }
4148
+ return tokenAmount;
4149
+ }
4150
+ if (!limitPriceCents || limitPriceCents <= 0) return 0;
4151
+ return toNum(
4152
+ toDecimal(tokenAmount).times(toDecimal(limitPriceCents)).dividedBy(100).toString()
4153
+ );
4154
+ }
4155
+ function computeTradeCost(params) {
4156
+ const { tokenAmount, orderType, limitPriceCents, marketPriceCents } = params;
4157
+ const estimatedCost = orderType === "limit" ? computeEstimatedCost(tokenAmount, "limit", limitPriceCents) : computeEstimatedCost(tokenAmount, "market", null, marketPriceCents);
4158
+ const potentialReturn = computePotentialReturn(tokenAmount);
4159
+ return {
4160
+ estimatedCost,
4161
+ potentialReturn,
4162
+ displayShares: tokenAmount
4163
+ };
4164
+ }
4165
+ function computePotentialReturn(tokenAmount) {
4166
+ return Math.max(0, tokenAmount);
4167
+ }
4168
+
4169
+ exports.ALL_DEXS = ALL_DEXS;
4170
+ exports.CORE_TO_EVM_GAS_LIMIT = CORE_TO_EVM_GAS_LIMIT;
4171
+ exports.HIP4Client = HIP4Client;
4172
+ exports.HIP4EventAdapter = HIP4EventAdapter;
4173
+ exports.HIP4TradingAdapter = HIP4TradingAdapter;
4174
+ exports.HIP4WalletAdapter = HIP4WalletAdapter;
4175
+ exports.HLApiError = HLApiError;
4176
+ exports.HYPE_CORE_EVM_SYSTEM_ADDRESS = HYPE_CORE_EVM_SYSTEM_ADDRESS;
4177
+ exports.HYPE_USDC_SPOT_PAIR_MAINNET = HYPE_USDC_SPOT_PAIR_MAINNET;
4178
+ exports.HYPE_USDC_SPOT_PAIR_TESTNET = HYPE_USDC_SPOT_PAIR_TESTNET;
4179
+ exports.MAX_CORE_EVM_TOKEN_INDEX = MAX_CORE_EVM_TOKEN_INDEX;
4180
+ exports.MIN_NOTIONAL = MIN_NOTIONAL;
4181
+ exports.USDC_HL_TOKEN_MAINNET = USDC_HL_TOKEN_MAINNET;
4182
+ exports.USDC_HL_TOKEN_TESTNET = USDC_HL_TOKEN_TESTNET;
4183
+ exports.USDH_ASSET_ID = USDH_ASSET_ID;
4184
+ exports.USDH_ASSET_ID_MAINNET = USDH_ASSET_ID_MAINNET;
4185
+ exports.USDH_ASSET_ID_TESTNET = USDH_ASSET_ID_TESTNET;
4186
+ exports.USDH_CORE_DECIMALS = USDH_CORE_DECIMALS;
4187
+ exports.USDH_EVM_ADDRESS_MAINNET = USDH_EVM_ADDRESS_MAINNET;
4188
+ exports.USDH_EVM_ADDRESS_TESTNET = USDH_EVM_ADDRESS_TESTNET;
4189
+ exports.USDH_EVM_DECIMALS = USDH_EVM_DECIMALS;
4190
+ exports.USDH_HL_TOKEN_MAINNET = USDH_HL_TOKEN_MAINNET;
4191
+ exports.USDH_HL_TOKEN_TESTNET = USDH_HL_TOKEN_TESTNET;
4192
+ exports.USDH_SPOT_INDEX_MAINNET = USDH_SPOT_INDEX_MAINNET;
4193
+ exports.USDH_SPOT_INDEX_TESTNET = USDH_SPOT_INDEX_TESTNET;
4194
+ exports.USDH_SPOT_PAIR = USDH_SPOT_PAIR;
4195
+ exports.USDH_SPOT_PAIR_MAINNET = USDH_SPOT_PAIR_MAINNET;
4196
+ exports.USDH_SPOT_PAIR_TESTNET = USDH_SPOT_PAIR_TESTNET;
4197
+ exports.USDH_TOKEN_HASH_MAINNET = USDH_TOKEN_HASH_MAINNET;
4198
+ exports.USDH_TOKEN_HASH_TESTNET = USDH_TOKEN_HASH_TESTNET;
4199
+ exports.USDH_TOKEN_INDEX_MAINNET = USDH_TOKEN_INDEX_MAINNET;
4200
+ exports.USDH_TOKEN_INDEX_TESTNET = USDH_TOKEN_INDEX_TESTNET;
4201
+ exports.buildQuestionIndex = buildQuestionIndex;
4202
+ exports.candleBoundaryMs = candleBoundaryMs;
4203
+ exports.classifyAllOutcomes = classifyAllOutcomes;
4204
+ exports.classifyOutcome = classifyOutcome;
4205
+ exports.computeEstimatedCost = computeEstimatedCost;
4206
+ exports.computePotentialReturn = computePotentialReturn;
4207
+ exports.computeTickSize = computeTickSize;
4208
+ exports.computeTradeCost = computeTradeCost;
4209
+ exports.createHIP4Adapter = createHIP4Adapter;
4210
+ exports.createPerpPriceFeed = createPerpPriceFeed;
4211
+ exports.createPriceFeed = createPriceFeed;
4212
+ exports.deriveCoreEvmSystemAddress = deriveCoreEvmSystemAddress;
4213
+ exports.discoverPriceBinaryMarkets = discoverPriceBinaryMarkets;
4214
+ exports.estimateCoreToEvmFee = estimateCoreToEvmFee;
4215
+ exports.findHypeUsdcSpotPairCoin = findHypeUsdcSpotPairCoin;
4216
+ exports.formatMarketLabel = formatMarketLabel;
4217
+ exports.formatPredictionPrice = formatPredictionPrice;
4218
+ exports.formatPrice = formatPrice;
4219
+ exports.getAgentApprovalTypedData = getAgentApprovalTypedData;
4220
+ exports.getBuilderFeeApprovalTypedData = getBuilderFeeApprovalTypedData;
4221
+ exports.getMinShares = getMinShares;
4222
+ exports.getPriceBucketBounds = getPriceBucketBounds;
4223
+ exports.intervalToMs = intervalToMs;
4224
+ exports.isUsdClassTransferRequired = isUsdClassTransferRequired;
4225
+ exports.medianBaseFeeWei = medianBaseFeeWei;
4226
+ exports.normalizeSignature = normalizeSignature;
4227
+ exports.outcomeCoin = outcomeCoin;
4228
+ exports.parseDescription = parseDescription;
4229
+ exports.parseOutcomeCoin = parseOutcomeCoin;
4230
+ exports.parsePriceBucketDescription = parsePriceBucketDescription;
4231
+ exports.parseSideCoin = parseSideCoin;
4232
+ exports.periodMinutes = periodMinutes;
4233
+ exports.processTick = processTick;
4234
+ exports.roundToTick = roundToTick;
4235
+ exports.selectHypeSpotMarkPx = selectHypeSpotMarkPx;
4236
+ exports.sideAssetId = sideAssetId;
4237
+ exports.sideCoin = sideCoin;
4238
+ exports.splitHexSignature = splitHexSignature;
4239
+ exports.stripZeros = stripZeros;
4240
+ exports.submitAgentApproval = submitAgentApproval;
4241
+ exports.submitBuilderFeeApproval = submitBuilderFeeApproval;
4242
+ exports.timeToExpiry = timeToExpiry;
4243
+ //# sourceMappingURL=index.cjs.map
4244
+ //# sourceMappingURL=index.cjs.map