openbroker 1.1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +54 -5
- package/bin/cli.ts +26 -2
- package/bin/openbroker.js +4 -0
- package/package.json +4 -1
- package/scripts/auto/cli.ts +89 -3
- package/scripts/auto/prune.ts +252 -0
- package/scripts/auto/runtime.ts +26 -11
- package/scripts/core/client.ts +363 -1
- package/scripts/core/types.ts +50 -0
- package/scripts/info/all-markets.ts +47 -4
- package/scripts/info/outcomes.ts +200 -0
- package/scripts/info/search-markets.ts +40 -5
- package/scripts/operations/outcome-order.ts +185 -0
package/scripts/auto/runtime.ts
CHANGED
|
@@ -288,6 +288,12 @@ function createAuditedClient(
|
|
|
288
288
|
async function buildSnapshot(
|
|
289
289
|
client: HyperliquidClient,
|
|
290
290
|
): Promise<AutomationSnapshot> {
|
|
291
|
+
// `metaAndAssetCtxs` contains both mostly-static market metadata and live
|
|
292
|
+
// funding/premium values. The client caches it for market lookups, so clear
|
|
293
|
+
// it before each automation poll snapshot or funding_update events freeze at
|
|
294
|
+
// the first fetched value.
|
|
295
|
+
client.invalidateMetaCache();
|
|
296
|
+
|
|
291
297
|
const [state, mids, metaCtxs] = await Promise.all([
|
|
292
298
|
client.getUserStateAll(),
|
|
293
299
|
client.getAllMids(),
|
|
@@ -321,20 +327,29 @@ async function buildSnapshot(
|
|
|
321
327
|
|
|
322
328
|
// Build funding rates from asset contexts
|
|
323
329
|
const fundingRates = new Map<string, { rate: number; premium: number }>();
|
|
330
|
+
const addFundingRates = (
|
|
331
|
+
universe: Array<{ name?: string }> | undefined,
|
|
332
|
+
assetCtxs: Array<{ funding?: string | number | null; premium?: string | number | null }> | undefined,
|
|
333
|
+
) => {
|
|
334
|
+
if (!universe || !assetCtxs) return;
|
|
335
|
+
for (let i = 0; i < universe.length; i++) {
|
|
336
|
+
const meta = universe[i];
|
|
337
|
+
const ctx = assetCtxs[i];
|
|
338
|
+
if (ctx && meta?.name) {
|
|
339
|
+
fundingRates.set(meta.name, {
|
|
340
|
+
rate: parseFloat(String(ctx.funding || '0')),
|
|
341
|
+
premium: parseFloat(String(ctx.premium || '0')),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
324
347
|
if (metaCtxs && Array.isArray(metaCtxs)) {
|
|
325
348
|
for (const group of metaCtxs) {
|
|
326
|
-
|
|
327
|
-
for (let i = 0; i < group.universe.length; i++) {
|
|
328
|
-
const meta = group.universe[i];
|
|
329
|
-
const ctx = group.assetCtxs[i];
|
|
330
|
-
if (ctx && meta) {
|
|
331
|
-
fundingRates.set(meta.name, {
|
|
332
|
-
rate: parseFloat(ctx.funding || '0'),
|
|
333
|
-
premium: parseFloat(ctx.premium || '0'),
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
}
|
|
349
|
+
addFundingRates(group.universe, group.assetCtxs);
|
|
337
350
|
}
|
|
351
|
+
} else if (metaCtxs) {
|
|
352
|
+
addFundingRates(metaCtxs.meta?.universe, metaCtxs.assetCtxs);
|
|
338
353
|
}
|
|
339
354
|
|
|
340
355
|
return {
|
package/scripts/core/client.ts
CHANGED
|
@@ -13,6 +13,9 @@ import type {
|
|
|
13
13
|
AssetCtx,
|
|
14
14
|
ClearinghouseState,
|
|
15
15
|
OpenOrder,
|
|
16
|
+
OutcomeMetaResponse,
|
|
17
|
+
OutcomeMarket,
|
|
18
|
+
OutcomeQuestion,
|
|
16
19
|
} from './types.js';
|
|
17
20
|
import { loadConfig, isMainnet } from './config.js';
|
|
18
21
|
import { roundPrice, roundSize } from './utils.js';
|
|
@@ -51,6 +54,8 @@ export class HyperliquidClient {
|
|
|
51
54
|
private spotSzDecimalsMap: Map<string, number> = new Map();
|
|
52
55
|
/** Whether spot metadata has been loaded */
|
|
53
56
|
private spotMetaLoaded: boolean = false;
|
|
57
|
+
/** HIP-4 outcome metadata cache */
|
|
58
|
+
private outcomeMeta: OutcomeMetaResponse | null = null;
|
|
54
59
|
public verbose: boolean = false;
|
|
55
60
|
|
|
56
61
|
constructor(config?: OpenBrokerConfig) {
|
|
@@ -658,6 +663,247 @@ export class HyperliquidClient {
|
|
|
658
663
|
};
|
|
659
664
|
}
|
|
660
665
|
|
|
666
|
+
// ============ HIP-4 Outcomes ============
|
|
667
|
+
|
|
668
|
+
private parseOutcomeDescription(description: string): Record<string, string> {
|
|
669
|
+
const parsed: Record<string, string> = {};
|
|
670
|
+
for (const part of description.split('|')) {
|
|
671
|
+
const idx = part.indexOf(':');
|
|
672
|
+
if (idx <= 0) continue;
|
|
673
|
+
parsed[part.slice(0, idx)] = part.slice(idx + 1);
|
|
674
|
+
}
|
|
675
|
+
return parsed;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private normalizeOutcomeSide(side: string | number): 0 | 1 {
|
|
679
|
+
if (typeof side === 'number') {
|
|
680
|
+
if (side === 0 || side === 1) return side;
|
|
681
|
+
} else {
|
|
682
|
+
const normalized = side.trim().toLowerCase();
|
|
683
|
+
if (normalized === '0' || normalized === 'yes' || normalized === 'y') return 0;
|
|
684
|
+
if (normalized === '1' || normalized === 'no' || normalized === 'n') return 1;
|
|
685
|
+
}
|
|
686
|
+
throw new Error(`Invalid outcome side "${side}". Use yes/no or 0/1.`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
getOutcomeEncoding(outcome: number, side: 0 | 1): number {
|
|
690
|
+
return 10 * outcome + side;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
getOutcomeCoin(outcome: number, side: 0 | 1): string {
|
|
694
|
+
return `#${this.getOutcomeEncoding(outcome, side)}`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
getOutcomeAssetId(outcome: number, side: 0 | 1): number {
|
|
698
|
+
return 100_000_000 + this.getOutcomeEncoding(outcome, side);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
resolveOutcomeRef(ref: string | number, side?: string | number): {
|
|
702
|
+
outcome: number;
|
|
703
|
+
side: 0 | 1;
|
|
704
|
+
encoding: number;
|
|
705
|
+
coin: string;
|
|
706
|
+
tokenName: string;
|
|
707
|
+
assetId: number;
|
|
708
|
+
} {
|
|
709
|
+
if (typeof ref === 'number') {
|
|
710
|
+
const resolvedSide = this.normalizeOutcomeSide(side ?? 0);
|
|
711
|
+
const encoding = this.getOutcomeEncoding(ref, resolvedSide);
|
|
712
|
+
return {
|
|
713
|
+
outcome: ref,
|
|
714
|
+
side: resolvedSide,
|
|
715
|
+
encoding,
|
|
716
|
+
coin: `#${encoding}`,
|
|
717
|
+
tokenName: `+${encoding}`,
|
|
718
|
+
assetId: 100_000_000 + encoding,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const trimmed = ref.trim();
|
|
723
|
+
const encoded = trimmed.startsWith('#') || trimmed.startsWith('+')
|
|
724
|
+
? parseInt(trimmed.slice(1), 10)
|
|
725
|
+
: NaN;
|
|
726
|
+
|
|
727
|
+
if (!Number.isNaN(encoded)) {
|
|
728
|
+
const resolvedSide = encoded % 10;
|
|
729
|
+
if (resolvedSide !== 0 && resolvedSide !== 1) {
|
|
730
|
+
throw new Error(`Invalid outcome encoding "${ref}". Outcome side must encode to 0 or 1.`);
|
|
731
|
+
}
|
|
732
|
+
const outcome = Math.floor(encoded / 10);
|
|
733
|
+
return {
|
|
734
|
+
outcome,
|
|
735
|
+
side: resolvedSide as 0 | 1,
|
|
736
|
+
encoding: encoded,
|
|
737
|
+
coin: `#${encoded}`,
|
|
738
|
+
tokenName: `+${encoded}`,
|
|
739
|
+
assetId: 100_000_000 + encoded,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const outcome = parseInt(trimmed, 10);
|
|
744
|
+
if (!Number.isFinite(outcome) || outcome < 0) {
|
|
745
|
+
throw new Error(`Invalid outcome reference "${ref}". Use an outcome id, #<encoding>, or +<encoding>.`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const resolvedSide = this.normalizeOutcomeSide(side ?? 0);
|
|
749
|
+
const encoding = this.getOutcomeEncoding(outcome, resolvedSide);
|
|
750
|
+
return {
|
|
751
|
+
outcome,
|
|
752
|
+
side: resolvedSide,
|
|
753
|
+
encoding,
|
|
754
|
+
coin: `#${encoding}`,
|
|
755
|
+
tokenName: `+${encoding}`,
|
|
756
|
+
assetId: 100_000_000 + encoding,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async getOutcomeMeta(): Promise<OutcomeMetaResponse> {
|
|
761
|
+
if (this.outcomeMeta) return this.outcomeMeta;
|
|
762
|
+
|
|
763
|
+
this.log('Fetching outcomeMeta...');
|
|
764
|
+
const data = await this.postInfo<OutcomeMetaResponse>({ type: 'outcomeMeta' }, 'outcomeMeta');
|
|
765
|
+
if (!data || !Array.isArray(data.outcomes)) {
|
|
766
|
+
throw new Error('outcomeMeta returned empty/malformed payload.');
|
|
767
|
+
}
|
|
768
|
+
this.outcomeMeta = {
|
|
769
|
+
outcomes: data.outcomes,
|
|
770
|
+
questions: data.questions ?? [],
|
|
771
|
+
};
|
|
772
|
+
this.log(`Loaded ${this.outcomeMeta.outcomes.length} outcome markets`);
|
|
773
|
+
return this.outcomeMeta;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private async getOutcomeCtxMap(): Promise<Map<string, {
|
|
777
|
+
coin?: string;
|
|
778
|
+
dayNtlVlm?: string;
|
|
779
|
+
markPx?: string;
|
|
780
|
+
midPx?: string | null;
|
|
781
|
+
prevDayPx?: string;
|
|
782
|
+
}>> {
|
|
783
|
+
const ctxMap = new Map<string, {
|
|
784
|
+
coin?: string;
|
|
785
|
+
dayNtlVlm?: string;
|
|
786
|
+
markPx?: string;
|
|
787
|
+
midPx?: string | null;
|
|
788
|
+
prevDayPx?: string;
|
|
789
|
+
}>();
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
const spotData = await this.getSpotMetaAndAssetCtxs();
|
|
793
|
+
for (const ctx of spotData.assetCtxs) {
|
|
794
|
+
if (ctx.coin) ctxMap.set(ctx.coin, ctx);
|
|
795
|
+
}
|
|
796
|
+
} catch (e) {
|
|
797
|
+
this.log('Unable to load spot/outcome contexts:', e);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
try {
|
|
801
|
+
const mids = await this.getAllMids();
|
|
802
|
+
for (const [coin, midPx] of Object.entries(mids)) {
|
|
803
|
+
if (!coin.startsWith('#')) continue;
|
|
804
|
+
const existing = ctxMap.get(coin) ?? { coin };
|
|
805
|
+
existing.midPx = existing.midPx ?? midPx;
|
|
806
|
+
existing.markPx = existing.markPx ?? midPx;
|
|
807
|
+
ctxMap.set(coin, existing);
|
|
808
|
+
}
|
|
809
|
+
} catch (e) {
|
|
810
|
+
this.log('Unable to load allMids for outcomes:', e);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return ctxMap;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async getOutcomeMarkets(): Promise<OutcomeMarket[]> {
|
|
817
|
+
const [meta, spotMeta, ctxMap] = await Promise.all([
|
|
818
|
+
this.getOutcomeMeta(),
|
|
819
|
+
this.getSpotMeta().catch(() => null),
|
|
820
|
+
this.getOutcomeCtxMap(),
|
|
821
|
+
]);
|
|
822
|
+
|
|
823
|
+
const tokenDecimals = new Map<number, number>();
|
|
824
|
+
if (spotMeta) {
|
|
825
|
+
for (const token of spotMeta.tokens) {
|
|
826
|
+
tokenDecimals.set(token.index, token.szDecimals);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const questions = new Map<number, OutcomeQuestion>();
|
|
831
|
+
for (const question of meta.questions ?? []) {
|
|
832
|
+
for (const outcome of question.namedOutcomes) {
|
|
833
|
+
questions.set(outcome, question);
|
|
834
|
+
}
|
|
835
|
+
questions.set(question.fallbackOutcome, question);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return meta.outcomes.map((outcome) => {
|
|
839
|
+
const sides = outcome.sideSpecs.map((sideSpec, idx) => {
|
|
840
|
+
const side = idx as 0 | 1;
|
|
841
|
+
const encoding = this.getOutcomeEncoding(outcome.outcome, side);
|
|
842
|
+
const coin = `#${encoding}`;
|
|
843
|
+
const ctx = ctxMap.get(coin);
|
|
844
|
+
return {
|
|
845
|
+
side,
|
|
846
|
+
name: sideSpec.name,
|
|
847
|
+
encoding,
|
|
848
|
+
coin,
|
|
849
|
+
tokenName: `+${encoding}`,
|
|
850
|
+
assetId: 100_000_000 + encoding,
|
|
851
|
+
token: sideSpec.token,
|
|
852
|
+
szDecimals: sideSpec.token !== undefined ? tokenDecimals.get(sideSpec.token) : undefined,
|
|
853
|
+
midPx: ctx?.midPx ?? undefined,
|
|
854
|
+
markPx: ctx?.markPx,
|
|
855
|
+
prevDayPx: ctx?.prevDayPx,
|
|
856
|
+
dayNtlVlm: ctx?.dayNtlVlm,
|
|
857
|
+
};
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
return {
|
|
861
|
+
outcome: outcome.outcome,
|
|
862
|
+
name: outcome.name,
|
|
863
|
+
description: outcome.description,
|
|
864
|
+
parsedDescription: this.parseOutcomeDescription(outcome.description),
|
|
865
|
+
sides,
|
|
866
|
+
question: questions.get(outcome.outcome),
|
|
867
|
+
};
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async getOutcomeMarket(outcomeId: number): Promise<OutcomeMarket | null> {
|
|
872
|
+
const markets = await this.getOutcomeMarkets();
|
|
873
|
+
return markets.find((market) => market.outcome === outcomeId) ?? null;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async getOutcomeSzDecimals(outcome: number, side: 0 | 1): Promise<number> {
|
|
877
|
+
const market = await this.getOutcomeMarket(outcome);
|
|
878
|
+
return market?.sides.find((s) => s.side === side)?.szDecimals ?? 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async getOutcomeMidPrice(outcome: number, side: 0 | 1): Promise<number> {
|
|
882
|
+
const coin = this.getOutcomeCoin(outcome, side);
|
|
883
|
+
const markets = await this.getOutcomeMarkets();
|
|
884
|
+
const marketSide = markets
|
|
885
|
+
.find((market) => market.outcome === outcome)
|
|
886
|
+
?.sides.find((s) => s.side === side);
|
|
887
|
+
const fromMeta = marketSide?.midPx ?? marketSide?.markPx;
|
|
888
|
+
if (fromMeta) {
|
|
889
|
+
const mid = parseFloat(fromMeta);
|
|
890
|
+
if (mid > 0) return mid;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const mids = await this.getAllMids();
|
|
894
|
+
const mid = parseFloat(mids[coin] || '0');
|
|
895
|
+
if (mid > 0) return mid;
|
|
896
|
+
|
|
897
|
+
try {
|
|
898
|
+
const book = await this.getL2Book(coin);
|
|
899
|
+
if (book.midPrice > 0) return book.midPrice;
|
|
900
|
+
} catch (e) {
|
|
901
|
+
this.log(`Unable to fetch outcome L2 book for ${coin}:`, e);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
throw new Error(`No outcome price for ${coin}. The market may not be open or may have no liquidity.`);
|
|
905
|
+
}
|
|
906
|
+
|
|
661
907
|
/**
|
|
662
908
|
* Load spot metadata into lookup maps.
|
|
663
909
|
* Spot asset index for orders = 10000 + universe[i].index
|
|
@@ -821,7 +1067,11 @@ export class HyperliquidClient {
|
|
|
821
1067
|
|
|
822
1068
|
const response = await fetch(baseUrl + '/info', {
|
|
823
1069
|
method: 'POST',
|
|
824
|
-
headers: {
|
|
1070
|
+
headers: {
|
|
1071
|
+
'Content-Type': 'application/json',
|
|
1072
|
+
'Cache-Control': 'no-cache, no-store, max-age=0',
|
|
1073
|
+
Pragma: 'no-cache',
|
|
1074
|
+
},
|
|
825
1075
|
body: JSON.stringify({ type: 'predictedFundings' }),
|
|
826
1076
|
});
|
|
827
1077
|
const data = await response.json() as Array<[
|
|
@@ -2346,6 +2596,118 @@ export class HyperliquidClient {
|
|
|
2346
2596
|
);
|
|
2347
2597
|
}
|
|
2348
2598
|
|
|
2599
|
+
/**
|
|
2600
|
+
* Place a HIP-4 outcome order.
|
|
2601
|
+
* Outcome assets are spot-like, but encoded as:
|
|
2602
|
+
* encoding = 10 * outcome + side
|
|
2603
|
+
* assetId = 100_000_000 + encoding
|
|
2604
|
+
* coin = #<encoding>
|
|
2605
|
+
*
|
|
2606
|
+
* Side 0 is usually YES and side 1 is usually NO, per outcomeMeta.sideSpecs.
|
|
2607
|
+
*/
|
|
2608
|
+
async outcomeOrder(
|
|
2609
|
+
outcomeRef: string | number,
|
|
2610
|
+
outcomeSide: string | number | undefined,
|
|
2611
|
+
isBuy: boolean,
|
|
2612
|
+
size: number,
|
|
2613
|
+
price: number,
|
|
2614
|
+
orderType: { limit: { tif: 'Gtc' | 'Ioc' | 'Alo' } },
|
|
2615
|
+
includeBuilder: boolean = true,
|
|
2616
|
+
szDecimalsOverride?: number,
|
|
2617
|
+
): Promise<OrderResponse> {
|
|
2618
|
+
await this.requireTrading();
|
|
2619
|
+
|
|
2620
|
+
const resolved = this.resolveOutcomeRef(outcomeRef, outcomeSide);
|
|
2621
|
+
const szDecimals = szDecimalsOverride ?? await this.getOutcomeSzDecimals(resolved.outcome, resolved.side);
|
|
2622
|
+
|
|
2623
|
+
const orderWire = {
|
|
2624
|
+
a: resolved.assetId,
|
|
2625
|
+
b: isBuy,
|
|
2626
|
+
p: roundPrice(price, szDecimals, true),
|
|
2627
|
+
s: roundSize(size, szDecimals),
|
|
2628
|
+
r: false,
|
|
2629
|
+
t: orderType,
|
|
2630
|
+
};
|
|
2631
|
+
|
|
2632
|
+
this.log('Placing outcome order:', JSON.stringify({ resolved, orderWire }, null, 2));
|
|
2633
|
+
|
|
2634
|
+
const orderRequest: {
|
|
2635
|
+
orders: typeof orderWire[];
|
|
2636
|
+
grouping: 'na';
|
|
2637
|
+
builder?: BuilderInfo;
|
|
2638
|
+
} = {
|
|
2639
|
+
orders: [orderWire],
|
|
2640
|
+
grouping: 'na',
|
|
2641
|
+
};
|
|
2642
|
+
|
|
2643
|
+
if (includeBuilder && !this.isTestnet && this.config.builderAddress !== '0x0000000000000000000000000000000000000000') {
|
|
2644
|
+
orderRequest.builder = this.builderInfo;
|
|
2645
|
+
this.log('Including builder fee:', this.builderInfo);
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
try {
|
|
2649
|
+
const response = await this.exchange.order(orderRequest, this.vaultParam);
|
|
2650
|
+
this.log('Outcome order response:', JSON.stringify(response, null, 2));
|
|
2651
|
+
return response as unknown as OrderResponse;
|
|
2652
|
+
} catch (error) {
|
|
2653
|
+
this.log('Outcome order error:', error);
|
|
2654
|
+
return {
|
|
2655
|
+
status: 'err',
|
|
2656
|
+
response: error instanceof Error ? error.message : String(error),
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
async outcomeMarketOrder(
|
|
2662
|
+
outcomeRef: string | number,
|
|
2663
|
+
outcomeSide: string | number | undefined,
|
|
2664
|
+
isBuy: boolean,
|
|
2665
|
+
size: number,
|
|
2666
|
+
slippageBps?: number,
|
|
2667
|
+
szDecimalsOverride?: number,
|
|
2668
|
+
): Promise<OrderResponse> {
|
|
2669
|
+
const resolved = this.resolveOutcomeRef(outcomeRef, outcomeSide);
|
|
2670
|
+
const midPrice = await this.getOutcomeMidPrice(resolved.outcome, resolved.side);
|
|
2671
|
+
const slippage = (slippageBps ?? this.config.slippageBps) / 10000;
|
|
2672
|
+
const limitPrice = isBuy
|
|
2673
|
+
? midPrice * (1 + slippage)
|
|
2674
|
+
: midPrice * (1 - slippage);
|
|
2675
|
+
|
|
2676
|
+
this.log(`Outcome market order: ${resolved.coin} ${isBuy ? 'BUY' : 'SELL'} ${size} @ ${limitPrice} (mid: ${midPrice})`);
|
|
2677
|
+
|
|
2678
|
+
return this.outcomeOrder(
|
|
2679
|
+
outcomeRef,
|
|
2680
|
+
outcomeSide,
|
|
2681
|
+
isBuy,
|
|
2682
|
+
size,
|
|
2683
|
+
limitPrice,
|
|
2684
|
+
{ limit: { tif: 'Ioc' } },
|
|
2685
|
+
true,
|
|
2686
|
+
szDecimalsOverride,
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
async outcomeLimitOrder(
|
|
2691
|
+
outcomeRef: string | number,
|
|
2692
|
+
outcomeSide: string | number | undefined,
|
|
2693
|
+
isBuy: boolean,
|
|
2694
|
+
size: number,
|
|
2695
|
+
price: number,
|
|
2696
|
+
tif: 'Gtc' | 'Ioc' | 'Alo' = 'Gtc',
|
|
2697
|
+
szDecimalsOverride?: number,
|
|
2698
|
+
): Promise<OrderResponse> {
|
|
2699
|
+
return this.outcomeOrder(
|
|
2700
|
+
outcomeRef,
|
|
2701
|
+
outcomeSide,
|
|
2702
|
+
isBuy,
|
|
2703
|
+
size,
|
|
2704
|
+
price,
|
|
2705
|
+
{ limit: { tif } },
|
|
2706
|
+
true,
|
|
2707
|
+
szDecimalsOverride,
|
|
2708
|
+
);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2349
2711
|
/**
|
|
2350
2712
|
* Cancel a spot order by coin and order ID.
|
|
2351
2713
|
*/
|
package/scripts/core/types.ts
CHANGED
|
@@ -119,6 +119,56 @@ export interface MetaAndAssetCtxs {
|
|
|
119
119
|
assetCtxs: AssetCtx[];
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// ============ Outcome / HIP-4 Types ============
|
|
123
|
+
|
|
124
|
+
export interface OutcomeSideSpec {
|
|
125
|
+
name: string;
|
|
126
|
+
token?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface OutcomeMetaEntry {
|
|
130
|
+
outcome: number;
|
|
131
|
+
name: string;
|
|
132
|
+
description: string;
|
|
133
|
+
sideSpecs: OutcomeSideSpec[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface OutcomeQuestion {
|
|
137
|
+
question: number;
|
|
138
|
+
name: string;
|
|
139
|
+
description: string;
|
|
140
|
+
fallbackOutcome: number;
|
|
141
|
+
namedOutcomes: number[];
|
|
142
|
+
settledNamedOutcomes?: number[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface OutcomeMetaResponse {
|
|
146
|
+
outcomes: OutcomeMetaEntry[];
|
|
147
|
+
questions?: OutcomeQuestion[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface OutcomeMarket {
|
|
151
|
+
outcome: number;
|
|
152
|
+
name: string;
|
|
153
|
+
description: string;
|
|
154
|
+
parsedDescription: Record<string, string>;
|
|
155
|
+
sides: Array<{
|
|
156
|
+
side: 0 | 1;
|
|
157
|
+
name: string;
|
|
158
|
+
encoding: number;
|
|
159
|
+
coin: string;
|
|
160
|
+
tokenName: string;
|
|
161
|
+
assetId: number;
|
|
162
|
+
token?: number;
|
|
163
|
+
szDecimals?: number;
|
|
164
|
+
midPx?: string;
|
|
165
|
+
markPx?: string;
|
|
166
|
+
prevDayPx?: string;
|
|
167
|
+
dayNtlVlm?: string;
|
|
168
|
+
}>;
|
|
169
|
+
question?: OutcomeQuestion;
|
|
170
|
+
}
|
|
171
|
+
|
|
122
172
|
// ============ Account Types ============
|
|
123
173
|
|
|
124
174
|
export interface Position {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
5
|
|
|
6
6
|
interface Args {
|
|
7
|
-
type?: 'perp' | 'spot' | 'hip3' | 'all';
|
|
7
|
+
type?: 'perp' | 'spot' | 'hip3' | 'outcome' | 'all';
|
|
8
8
|
top?: number;
|
|
9
9
|
verbose?: boolean;
|
|
10
10
|
json?: boolean;
|
|
@@ -17,7 +17,7 @@ function parseArgs(): Args {
|
|
|
17
17
|
const arg = process.argv[i];
|
|
18
18
|
if (arg === '--type' && process.argv[i + 1]) {
|
|
19
19
|
const val = process.argv[++i].toLowerCase();
|
|
20
|
-
if (['perp', 'spot', 'hip3', 'all'].includes(val)) {
|
|
20
|
+
if (['perp', 'spot', 'hip3', 'outcome', 'all'].includes(val)) {
|
|
21
21
|
args.type = val as Args['type'];
|
|
22
22
|
}
|
|
23
23
|
} else if (arg === '--top' && process.argv[i + 1]) {
|
|
@@ -33,7 +33,7 @@ All Markets - View all available markets on Hyperliquid
|
|
|
33
33
|
Usage: npx tsx scripts/info/all-markets.ts [options]
|
|
34
34
|
|
|
35
35
|
Options:
|
|
36
|
-
--type <type> Market type: perp, spot, hip3, or all (default: all)
|
|
36
|
+
--type <type> Market type: perp, spot, hip3, outcome, or all (default: all)
|
|
37
37
|
--top <n> Show only top N markets by volume
|
|
38
38
|
--json Output as JSON (machine-readable)
|
|
39
39
|
--verbose Show detailed output
|
|
@@ -44,6 +44,7 @@ Examples:
|
|
|
44
44
|
npx tsx scripts/info/all-markets.ts --type perp # Show only main perps
|
|
45
45
|
npx tsx scripts/info/all-markets.ts --type hip3 # Show only HIP-3 perps
|
|
46
46
|
npx tsx scripts/info/all-markets.ts --type spot # Show only spot markets
|
|
47
|
+
npx tsx scripts/info/all-markets.ts --type outcome # Show only HIP-4 outcomes
|
|
47
48
|
npx tsx scripts/info/all-markets.ts --top 20 # Show top 20 by volume
|
|
48
49
|
npx tsx scripts/info/all-markets.ts --json # JSON output
|
|
49
50
|
`);
|
|
@@ -77,7 +78,7 @@ function formatFunding(rate: string): string {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
interface MarketRow {
|
|
80
|
-
type: 'perp' | 'spot' | 'hip3';
|
|
81
|
+
type: 'perp' | 'spot' | 'hip3' | 'outcome';
|
|
81
82
|
provider: string;
|
|
82
83
|
coin: string;
|
|
83
84
|
assetId: number;
|
|
@@ -85,6 +86,9 @@ interface MarketRow {
|
|
|
85
86
|
volume24h: number;
|
|
86
87
|
funding?: string;
|
|
87
88
|
maxLeverage?: number;
|
|
89
|
+
outcome?: number;
|
|
90
|
+
outcomeSide?: string;
|
|
91
|
+
description?: string;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
async function main() {
|
|
@@ -183,6 +187,30 @@ async function main() {
|
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
|
|
190
|
+
// Fetch HIP-4 outcomes
|
|
191
|
+
if (args.type === 'all' || args.type === 'outcome') {
|
|
192
|
+
try {
|
|
193
|
+
const outcomes = await client.getOutcomeMarkets();
|
|
194
|
+
for (const market of outcomes) {
|
|
195
|
+
for (const side of market.sides) {
|
|
196
|
+
allMarkets.push({
|
|
197
|
+
type: 'outcome',
|
|
198
|
+
provider: 'HIP-4',
|
|
199
|
+
coin: side.coin,
|
|
200
|
+
assetId: side.assetId,
|
|
201
|
+
price: side.midPx ?? side.markPx ?? '0',
|
|
202
|
+
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
203
|
+
outcome: market.outcome,
|
|
204
|
+
outcomeSide: side.name,
|
|
205
|
+
description: market.description,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
if (args.verbose) console.error('Failed to fetch HIP-4 outcomes:', e);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
186
214
|
// Sort by volume
|
|
187
215
|
allMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
188
216
|
|
|
@@ -198,6 +226,7 @@ async function main() {
|
|
|
198
226
|
const perps = markets.filter((m) => m.type === 'perp');
|
|
199
227
|
const hip3 = markets.filter((m) => m.type === 'hip3');
|
|
200
228
|
const spots = markets.filter((m) => m.type === 'spot');
|
|
229
|
+
const outcomes = markets.filter((m) => m.type === 'outcome');
|
|
201
230
|
|
|
202
231
|
// Print summary
|
|
203
232
|
console.log('=== Market Summary ===\n');
|
|
@@ -205,6 +234,7 @@ async function main() {
|
|
|
205
234
|
console.log(` - Main Perps: ${perps.length}`);
|
|
206
235
|
console.log(` - HIP-3 Perps: ${hip3.length}`);
|
|
207
236
|
console.log(` - Spot Markets: ${spots.length}`);
|
|
237
|
+
console.log(` - HIP-4 Outcomes: ${outcomes.length}`);
|
|
208
238
|
console.log();
|
|
209
239
|
|
|
210
240
|
// Print perps
|
|
@@ -245,6 +275,19 @@ async function main() {
|
|
|
245
275
|
}
|
|
246
276
|
console.log();
|
|
247
277
|
}
|
|
278
|
+
|
|
279
|
+
// Print HIP-4 outcome markets
|
|
280
|
+
if (outcomes.length > 0) {
|
|
281
|
+
console.log('=== HIP-4 Outcomes ===\n');
|
|
282
|
+
console.log('Coin Outcome Side AssetID Price 24h Volume');
|
|
283
|
+
console.log('-'.repeat(78));
|
|
284
|
+
for (const m of outcomes) {
|
|
285
|
+
console.log(
|
|
286
|
+
`${m.coin.padEnd(14)} ${String(m.outcome ?? '-').padStart(7)} ${(m.outcomeSide ?? '-').padEnd(8)} ${String(m.assetId).padStart(9)} ${formatPrice(m.price).padStart(7)} ${formatVolume(m.volume24h).padStart(13)}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
console.log();
|
|
290
|
+
}
|
|
248
291
|
}
|
|
249
292
|
|
|
250
293
|
main().catch((e) => {
|