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